implementation.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. from __future__ import annotations
  2. from typing import Any
  3. from .common import (
  4. EventLoader,
  5. decision_context,
  6. decision_event_refs,
  7. event_content,
  8. event_notices,
  9. event_unit,
  10. find_branch,
  11. find_by_id,
  12. find_detail,
  13. find_raw_branch,
  14. integer,
  15. load_events,
  16. suffix_int,
  17. )
  18. from .schema import field, payload
  19. def project_implementation_task(
  20. detail_ref: str,
  21. round_index: int,
  22. branch_id: int,
  23. bundle: dict[str, Any],
  24. view: dict[str, Any],
  25. load_event: EventLoader,
  26. ) -> dict[str, Any]:
  27. raw = find_raw_branch(bundle, branch_id) or {}
  28. branch = find_branch(view, round_index, branch_id) or {}
  29. stage = branch.get("retrievalStage") or {}
  30. implementer_id = integer(stage.get("implementerEventId"))
  31. events = load_events(load_event, [implementer_id] if implementer_id else [])
  32. full_task = _agent_task(events[0]) if events else None
  33. task_value = full_task or raw.get("impl_task") or raw.get("target")
  34. task_source = "runtime-event" if full_task else "database"
  35. task_source_label = "实现 Agent Event Body" if full_task else "script_build_branch(历史回退)"
  36. task_source_ref = f"event:{implementer_id}" if full_task and implementer_id else detail_ref
  37. inputs = [
  38. field(
  39. f"branch:{branch_id}:task",
  40. "完整实现任务",
  41. task_value,
  42. source_kind=task_source,
  43. source_label=task_source_label,
  44. source_ref=task_source_ref,
  45. field_path="input.content.task" if full_task else "impl_task",
  46. relation="direct-input",
  47. completeness="complete" if full_task else "partial" if task_value else "missing",
  48. ),
  49. field(
  50. f"branch:{branch_id}:path-type",
  51. "实现范围",
  52. raw.get("path_type"),
  53. source_kind="database",
  54. source_label="script_build_branch",
  55. source_ref=detail_ref,
  56. field_path="path_type",
  57. relation="standing-constraint",
  58. ),
  59. field(
  60. f"branch:{branch_id}:target",
  61. "实现目标",
  62. raw.get("target"),
  63. source_kind="database",
  64. source_label="script_build_branch",
  65. source_ref=detail_ref,
  66. field_path="target",
  67. relation="standing-constraint",
  68. ),
  69. ]
  70. notices = event_notices(events)
  71. if task_value and not full_task:
  72. notices.append({"level": "warning", "message": "未找到完整实现 Agent 输入,当前使用 Branch 字段作历史回退。"})
  73. return payload(
  74. "implementation-task",
  75. "single-event" if events else "business-record",
  76. inputs=inputs,
  77. units=[event_unit(item, label="实现 Agent") for item in events],
  78. outputs=[],
  79. runtime_summary="任务作为实现 Agent 的完整输入执行。" if events else "该历史记录没有可用的实现 Agent Event Body。",
  80. notices=notices,
  81. completeness="complete" if full_task else "partial" if task_value else "missing",
  82. business_modules=[
  83. # The readable Inspector sections are parsed from the complete
  84. # implementer task. Keep all six source modules on that exact
  85. # Event input so the source Inspector can bind every displayed
  86. # section to a text span. Branch.target remains available to the
  87. # canvas summary below, but must not masquerade as the section's
  88. # original source.
  89. {"id": "scope", "title": "实现范围", "sourceIds": [inputs[0]["id"]]},
  90. {"id": "method", "title": "实施方法", "sourceIds": [inputs[0]["id"]]},
  91. {"id": "goal", "title": "完整目标", "sourceIds": [inputs[0]["id"]]},
  92. {"id": "constraints", "title": "参考依据与约束", "sourceIds": [inputs[0]["id"]]},
  93. {"id": "avoid", "title": "需要避免", "sourceIds": [inputs[0]["id"]]},
  94. {"id": "deliverable", "title": "预期交付", "sourceIds": [inputs[0]["id"]]},
  95. ],
  96. card_fields=[
  97. {"key": "scope", "label": "范围", "sourceIds": [inputs[2]["id"]], "transform": "直接使用结构化 target"},
  98. {"key": "method", "label": "方法", "sourceIds": [inputs[0]["id"]], "transform": "使用任务中的实施方法段"},
  99. {"key": "target", "label": "目标", "sourceIds": [inputs[2]["id"], inputs[0]["id"]], "transform": "优先结构化 target"},
  100. ],
  101. )
  102. def project_data_decision(
  103. detail_ref: str,
  104. bundle: dict[str, Any],
  105. view: dict[str, Any],
  106. load_event: EventLoader,
  107. ) -> dict[str, Any]:
  108. record_id = suffix_int(detail_ref)
  109. row = find_by_id(bundle.get("dataDecisions") or [], record_id)
  110. if row is None:
  111. raise KeyError(detail_ref)
  112. node = find_detail(view, detail_ref)
  113. context = decision_context(node)
  114. events = load_events(load_event, decision_event_refs(context))
  115. sources = row.get("sources") if isinstance(row.get("sources"), list) else []
  116. inputs = [
  117. field(
  118. f"data-decision:{record_id}:source:{index}",
  119. f"参与判断的数据 {index}",
  120. source,
  121. source_kind="database",
  122. source_label="script_build_data_decision.sources",
  123. source_ref=detail_ref,
  124. field_path=f"sources[{index - 1}]",
  125. relation="explicit-basis",
  126. )
  127. for index, source in enumerate(sources, 1)
  128. ]
  129. outputs = [
  130. field(f"data-decision:{record_id}:decision", "明确取舍", row.get("decision"), source_kind="database", source_label="script_build_data_decision", source_ref=detail_ref, field_path="decision", relation="persisted-output"),
  131. field(f"data-decision:{record_id}:reasoning", "取舍理由", row.get("reasoning"), source_kind="database", source_label="script_build_data_decision", source_ref=detail_ref, field_path="reasoning", relation="persisted-output"),
  132. ]
  133. return payload(
  134. "data-decision",
  135. "business-record",
  136. inputs=inputs,
  137. units=[event_unit(item) for item in events],
  138. outputs=outputs,
  139. runtime_summary="这是独立落库的业务取舍记录;不伪造一次不存在的 Agent I/O。",
  140. notices=event_notices(events) + ([] if events else [{"level": "info", "message": "未找到能与该取舍记录一对一安全关联的运行事件。"}]),
  141. completeness="complete" if row.get("decision") and sources else "partial",
  142. business_modules=[
  143. {"id": "sources", "title": "参与判断的数据", "sourceIds": [item["id"] for item in inputs]},
  144. {"id": "tradeoff", "title": "采用 / 组合 / 舍弃", "sourceIds": [outputs[0]["id"]]},
  145. {"id": "decision", "title": "明确取舍", "sourceIds": [outputs[0]["id"]]},
  146. {"id": "reason", "title": "理由", "sourceIds": [outputs[1]["id"]]},
  147. {"id": "source-completeness", "title": "来源完整性", "sourceIds": [item["id"] for item in inputs]},
  148. ],
  149. card_fields=[
  150. {"key": "conclusion", "label": "取舍结论", "sourceIds": [outputs[0]["id"]], "transform": "直接使用业务记录"},
  151. {"key": "sourceCount", "label": "来源数", "sourceIds": [item["id"] for item in inputs], "transform": "明确来源数量"},
  152. {"key": "reason", "label": "核心理由", "sourceIds": [outputs[1]["id"]], "transform": "使用结构化理由"},
  153. ],
  154. )
  155. def project_creative(
  156. detail_ref: str,
  157. bundle: dict[str, Any],
  158. view: dict[str, Any],
  159. load_event: EventLoader,
  160. ) -> dict[str, Any]:
  161. node = find_detail(view, detail_ref)
  162. if node is None:
  163. raise KeyError(detail_ref)
  164. context = decision_context(node)
  165. seed_id = suffix_int(detail_ref)
  166. seed = find_by_id(bundle.get("events") or [], seed_id)
  167. round_index = integer((seed or {}).get("round_index"))
  168. branch_id = integer((seed or {}).get("branch_id"))
  169. refs = decision_event_refs(context)
  170. if seed_id is not None:
  171. refs.append(f"event:{seed_id}")
  172. # Explicit round/branch/scope metadata is safe lineage. It also captures
  173. # script mutation calls omitted by the old think_and_plan-only refs.
  174. implementer_ids = {
  175. integer(item.get("id"))
  176. for item in bundle.get("events") or []
  177. if item.get("event_name") == "script_implementer"
  178. and integer(item.get("round_index")) == round_index
  179. and integer(item.get("branch_id")) == branch_id
  180. }
  181. for item in bundle.get("events") or []:
  182. if integer(item.get("round_index")) != round_index or integer(item.get("branch_id")) != branch_id:
  183. continue
  184. if integer(item.get("scope_event_id")) in implementer_ids or integer(item.get("id")) in implementer_ids:
  185. refs.append(f"event:{item.get('id')}")
  186. events = load_events(load_event, refs)
  187. raw_branch = find_raw_branch(bundle, branch_id or -1) or {}
  188. candidate = raw_branch.get("candidate_snapshot") or {}
  189. snapshot = candidate.get("snapshot") if isinstance(candidate, dict) else None
  190. visible_inputs = []
  191. for index, source in enumerate(context.get("evidence") or context.get("inputs") or [], 1):
  192. if not isinstance(source, dict):
  193. continue
  194. observed = str(source.get("observedRelation") or "")
  195. relation = "direct-input" if observed == "read-by-actor" else "previous-result" if observed == "returned-to-actor" else "available-upstream"
  196. visible_inputs.append(field(f"creative:{seed_id}:input:{index}", str(source.get("label") or f"创作依据 {index}"), source.get("summary") or source.get("value"), source_kind="runtime-event" if source.get("detailRef") else "database", source_label="实现过程中可确认的数据", source_ref=source.get("detailRef"), field_path="output.content" if source.get("detailRef") else "", relation=relation))
  197. explicit_basis = [
  198. field(
  199. f"creative:{seed_id}:explicit-basis:{index}",
  200. str(item.get("label") or f"明确采用的数据取舍 {index}"),
  201. {"decision": item.get("label"), "reasoning": item.get("value")},
  202. source_kind="database",
  203. source_label="script_build_data_decision",
  204. source_ref=item.get("detailRef"),
  205. field_path="",
  206. relation="explicit-basis",
  207. )
  208. for index, item in enumerate(context.get("explicitBasis") or [], 1)
  209. if isinstance(item, dict) and str(item.get("detailRef") or "").startswith("data-decision:")
  210. ]
  211. change_fields = [
  212. field(
  213. f"creative:{seed_id}:change:{index}",
  214. str(item.get("label") or f"脚本改动 {index}"),
  215. {"status": item.get("value"), "note": item.get("note")},
  216. source_kind="runtime-event",
  217. source_label="脚本修改运行事件",
  218. source_ref=item.get("detailRef"),
  219. field_path="",
  220. relation="run-output",
  221. )
  222. for index, item in enumerate(context.get("runtimeActions") or [], 1)
  223. if isinstance(item, dict) and str(item.get("detailRef") or "").startswith("event:")
  224. ]
  225. outputs = [
  226. field(f"creative:{seed_id}:snapshot", "产出的候选表", snapshot, source_kind="artifact", source_label="候选分支快照", source_ref=f"artifact:branch:{branch_id}" if branch_id else None, field_path="candidate_snapshot.snapshot", relation="persisted-output", completeness="complete" if snapshot else "missing"),
  227. field(f"creative:{seed_id}:assessment", "实现者自评", raw_branch.get("self_assessment"), source_kind="database", source_label="script_build_branch", source_ref=f"round:{round_index}:branch:{branch_id}:output", field_path="self_assessment", relation="persisted-output"),
  228. ]
  229. notices = event_notices(events)
  230. if visible_inputs:
  231. notices.append({"level": "info", "message": "可确认是上游信息,但无法确认是否被本次决策采用。"})
  232. if not events:
  233. notices.append({"level": "warning", "message": "候选产出存在,但未保存可安全关联的完整创作过程。"})
  234. card_kind = "creative" if events else "candidate-output-fallback"
  235. return payload(
  236. card_kind,
  237. "event-sequence" if events else "business-record",
  238. inputs=[*visible_inputs, *explicit_basis],
  239. units=[event_unit(item) for item in events],
  240. outputs=[*outputs, *change_fields],
  241. runtime_summary=(f"按明确的轮次、分支和实现 Agent scope 串起 {len(events)} 个创作/修改事件。" if events else "只能展示候选快照和实现者自评。"),
  242. notices=notices,
  243. completeness="complete" if events and snapshot else "partial" if snapshot or raw_branch.get("self_assessment") else "missing",
  244. business_modules=[
  245. {"id": "process", "title": "创作处理", "sourceIds": []},
  246. {"id": "basis", "title": "明确采用的数据取舍", "sourceIds": [item["id"] for item in explicit_basis]},
  247. {"id": "available", "title": "可确认的上游信息", "sourceIds": [item["id"] for item in visible_inputs]},
  248. {"id": "changes", "title": "实际脚本改动", "sourceIds": [item["id"] for item in change_fields]},
  249. {"id": "candidate", "title": "产出的候选表", "sourceIds": [outputs[0]["id"]]},
  250. {"id": "uncertainty", "title": "存疑项 / 完整性", "sourceIds": [outputs[1]["id"]]},
  251. ],
  252. )
  253. def project_multipath_decision(
  254. detail_ref: str,
  255. bundle: dict[str, Any],
  256. view: dict[str, Any],
  257. load_event: EventLoader,
  258. ) -> dict[str, Any]:
  259. record_id = suffix_int(detail_ref)
  260. row = find_by_id(bundle.get("multipathDecisions") or [], record_id)
  261. if row is None:
  262. raise KeyError(detail_ref)
  263. node = find_detail(view, detail_ref)
  264. context = decision_context(node)
  265. events = load_events(load_event, decision_event_refs(context))
  266. branch_ids = row.get("branch_ids") if isinstance(row.get("branch_ids"), list) else []
  267. inputs = [field(f"multipath:{record_id}:branch:{branch_id}", f"候选方案 {branch_id}", find_raw_branch(bundle, integer(branch_id) or -1), source_kind="database", source_label="script_build_branch", source_ref=f"round:{row.get('round_index')}:branch:{branch_id}", field_path="", relation="available-upstream") for branch_id in branch_ids]
  268. review_events = [
  269. item for item in bundle.get("events") or []
  270. if item.get("event_name") == "script_multipath_evaluator"
  271. and integer(item.get("round_index")) == integer(row.get("round_index"))
  272. ]
  273. attached_ids = {integer(item.get("id")) for item in events}
  274. unscoped_reviews = [item for item in review_events if integer(item.get("id")) not in attached_ids]
  275. for item in unscoped_reviews:
  276. inputs.append(field(f"multipath:{record_id}:review:{item.get('id')}", "同轮多方案评审", item.get("agentOutputData") or item.get("output_preview"), source_kind="runtime-event", source_label="同轮评审事件(候选范围未安全关联)", source_ref=f"event:{item.get('id')}", field_path="output", relation="available-upstream", completeness="partial"))
  277. outputs = [
  278. field(f"multipath:{record_id}:decision", "最终决定", row.get("decision"), source_kind="database", source_label="script_build_multipath_decision", source_ref=detail_ref, field_path="decision", relation="persisted-output"),
  279. field(f"multipath:{record_id}:reasoning", "决策理由", row.get("reasoning"), source_kind="database", source_label="script_build_multipath_decision", source_ref=detail_ref, field_path="reasoning", relation="persisted-output"),
  280. ]
  281. notices = event_notices(events)
  282. notices.append({"level": "info", "message": "候选分支可确认是本次取舍对象;除非有明确评审关联,不宣称其内容已被采用。"})
  283. if unscoped_reviews:
  284. notices.append({"level": "warning", "message": "发现同轮评审,但无法安全关联到具体候选。"})
  285. return payload(
  286. "multipath-decision",
  287. "business-record",
  288. inputs=inputs,
  289. units=[event_unit(item) for item in events],
  290. outputs=outputs,
  291. runtime_summary="主 Agent 多路决策以业务表为最终事实,评审 Event 只在有可靠关联时作为明确依据。",
  292. notices=notices,
  293. completeness="complete" if row.get("decision") and branch_ids else "partial",
  294. business_modules=[
  295. {"id": "scope", "title": "取舍对象", "sourceIds": [item["id"] for item in inputs if ":branch:" in item["id"]]},
  296. {"id": "comparison", "title": "评审建议与最终决定", "sourceIds": [item["id"] for item in inputs if ":review:" in item["id"]] + [outputs[0]["id"]]},
  297. {"id": "outcomes", "title": "逐方案处置", "sourceIds": [item["id"] for item in inputs if ":branch:" in item["id"]]},
  298. {"id": "reason", "title": "决策理由", "sourceIds": [outputs[1]["id"]]},
  299. {"id": "strength", "title": "关联强度", "sourceIds": [item["id"] for item in inputs]},
  300. ],
  301. )
  302. def project_branch_output(
  303. detail_ref: str,
  304. round_index: int,
  305. branch_id: int,
  306. bundle: dict[str, Any],
  307. view: dict[str, Any],
  308. ) -> dict[str, Any]:
  309. raw = find_raw_branch(bundle, branch_id)
  310. branch = find_branch(view, round_index, branch_id)
  311. if raw is None or branch is None:
  312. raise KeyError(detail_ref)
  313. output = branch.get("output") or {}
  314. if output.get("type") == "domain-info":
  315. facts = [
  316. item for item in bundle.get("domainInfo") or []
  317. if integer(item.get("branch_id")) == branch_id
  318. ]
  319. outputs = [field(f"branch:{branch_id}:facts", "已核实领域事实", facts, source_kind="database", source_label="script_build_domain_info", source_ref=detail_ref, field_path="domainInfo", relation="persisted-output")]
  320. return payload(
  321. "domain-facts",
  322. "aggregate",
  323. outputs=outputs,
  324. runtime_summary="按 branch_id 聚合该领域信息方案写入的事实。",
  325. calculation="只按显式 branch_id 关联,不使用文本相似度。",
  326. # A successful branch-scoped lookup returning zero rows is a
  327. # complete empty result, not a missing source record.
  328. completeness="complete",
  329. business_modules=[
  330. {"id": "facts", "title": "已核实事实", "sourceIds": [outputs[0]["id"]]},
  331. {"id": "verification", "title": "核实说明", "sourceIds": [outputs[0]["id"]]},
  332. {"id": "sources", "title": "出处", "sourceIds": [outputs[0]["id"]]},
  333. {"id": "usage", "title": "用途与存疑状态", "sourceIds": [outputs[0]["id"]]},
  334. ],
  335. )
  336. candidate = raw.get("candidate_snapshot") or {}
  337. snapshot = candidate.get("snapshot") if isinstance(candidate, dict) else None
  338. outputs = [
  339. field(f"branch:{branch_id}:candidate", "形成了什么", snapshot, source_kind="artifact", source_label="候选分支快照", source_ref=f"artifact:branch:{branch_id}", field_path="candidate_snapshot.snapshot", relation="persisted-output", completeness="complete" if snapshot else "missing"),
  340. field(f"branch:{branch_id}:assessment", "实现者自评", raw.get("self_assessment"), source_kind="database", source_label="script_build_branch", source_ref=detail_ref, field_path="self_assessment", relation="persisted-output"),
  341. ]
  342. return payload(
  343. "candidate-output-fallback",
  344. "business-record",
  345. outputs=outputs,
  346. runtime_summary="候选产出回退详情只展示候选快照和自评,不伪造未保存的创作过程。",
  347. notices=[{"level": "warning", "message": "未保存可安全关联的完整创作过程。"}],
  348. completeness="partial" if snapshot or raw.get("self_assessment") else "missing",
  349. business_modules=[
  350. {"id": "output", "title": "形成了什么", "sourceIds": [outputs[0]["id"]]},
  351. {"id": "assessment", "title": "实现者自评", "sourceIds": [outputs[1]["id"]]},
  352. {"id": "missing", "title": "创作过程缺失说明", "sourceIds": [item["id"] for item in outputs]},
  353. ],
  354. )
  355. def project_domain_info(detail_ref: str, bundle: dict[str, Any]) -> dict[str, Any]:
  356. record_id = suffix_int(detail_ref)
  357. row = find_by_id(bundle.get("domainInfo") or [], record_id)
  358. if row is None:
  359. raise KeyError(detail_ref)
  360. outputs = [
  361. field(f"domain:{record_id}:fact", "已核实事实", row.get("content"), source_kind="database", source_label="script_build_domain_info", source_ref=detail_ref, field_path="content", relation="persisted-output"),
  362. field(f"domain:{record_id}:note", "核实说明", row.get("note"), source_kind="database", source_label="script_build_domain_info", source_ref=detail_ref, field_path="note", relation="persisted-output"),
  363. field(f"domain:{record_id}:source", "信息出处", row.get("source"), source_kind="database", source_label="script_build_domain_info", source_ref=detail_ref, field_path="source", relation="persisted-output"),
  364. ]
  365. return payload(
  366. "domain-fact",
  367. "business-record",
  368. outputs=outputs,
  369. runtime_summary="这是独立落库的已核实领域事实。",
  370. completeness="complete" if row.get("content") and row.get("source") else "partial",
  371. business_modules=[
  372. {"id": "fact", "title": "已核实事实", "sourceIds": [outputs[0]["id"]]},
  373. {"id": "verification", "title": "核实说明", "sourceIds": [outputs[1]["id"]]},
  374. {"id": "source", "title": "出处", "sourceIds": [outputs[2]["id"]]},
  375. {"id": "usage", "title": "用途与存疑状态", "sourceIds": [item["id"] for item in outputs]},
  376. ],
  377. )
  378. def _agent_task(event: dict[str, Any]) -> Any:
  379. content = event_content(event.get("input"))
  380. return content.get("task") if isinstance(content, dict) else content