| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- from __future__ import annotations
- import re
- from collections.abc import Callable, Iterable
- from typing import Any
- from .schema import RuntimeUnit, field
- EventLoader = Callable[[int], dict[str, Any]]
- def integer(value: Any) -> int | None:
- try:
- return int(value)
- except (TypeError, ValueError):
- return None
- def suffix_int(ref: str) -> int | None:
- return integer(str(ref).rsplit(":", 1)[-1])
- def event_content(side: Any) -> Any:
- return side.get("content") if isinstance(side, dict) else None
- def event_completeness(event: dict[str, Any]) -> str:
- has_input = event_content(event.get("input")) not in (None, "", [], {})
- has_output = event_content(event.get("output")) not in (None, "", [], {})
- if has_input and has_output:
- return "complete"
- if has_input or has_output or event:
- return "partial"
- return "missing"
- def event_unit(event: dict[str, Any], *, label: str | None = None) -> RuntimeUnit:
- event_id = integer(event.get("id")) or 0
- status = str(event.get("status") or "unknown")
- event_label = label or str(event.get("businessLabel") or event.get("title") or event.get("event_name") or f"运行事件 {event_id}")
- return {
- "id": f"runtime:event:{event_id}",
- "label": event_label,
- "status": status,
- "summary": event_summary(event, event_label),
- "input": event.get("input"),
- "output": event.get("output"),
- "durationMs": integer(event.get("duration_ms")),
- "eventRef": f"event:{event_id}",
- "completeness": event_completeness(event), # type: ignore[typeddict-item]
- "startedAt": event.get("started_at"),
- "endedAt": event.get("ended_at"),
- }
- def event_summary(event: dict[str, Any], fallback: str) -> str:
- if event.get("error_message"):
- return f"技术失败:{event['error_message']}"
- output = event_content(event.get("output"))
- if isinstance(output, dict):
- for key in ("summary", "message", "conclusion"):
- if output.get(key):
- return str(output[key])
- if event.get("output_preview"):
- return str(event["output_preview"])
- return fallback
- def load_events(loader: EventLoader, refs: Iterable[Any]) -> list[dict[str, Any]]:
- values: list[dict[str, Any]] = []
- seen: set[int] = set()
- for ref in refs:
- event_id = suffix_int(str(ref)) if isinstance(ref, str) else integer(ref)
- if event_id is None or event_id in seen:
- continue
- seen.add(event_id)
- try:
- values.append(loader(event_id))
- except LookupError:
- continue
- return values
- def event_notices(events: Iterable[dict[str, Any]]) -> list[dict[str, str]]:
- notices: list[dict[str, str]] = []
- omitted = 0
- missing_body = 0
- for event in events:
- for side_name in ("input", "output"):
- side = event.get(side_name)
- if isinstance(side, dict) and side.get("truncated"):
- omitted += int(side.get("omittedCharacters") or 0)
- if event.get("input") is None and event.get("output") is None:
- missing_body += 1
- if omitted:
- notices.append({"level": "warning", "message": f"Event Body 超过读取上限,共省略 {omitted} 个字符。"})
- if missing_body:
- notices.append({"level": "warning", "message": f"{missing_body} 个运行事件没有保存可用的 Event Body。"})
- return notices
- def find_by_id(items: Iterable[dict[str, Any]], record_id: int | None) -> dict[str, Any] | None:
- return next((item for item in items if integer(item.get("id")) == record_id), None)
- def find_round(view: dict[str, Any], round_index: int) -> dict[str, Any] | None:
- return next((item for item in view.get("rounds") or [] if integer(item.get("roundIndex")) == round_index), None)
- def find_raw_round(bundle: dict[str, Any], round_index: int) -> dict[str, Any] | None:
- return next((item for item in bundle.get("rounds") or [] if integer(item.get("round_index")) == round_index), None)
- def find_branch(view: dict[str, Any], round_index: int, branch_id: int) -> dict[str, Any] | None:
- round_ = find_round(view, round_index)
- return next((item for item in (round_ or {}).get("branches") or [] if integer(item.get("branchId")) == branch_id), None)
- def find_raw_branch(bundle: dict[str, Any], branch_id: int) -> dict[str, Any] | None:
- return next((item for item in bundle.get("branches") or [] if integer(item.get("branch_id")) == branch_id), None)
- def find_detail(value: Any, detail_ref: str) -> dict[str, Any] | None:
- if isinstance(value, dict):
- if value.get("detailRef") == detail_ref:
- return value
- for child in value.values():
- found = find_detail(child, detail_ref)
- if found is not None:
- return found
- elif isinstance(value, list):
- for child in value:
- found = find_detail(child, detail_ref)
- if found is not None:
- return found
- return None
- def decision_context(node: dict[str, Any] | None) -> dict[str, Any]:
- if not isinstance(node, dict):
- return {}
- decision = node.get("decision")
- if isinstance(decision, dict):
- body = decision.get("body")
- if isinstance(body, dict):
- return {**decision, **body}
- return decision
- return node
- def context_input_fields(
- context: dict[str, Any],
- prefix: str,
- events: list[dict[str, Any]] | None = None,
- ) -> list[dict[str, Any]]:
- relation_map = {
- "direct-read": "direct-input",
- "standing-constraint": "standing-constraint",
- "persisted-record": "previous-result",
- "explicit-basis": "explicit-basis",
- }
- fields: list[dict[str, Any]] = []
- events_by_ref = {
- f"event:{integer(event.get('id'))}": event
- for event in events or []
- if integer(event.get("id"))
- }
- for index, item in enumerate(context.get("inputs") or [], 1):
- if not isinstance(item, dict):
- continue
- raw_relation = str(item.get("relation") or "")
- relation = relation_map.get(raw_relation, "available-upstream")
- detail_ref = str(item.get("detailRef") or "")
- label = str(item.get("label") or f"上游输入 {index}")
- if not detail_ref:
- detail_ref = _implicit_context_ref(prefix, label)
- event = events_by_ref.get(detail_ref) or {}
- event_name = str(event.get("event_name") or "")
- source_kind, source_label, field_path = _context_source(
- detail_ref, raw_relation, event_name
- )
- fields.append(field(
- f"{prefix}:input:{index}",
- label,
- item.get("summary"),
- source_kind=source_kind,
- source_label=source_label,
- source_ref=detail_ref or None,
- field_path=field_path,
- relation=relation,
- ))
- return fields
- def _context_source(
- detail_ref: str,
- raw_relation: str,
- event_name: str,
- ) -> tuple[str, str, str]:
- """Classify context sources from their real reference contract."""
- event_paths = {
- "save_script_direction": "input.content.script_direction",
- "begin_round": "input.content.goal",
- "record_multipath_plan": "input.content",
- }
- if event_name or detail_ref.startswith("event:"):
- return (
- "runtime-event",
- "明确读取的运行事件" if raw_relation == "direct-read" else "上游运行事件",
- event_paths.get(event_name, ""),
- )
- database_refs = (
- (r"^run:objective$", "script_build_record", "script_direction"),
- (r"^round:\d+:goal$", "script_build_round", "goal"),
- (r"^multipath-decision:\d+$", "script_build_multipath_decision", "decision"),
- (r"^data-decision:\d+$", "script_build_data_decision", "decision"),
- (r"^domain-info:\d+$", "script_build_domain_info", "content"),
- )
- for pattern, table, field_path in database_refs:
- if re.match(pattern, detail_ref):
- return "database", table, field_path
- if detail_ref.startswith("artifact:"):
- return "artifact", "脚本或候选产物", ""
- return "database", "上游业务结果", ""
- def _implicit_context_ref(prefix: str, label: str) -> str:
- """Recover only deterministic upstream refs omitted by historical views."""
- if prefix == "objective" and label in {"选题", "账号人设"}:
- return ""
- if re.match(r"^round:\d+:goal$", prefix) and label == "当前保存的创作目标":
- return "run:objective"
- match = re.match(r"^(round:\d+):plan$", prefix)
- if match and label == "本轮目标":
- return f"{match.group(1)}:goal"
- return ""
- def decision_event_refs(context: dict[str, Any]) -> list[str]:
- refs: list[str] = []
- for key in ("revisionRefs", "technicalRefs", "eventRefs"):
- for ref in context.get(key) or []:
- if isinstance(ref, str) and ref.startswith("event:") and ref not in refs:
- refs.append(ref)
- for key in ("currentRevisionRef", "eventRef"):
- ref = context.get(key)
- if isinstance(ref, str) and ref.startswith("event:") and ref not in refs:
- refs.append(ref)
- for item in context.get("inputs") or []:
- ref = item.get("detailRef") if isinstance(item, dict) else None
- if isinstance(ref, str) and ref.startswith("event:") and ref not in refs:
- refs.append(ref)
- for item in context.get("revisions") or []:
- ref = item.get("detailRef") if isinstance(item, dict) else None
- if isinstance(ref, str) and ref.startswith("event:") and ref not in refs:
- refs.append(ref)
- return refs
- def output_field_from_event(event: dict[str, Any], prefix: str, label: str, relation: str = "persisted-output") -> dict[str, Any]:
- event_id = integer(event.get("id")) or 0
- return field(
- f"{prefix}:event:{event_id}:output",
- label,
- event_content(event.get("output")),
- source_kind="runtime-event",
- source_label="Event Body 输出",
- source_ref=f"event:{event_id}",
- field_path="output.content",
- relation=relation,
- completeness=event_completeness(event),
- )
|