retrieval.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. from __future__ import annotations
  2. from typing import Any
  3. from .common import (
  4. EventLoader,
  5. event_completeness,
  6. event_content,
  7. event_notices,
  8. event_unit,
  9. find_branch,
  10. find_detail,
  11. integer,
  12. load_events,
  13. suffix_int,
  14. )
  15. from .schema import field, payload
  16. def project_event(detail_ref: str, load_event: EventLoader) -> dict[str, Any]:
  17. event_id = suffix_int(detail_ref)
  18. if event_id is None:
  19. raise KeyError(detail_ref)
  20. event = load_event(event_id)
  21. event_name = str(event.get("event_name") or "")
  22. input_value = event_content(event.get("input"))
  23. output_value = event_content(event.get("output"))
  24. inputs = [field(
  25. f"event:{event_id}:input",
  26. "运行输入",
  27. input_value,
  28. source_kind="runtime-event",
  29. source_label="Event Body 输入",
  30. source_ref=detail_ref,
  31. field_path="input.content",
  32. relation="direct-input",
  33. completeness="complete" if input_value not in (None, "", [], {}) else "missing",
  34. )]
  35. outputs = [field(
  36. f"event:{event_id}:output",
  37. "运行输出",
  38. output_value,
  39. source_kind="runtime-event",
  40. source_label="Event Body 输出",
  41. source_ref=detail_ref,
  42. field_path="output.content",
  43. relation="persisted-output",
  44. completeness="complete" if output_value not in (None, "", [], {}) else "missing",
  45. )]
  46. card_kind = _event_card_kind(event)
  47. modules = _event_modules(event)
  48. return payload(
  49. card_kind,
  50. "single-event",
  51. inputs=inputs,
  52. units=[event_unit(event)],
  53. outputs=outputs,
  54. runtime_summary=f"{event_name or '运行事件'} 是一次可独立下钻的运行 I/O。",
  55. notices=event_notices([event]),
  56. completeness=event_completeness(event), # type: ignore[arg-type]
  57. business_modules=modules(inputs[0]["id"], outputs[0]["id"]),
  58. )
  59. def project_retrieval_stage(
  60. detail_ref: str,
  61. round_index: int,
  62. branch_id: int,
  63. view: dict[str, Any],
  64. load_event: EventLoader,
  65. ) -> dict[str, Any]:
  66. branch = find_branch(view, round_index, branch_id)
  67. if branch is None:
  68. raise KeyError(detail_ref)
  69. stage = branch.get("retrievalStage") or {}
  70. refs: list[Any] = [stage.get("implementerEventId")]
  71. for group in stage.get("directToolGroups") or []:
  72. refs.extend(item.get("eventId") for item in group.get("calls") or [])
  73. for run in stage.get("agentRuns") or []:
  74. refs.append(run.get("eventId"))
  75. refs.extend(item.get("eventId") for item in run.get("attempts") or [])
  76. events = load_events(load_event, refs)
  77. inputs = [field(
  78. f"round:{round_index}:branch:{branch_id}:retrieval:purpose",
  79. "取数目的",
  80. ((branch.get("task") or {}).get("card") or {}).get("primary", {}).get("value"),
  81. source_kind="database",
  82. source_label="实现任务",
  83. source_ref=f"round:{round_index}:branch:{branch_id}:task",
  84. field_path="impl_task",
  85. relation="standing-constraint",
  86. )]
  87. outputs = [
  88. field(f"round:{round_index}:branch:{branch_id}:retrieval:mode", "执行方式", stage.get("observedMode") or "unknown", source_kind="calculation", source_label="按执行时间和依赖关系确定", source_ref=detail_ref, field_path="observedMode", relation="persisted-output", completeness="complete"),
  89. field(f"round:{round_index}:branch:{branch_id}:retrieval:waves", "执行波次", stage.get("waves") or [], source_kind="calculation", source_label="按明确 Event 时间聚合", source_ref=detail_ref, field_path="waves", relation="persisted-output", completeness="complete"),
  90. field(f"round:{round_index}:branch:{branch_id}:retrieval:groups", "工具取数", stage.get("directToolGroups") or [], source_kind="calculation", source_label="按明确 Event scope 分组", source_ref=detail_ref, field_path="directToolGroups", relation="persisted-output", completeness="complete"),
  91. field(f"round:{round_index}:branch:{branch_id}:retrieval:agents", "Agent 取数", stage.get("agentRuns") or [], source_kind="calculation", source_label="按 parent_event_id 聚合", source_ref=detail_ref, field_path="agentRuns", relation="persisted-output", completeness="complete"),
  92. field(f"round:{round_index}:branch:{branch_id}:retrieval:result", "阶段结果与失败", {"status": stage.get("status"), "directToolGroups": stage.get("directToolGroups") or [], "agentRuns": stage.get("agentRuns") or []}, source_kind="calculation", source_label="取数阶段状态确定性聚合", source_ref=detail_ref, field_path="status", relation="persisted-output", completeness="complete"),
  93. ]
  94. return payload(
  95. "retrieval-stage",
  96. "aggregate",
  97. inputs=inputs,
  98. units=[event_unit(item) for item in events],
  99. outputs=outputs,
  100. runtime_summary=f"按实现 Agent 的明确 scope 聚合 {len(events)} 个运行事件,观测模式为 {stage.get('observedMode') or 'unknown'}。",
  101. notices=event_notices(events),
  102. completeness="complete" if events and stage.get("status") not in {"missing", "unknown"} else "partial" if events else "missing",
  103. business_modules=[
  104. {"id": "purpose", "title": "取数目的", "sourceIds": [inputs[0]["id"]]},
  105. {"id": "mode", "title": "执行方式", "sourceIds": [outputs[0]["id"]]},
  106. {"id": "waves", "title": "执行波次", "sourceIds": [outputs[1]["id"]]},
  107. {"id": "direct", "title": "工具取数", "sourceIds": [outputs[2]["id"]]},
  108. {"id": "agents", "title": "Agent 取数", "sourceIds": [outputs[3]["id"]]},
  109. {"id": "result", "title": "阶段结果与失败", "sourceIds": [outputs[4]["id"]]},
  110. ],
  111. )
  112. def project_direct_group(
  113. detail_ref: str, view: dict[str, Any], load_event: EventLoader
  114. ) -> dict[str, Any]:
  115. group = find_detail(view, detail_ref)
  116. if group is None:
  117. raise KeyError(detail_ref)
  118. calls = group.get("calls") or []
  119. events = load_events(load_event, [item.get("eventId") for item in calls])
  120. inputs = [field(
  121. f"{detail_ref}:purpose",
  122. "读取目的",
  123. [item.get("businessLabel") for item in calls if item.get("businessLabel")],
  124. source_kind="calculation",
  125. source_label="工具类型业务标签",
  126. source_ref=detail_ref,
  127. field_path="calls.businessLabel",
  128. relation="direct-input",
  129. )]
  130. outputs = [field(
  131. f"{detail_ref}:results",
  132. "实际返回的信息",
  133. [event_content(item.get("output")) for item in events],
  134. source_kind="runtime-event",
  135. source_label="逐次工具 Event Body",
  136. source_ref=detail_ref,
  137. field_path="events[].output.content",
  138. relation="persisted-output",
  139. completeness="complete" if events and all(event_content(item.get("output")) is not None for item in events) else "partial",
  140. )]
  141. return payload(
  142. "retrieval-direct-group",
  143. "event-sequence",
  144. inputs=inputs,
  145. units=[event_unit(item) for item in events],
  146. outputs=outputs,
  147. runtime_summary=f"该工具取数组包含 {len(events)} 次直接读取。",
  148. notices=event_notices(events),
  149. completeness="complete" if events and len(events) == len(calls) else "partial" if events else "missing",
  150. business_modules=[
  151. {"id": "purpose", "title": "读取目的", "sourceIds": [inputs[0]["id"]]},
  152. {"id": "calls", "title": "逐次工具调用", "sourceIds": [outputs[0]["id"]]},
  153. {"id": "results", "title": "实际返回的信息", "sourceIds": [outputs[0]["id"]]},
  154. {"id": "failures", "title": "失败与缺失", "sourceIds": [outputs[0]["id"]]},
  155. ],
  156. )
  157. def project_retrieval_agent(
  158. detail_ref: str, view: dict[str, Any], load_event: EventLoader
  159. ) -> dict[str, Any]:
  160. run = find_detail(view, detail_ref)
  161. if run is None:
  162. raise KeyError(detail_ref)
  163. agent_id = integer(run.get("eventId")) or suffix_int(detail_ref)
  164. refs = [agent_id, *[item.get("eventId") for item in run.get("attempts") or []]]
  165. events = load_events(load_event, refs)
  166. agent = next((item for item in events if integer(item.get("id")) == agent_id), {})
  167. queries = [item for item in events if integer(item.get("id")) != agent_id]
  168. task = _agent_task(agent) or run.get("taskPreview")
  169. screening = _agent_screening(agent) or (run.get("screening") or {}).get("preview")
  170. inputs = [field(
  171. f"{detail_ref}:task",
  172. "完整取数任务",
  173. task,
  174. source_kind="runtime-event",
  175. source_label="取数 Agent Event Body",
  176. source_ref=f"event:{agent_id}",
  177. field_path="input.content.task",
  178. relation="direct-input",
  179. completeness="complete" if _agent_task(agent) else "partial" if task else "missing",
  180. )]
  181. outputs = [
  182. field(f"{detail_ref}:raw-results", "原始命中", [event_content(item.get("output")) for item in queries], source_kind="runtime-event", source_label="查询 Event Body", source_ref=detail_ref, field_path="queryEvents[].output.content", relation="persisted-output", completeness="complete" if queries and all(event_content(item.get("output")) is not None for item in queries) else "partial" if queries else "missing"),
  183. field(f"{detail_ref}:screening", "初筛整理", screening, source_kind="runtime-event", source_label="取数 Agent Event Body", source_ref=f"event:{agent_id}", field_path="output.content", relation="persisted-output", completeness="complete" if _agent_screening(agent) else "partial" if screening else "missing"),
  184. field(f"{detail_ref}:query-summary", "查询统计", run.get("querySummary"), source_kind="calculation", source_label="查询 Event 状态聚合", source_ref=detail_ref, field_path="querySummary", relation="persisted-output"),
  185. ]
  186. return payload(
  187. "retrieval-agent",
  188. "aggregate",
  189. inputs=inputs,
  190. units=[event_unit(item, label=(run.get("businessLabel") if integer(item.get("id")) == agent_id else None)) for item in events],
  191. outputs=outputs,
  192. runtime_summary=f"一个取数 Agent 和 {len(queries)} 次查询组成该运行过程。",
  193. notices=event_notices(events),
  194. completeness="complete" if _agent_task(agent) and _agent_screening(agent) else "partial" if events else "missing",
  195. business_modules=[
  196. {"id": "task", "title": "完整取数任务", "sourceIds": [inputs[0]["id"]]},
  197. {"id": "queries", "title": "查询过程", "sourceIds": [outputs[2]["id"]]},
  198. {"id": "raw", "title": "原始命中", "sourceIds": [outputs[0]["id"]]},
  199. {"id": "screening", "title": "初筛整理", "sourceIds": [outputs[1]["id"]]},
  200. {"id": "failures", "title": "失败与空结果", "sourceIds": [outputs[2]["id"]]},
  201. ],
  202. card_fields=[
  203. {"key": "target", "label": "取数目标", "sourceIds": [inputs[0]["id"]], "transform": "使用完整任务的结构化目标"},
  204. {"key": "queryStats", "label": "查询统计", "sourceIds": [outputs[2]["id"]], "transform": "命中/空/失败数量"},
  205. {"key": "screening", "label": "初筛结论", "sourceIds": [outputs[1]["id"]], "transform": "使用 Agent 完整结果中的 summary"},
  206. ],
  207. )
  208. def _agent_task(event: dict[str, Any]) -> Any:
  209. content = event_content(event.get("input"))
  210. return content.get("task") if isinstance(content, dict) else content
  211. def _agent_screening(event: dict[str, Any]) -> Any:
  212. content = event_content(event.get("output"))
  213. return content.get("summary") if isinstance(content, dict) and content.get("summary") else content
  214. def _event_card_kind(event: dict[str, Any]) -> str:
  215. name = str(event.get("event_name") or "")
  216. if name == "think_and_plan" and str(event.get("agent_role") or "") == "main":
  217. return "planning-analysis"
  218. if name == "script_multipath_evaluator":
  219. return "multipath-review"
  220. if name == "script_evaluator":
  221. return "overall-review"
  222. if name == "script_implementer":
  223. return "implementation-task"
  224. if str(event.get("event_type") or "") == "tool_call":
  225. return "query" if name.startswith(("search_", "query_", "get_")) else "tool-call"
  226. return "runtime-event"
  227. def _event_modules(event: dict[str, Any]):
  228. kind = _event_card_kind(event)
  229. def modules(input_id: str, output_id: str) -> list[dict[str, Any]]:
  230. if kind == "planning-analysis":
  231. return [
  232. {"id": "summary", "title": "规划概要", "sourceIds": [input_id]},
  233. {"id": "thought", "title": "完整思考", "sourceIds": [input_id]},
  234. {"id": "plan", "title": "执行计划", "sourceIds": [input_id]},
  235. {"id": "action", "title": "下一步", "sourceIds": [input_id]},
  236. {"id": "completeness", "title": "记录完整性", "sourceIds": [input_id, output_id]},
  237. ]
  238. if kind == "multipath-review":
  239. return [
  240. {"id": "scope", "title": "评审范围", "sourceIds": [input_id]},
  241. {"id": "criteria", "title": "评审标准", "sourceIds": [input_id]},
  242. {"id": "per-branch", "title": "逐方案评审", "sourceIds": [output_id]},
  243. {"id": "comparison", "title": "方案对比", "sourceIds": [output_id]},
  244. {"id": "recommendation", "title": "评审建议", "sourceIds": [output_id]},
  245. ]
  246. if kind == "overall-review":
  247. return [
  248. {"id": "subject", "title": "评审对象", "sourceIds": [input_id]},
  249. {"id": "criteria", "title": "标准", "sourceIds": [input_id]},
  250. {"id": "achieved", "title": "已达成", "sourceIds": [output_id]},
  251. {"id": "issues", "title": "问题", "sourceIds": [output_id]},
  252. {"id": "conclusion", "title": "总结论", "sourceIds": [output_id]},
  253. {"id": "next", "title": "下一轮建议", "sourceIds": [output_id]},
  254. ]
  255. if kind == "implementation-task":
  256. return [
  257. {"id": "task", "title": "完整实现任务", "sourceIds": [input_id]},
  258. {"id": "result", "title": "实现 Agent 返回", "sourceIds": [output_id]},
  259. ]
  260. if kind in {"query", "tool-call"}:
  261. return [
  262. {"id": "purpose", "title": "目的 / 查询条件", "sourceIds": [input_id]},
  263. {"id": "result", "title": "返回内容 / 结果列表", "sourceIds": [output_id]},
  264. {"id": "status", "title": "状态、耗时与完整性", "sourceIds": [input_id, output_id]},
  265. ]
  266. return [
  267. {"id": "input", "title": "运行输入", "sourceIds": [input_id]},
  268. {"id": "output", "title": "运行输出", "sourceIds": [output_id]},
  269. ]
  270. return modules