subagent.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. """
  2. Sub-Agent 工具 - 统一 explore/delegate/evaluate
  3. 作为普通工具运行:创建(或继承)子 Trace,执行并返回结构化结果。
  4. """
  5. import asyncio
  6. from datetime import datetime
  7. from typing import Any, Dict, List, Optional
  8. from agent.tools import tool
  9. from agent.trace.models import Trace
  10. from agent.trace.trace_id import generate_sub_trace_id
  11. from agent.trace.goal_models import GoalTree
  12. from agent.trace.websocket import broadcast_sub_trace_started, broadcast_sub_trace_completed
  13. def _make_run_config(**kwargs):
  14. """延迟导入 RunConfig 以避免循环导入"""
  15. from agent.core.runner import RunConfig
  16. return RunConfig(**kwargs)
  17. def _build_explore_prompt(branches: List[str], background: Optional[str]) -> str:
  18. lines = ["# 探索任务", ""]
  19. if background:
  20. lines.extend([background, ""])
  21. lines.append("请探索以下方案:")
  22. for i, branch in enumerate(branches, 1):
  23. lines.append(f"{i}. {branch}")
  24. return "\n".join(lines)
  25. async def _build_evaluate_prompt(
  26. store,
  27. trace_id: str,
  28. target_goal_id: str,
  29. evaluation_input: Dict[str, Any],
  30. requirements: Optional[str],
  31. ) -> str:
  32. goal_tree = await store.get_goal_tree(trace_id)
  33. target_desc = ""
  34. if goal_tree:
  35. target_goal = goal_tree.find(target_goal_id)
  36. if target_goal:
  37. target_desc = target_goal.description
  38. goal_description = evaluation_input.get("goal_description") or target_desc or f"Goal {target_goal_id}"
  39. actual_result = evaluation_input.get("actual_result", "(无执行结果)")
  40. lines = [
  41. "# 评估任务",
  42. "",
  43. "请评估以下任务的执行结果是否满足要求。",
  44. "",
  45. "## 目标描述",
  46. "",
  47. str(goal_description),
  48. "",
  49. "## 执行结果",
  50. "",
  51. str(actual_result),
  52. "",
  53. ]
  54. if requirements:
  55. lines.extend(["## 评估要求", "", requirements, ""])
  56. lines.extend(
  57. [
  58. "## 输出格式",
  59. "",
  60. "## 评估结论",
  61. "[通过/不通过]",
  62. "",
  63. "## 评估理由",
  64. "[详细说明通过或不通过原因]",
  65. "",
  66. "## 修改建议(如果不通过)",
  67. "1. [建议1]",
  68. "2. [建议2]",
  69. ]
  70. )
  71. return "\n".join(lines)
  72. # ===== 辅助函数 =====
  73. async def _update_collaborator(
  74. store, trace_id: str,
  75. name: str, sub_trace_id: str,
  76. status: str, summary: str = "",
  77. ) -> None:
  78. """
  79. 更新 trace.context["collaborators"] 中的协作者信息。
  80. 如果同名协作者已存在则更新,否则追加。
  81. """
  82. trace = await store.get_trace(trace_id)
  83. if not trace:
  84. return
  85. collaborators = trace.context.get("collaborators", [])
  86. # 查找已有记录
  87. existing = None
  88. for c in collaborators:
  89. if c.get("trace_id") == sub_trace_id:
  90. existing = c
  91. break
  92. if existing:
  93. existing["status"] = status
  94. if summary:
  95. existing["summary"] = summary
  96. else:
  97. collaborators.append({
  98. "name": name,
  99. "type": "agent",
  100. "trace_id": sub_trace_id,
  101. "status": status,
  102. "summary": summary,
  103. })
  104. trace.context["collaborators"] = collaborators
  105. await store.update_trace(trace_id, context=trace.context)
  106. async def _update_goal_start(
  107. store, trace_id: str, goal_id: str, mode: str, sub_trace_ids: List[str]
  108. ) -> None:
  109. """标记 Goal 开始执行"""
  110. if not goal_id:
  111. return
  112. await store.update_goal(
  113. trace_id, goal_id,
  114. type="agent_call",
  115. agent_call_mode=mode,
  116. status="in_progress",
  117. sub_trace_ids=sub_trace_ids
  118. )
  119. async def _update_goal_complete(
  120. store, trace_id: str, goal_id: str,
  121. status: str, summary: str, sub_trace_ids: List[str]
  122. ) -> None:
  123. """标记 Goal 完成"""
  124. if not goal_id:
  125. return
  126. await store.update_goal(
  127. trace_id, goal_id,
  128. status=status,
  129. summary=summary,
  130. sub_trace_ids=sub_trace_ids
  131. )
  132. def _format_explore_results(
  133. branches: List[str], results: List[Dict[str, Any]]
  134. ) -> str:
  135. """格式化 explore 模式的汇总结果(Markdown)"""
  136. lines = ["## 探索结果\n"]
  137. successful = 0
  138. failed = 0
  139. total_tokens = 0
  140. total_cost = 0.0
  141. for i, (branch, result) in enumerate(zip(branches, results)):
  142. branch_name = chr(ord('A') + i) # A, B, C...
  143. lines.append(f"### 方案 {branch_name}: {branch}")
  144. if isinstance(result, dict):
  145. status = result.get("status", "unknown")
  146. if status == "completed":
  147. lines.append("**状态**: ✓ 完成")
  148. successful += 1
  149. else:
  150. lines.append("**状态**: ✗ 失败")
  151. failed += 1
  152. summary = result.get("summary", "")
  153. if summary:
  154. lines.append(f"**摘要**: {summary[:200]}...") # 限制长度
  155. stats = result.get("stats", {})
  156. if stats:
  157. messages = stats.get("total_messages", 0)
  158. tokens = stats.get("total_tokens", 0)
  159. cost = stats.get("total_cost", 0.0)
  160. lines.append(f"**统计**: {messages} messages, {tokens} tokens, ${cost:.4f}")
  161. total_tokens += tokens
  162. total_cost += cost
  163. else:
  164. lines.append("**状态**: ✗ 异常")
  165. failed += 1
  166. lines.append("")
  167. lines.append("---\n")
  168. lines.append("## 总结")
  169. lines.append(f"- 总分支数: {len(branches)}")
  170. lines.append(f"- 成功: {successful}")
  171. lines.append(f"- 失败: {failed}")
  172. lines.append(f"- 总 tokens: {total_tokens}")
  173. lines.append(f"- 总成本: ${total_cost:.4f}")
  174. return "\n".join(lines)
  175. def _format_delegate_result(result: Dict[str, Any]) -> str:
  176. """格式化 delegate 模式的详细结果"""
  177. lines = ["## 委托任务完成\n"]
  178. summary = result.get("summary", "")
  179. if summary:
  180. lines.append(summary)
  181. lines.append("")
  182. lines.append("---\n")
  183. lines.append("**执行统计**:")
  184. stats = result.get("stats", {})
  185. if stats:
  186. lines.append(f"- 消息数: {stats.get('total_messages', 0)}")
  187. lines.append(f"- Tokens: {stats.get('total_tokens', 0)}")
  188. lines.append(f"- 成本: ${stats.get('total_cost', 0.0):.4f}")
  189. return "\n".join(lines)
  190. def _format_evaluate_result(result: Dict[str, Any]) -> str:
  191. """格式化 evaluate 模式的评估结果"""
  192. summary = result.get("summary", "")
  193. return summary # evaluate 的 summary 已经是格式化的评估结果
  194. def _get_allowed_tools_for_mode(mode: str, context: dict) -> Optional[List[str]]:
  195. """获取模式对应的允许工具列表"""
  196. if mode == "explore":
  197. return ["read_file", "grep_content", "glob_files", "goal"]
  198. elif mode in ["delegate", "evaluate"]:
  199. # 获取所有工具,排除 subagent
  200. runner = context.get("runner")
  201. if runner and hasattr(runner, "tools") and hasattr(runner.tools, "registry"):
  202. all_tools = list(runner.tools.registry.keys())
  203. return [t for t in all_tools if t != "subagent"]
  204. return None # 使用默认(所有工具)
  205. def _aggregate_stats(results: List[Dict[str, Any]]) -> Dict[str, Any]:
  206. """聚合多个结果的统计信息"""
  207. total_messages = 0
  208. total_tokens = 0
  209. total_cost = 0.0
  210. for result in results:
  211. if isinstance(result, dict) and "stats" in result:
  212. stats = result["stats"]
  213. total_messages += stats.get("total_messages", 0)
  214. total_tokens += stats.get("total_tokens", 0)
  215. total_cost += stats.get("total_cost", 0.0)
  216. return {
  217. "total_messages": total_messages,
  218. "total_tokens": total_tokens,
  219. "total_cost": total_cost
  220. }
  221. # ===== 模式处理函数 =====
  222. async def _handle_explore_mode(
  223. branches: List[str],
  224. background: Optional[str],
  225. continue_from: Optional[str],
  226. store, current_trace_id: str, current_goal_id: str, runner
  227. ) -> Dict[str, Any]:
  228. """Explore 模式:并行探索多个方案"""
  229. # 1. 检查 continue_from(不支持)
  230. if continue_from:
  231. return {
  232. "status": "failed",
  233. "error": "explore mode does not support continue_from parameter"
  234. }
  235. # 2. 获取父 Trace 信息(用于继承 uid、model)
  236. parent_trace = await store.get_trace(current_trace_id)
  237. # 3. 创建所有 Sub-Traces
  238. sub_trace_ids = []
  239. tasks = []
  240. for i, branch in enumerate(branches):
  241. # 生成唯一的 sub_trace_id
  242. sub_trace_id = generate_sub_trace_id(current_trace_id, f"explore-{i+1:03d}")
  243. sub_trace_ids.append({
  244. "trace_id": sub_trace_id,
  245. "mission": branch
  246. })
  247. # 创建 Sub-Trace
  248. sub_trace = Trace(
  249. trace_id=sub_trace_id,
  250. mode="agent",
  251. task=branch,
  252. parent_trace_id=current_trace_id,
  253. parent_goal_id=current_goal_id,
  254. agent_type="explore",
  255. uid=parent_trace.uid if parent_trace else None,
  256. model=parent_trace.model if parent_trace else None,
  257. status="running",
  258. context={"subagent_mode": "explore", "created_by_tool": "subagent"},
  259. created_at=datetime.now(),
  260. )
  261. await store.create_trace(sub_trace)
  262. await store.update_goal_tree(sub_trace_id, GoalTree(mission=branch))
  263. # 广播 sub_trace_started
  264. await broadcast_sub_trace_started(
  265. current_trace_id, sub_trace_id, current_goal_id or "",
  266. "explore", branch
  267. )
  268. # 注册为活跃协作者
  269. await _update_collaborator(
  270. store, current_trace_id,
  271. name=f"explore-{i+1}", sub_trace_id=sub_trace_id,
  272. status="running", summary=branch[:80],
  273. )
  274. # 创建执行任务
  275. task_coro = runner.run_result(
  276. messages=[{"role": "user", "content": branch}],
  277. config=_make_run_config(
  278. trace_id=sub_trace_id,
  279. agent_type="explore",
  280. model=parent_trace.model if parent_trace else "gpt-4o",
  281. uid=parent_trace.uid if parent_trace else None,
  282. tools=["read_file", "grep_content", "glob_files", "goal"],
  283. name=branch,
  284. ),
  285. )
  286. tasks.append(task_coro)
  287. # 4. 更新主 Goal 为 in_progress
  288. await _update_goal_start(store, current_trace_id, current_goal_id, "explore", sub_trace_ids)
  289. # 5. 并行执行所有分支
  290. results = await asyncio.gather(*tasks, return_exceptions=True)
  291. # 6. 处理结果并广播完成事件
  292. processed_results = []
  293. for i, result in enumerate(results):
  294. sub_tid = sub_trace_ids[i]["trace_id"]
  295. if isinstance(result, Exception):
  296. # 异常处理
  297. error_result = {
  298. "status": "failed",
  299. "summary": f"执行出错: {str(result)}",
  300. "stats": {"total_messages": 0, "total_tokens": 0, "total_cost": 0.0}
  301. }
  302. processed_results.append(error_result)
  303. await broadcast_sub_trace_completed(
  304. current_trace_id, sub_tid, "failed", str(result), {}
  305. )
  306. await _update_collaborator(
  307. store, current_trace_id,
  308. name=f"explore-{i+1}", sub_trace_id=sub_tid,
  309. status="failed", summary=str(result)[:80],
  310. )
  311. else:
  312. processed_results.append(result)
  313. await broadcast_sub_trace_completed(
  314. current_trace_id, sub_tid,
  315. result.get("status", "completed"),
  316. result.get("summary", ""),
  317. result.get("stats", {})
  318. )
  319. await _update_collaborator(
  320. store, current_trace_id,
  321. name=f"explore-{i+1}", sub_trace_id=sub_tid,
  322. status=result.get("status", "completed"),
  323. summary=result.get("summary", "")[:80],
  324. )
  325. # 7. 格式化汇总结果
  326. aggregated_summary = _format_explore_results(branches, processed_results)
  327. # 8. 更新主 Goal 为 completed
  328. overall_status = "completed" if any(
  329. r.get("status") == "completed" for r in processed_results if isinstance(r, dict)
  330. ) else "failed"
  331. await _update_goal_complete(
  332. store, current_trace_id, current_goal_id,
  333. overall_status, aggregated_summary, sub_trace_ids
  334. )
  335. # 9. 返回结果
  336. return {
  337. "mode": "explore",
  338. "status": overall_status,
  339. "summary": aggregated_summary,
  340. "sub_trace_ids": sub_trace_ids,
  341. "branches": branches,
  342. "stats": _aggregate_stats(processed_results)
  343. }
  344. async def _handle_delegate_mode(
  345. task: str,
  346. continue_from: Optional[str],
  347. store, current_trace_id: str, current_goal_id: str, runner, context: dict
  348. ) -> Dict[str, Any]:
  349. """Delegate 模式:委托单个任务"""
  350. # 1. 获取父 Trace 信息
  351. parent_trace = await store.get_trace(current_trace_id)
  352. # 2. 处理 continue_from 或创建新 Sub-Trace
  353. if continue_from:
  354. existing_trace = await store.get_trace(continue_from)
  355. if not existing_trace:
  356. return {"status": "failed", "error": f"Continue-from trace not found: {continue_from}"}
  357. sub_trace_id = continue_from
  358. # 获取 mission
  359. goal_tree = await store.get_goal_tree(continue_from)
  360. mission = goal_tree.mission if goal_tree else task
  361. sub_trace_ids = [{"trace_id": sub_trace_id, "mission": mission}]
  362. else:
  363. sub_trace_id = generate_sub_trace_id(current_trace_id, "delegate")
  364. sub_trace = Trace(
  365. trace_id=sub_trace_id,
  366. mode="agent",
  367. task=task,
  368. parent_trace_id=current_trace_id,
  369. parent_goal_id=current_goal_id,
  370. agent_type="delegate",
  371. uid=parent_trace.uid if parent_trace else None,
  372. model=parent_trace.model if parent_trace else None,
  373. status="running",
  374. context={"subagent_mode": "delegate", "created_by_tool": "subagent"},
  375. created_at=datetime.now(),
  376. )
  377. await store.create_trace(sub_trace)
  378. await store.update_goal_tree(sub_trace_id, GoalTree(mission=task))
  379. sub_trace_ids = [{"trace_id": sub_trace_id, "mission": task}]
  380. # 广播 sub_trace_started
  381. await broadcast_sub_trace_started(
  382. current_trace_id, sub_trace_id, current_goal_id or "",
  383. "delegate", task
  384. )
  385. # 注册为活跃协作者
  386. delegate_name = task[:30] if not continue_from else f"delegate-{sub_trace_id[:8]}"
  387. await _update_collaborator(
  388. store, current_trace_id,
  389. name=delegate_name, sub_trace_id=sub_trace_id,
  390. status="running", summary=task[:80],
  391. )
  392. # 3. 更新主 Goal 为 in_progress
  393. await _update_goal_start(store, current_trace_id, current_goal_id, "delegate", sub_trace_ids)
  394. # 4. 执行任务
  395. try:
  396. allowed_tools = _get_allowed_tools_for_mode("delegate", context)
  397. result = await runner.run_result(
  398. messages=[{"role": "user", "content": task}],
  399. config=_make_run_config(
  400. trace_id=sub_trace_id,
  401. agent_type="delegate",
  402. model=parent_trace.model if parent_trace else "gpt-4o",
  403. uid=parent_trace.uid if parent_trace else None,
  404. tools=allowed_tools,
  405. name=task[:50],
  406. ),
  407. )
  408. # 4. 广播 sub_trace_completed
  409. await broadcast_sub_trace_completed(
  410. current_trace_id, sub_trace_id,
  411. result.get("status", "completed"),
  412. result.get("summary", ""),
  413. result.get("stats", {})
  414. )
  415. # 更新协作者状态
  416. await _update_collaborator(
  417. store, current_trace_id,
  418. name=delegate_name, sub_trace_id=sub_trace_id,
  419. status=result.get("status", "completed"),
  420. summary=result.get("summary", "")[:80],
  421. )
  422. # 5. 格式化结果
  423. formatted_summary = _format_delegate_result(result)
  424. # 6. 更新主 Goal 为 completed
  425. await _update_goal_complete(
  426. store, current_trace_id, current_goal_id,
  427. result.get("status", "completed"), formatted_summary, sub_trace_ids
  428. )
  429. # 7. 返回结果
  430. return {
  431. "mode": "delegate",
  432. "sub_trace_id": sub_trace_id,
  433. "continue_from": bool(continue_from),
  434. **result,
  435. "summary": formatted_summary
  436. }
  437. except Exception as e:
  438. # 错误处理
  439. error_msg = str(e)
  440. await broadcast_sub_trace_completed(
  441. current_trace_id, sub_trace_id,
  442. "failed", error_msg, {}
  443. )
  444. await _update_collaborator(
  445. store, current_trace_id,
  446. name=delegate_name, sub_trace_id=sub_trace_id,
  447. status="failed", summary=error_msg[:80],
  448. )
  449. await _update_goal_complete(
  450. store, current_trace_id, current_goal_id,
  451. "failed", f"委托任务失败: {error_msg}", sub_trace_ids
  452. )
  453. return {
  454. "mode": "delegate",
  455. "status": "failed",
  456. "error": error_msg,
  457. "sub_trace_id": sub_trace_id
  458. }
  459. async def _handle_evaluate_mode(
  460. target_goal_id: str,
  461. evaluation_input: Dict[str, Any],
  462. requirements: Optional[str],
  463. continue_from: Optional[str],
  464. store, current_trace_id: str, current_goal_id: str, runner, context: dict
  465. ) -> Dict[str, Any]:
  466. """Evaluate 模式:评估任务结果"""
  467. # 1. 构建评估 prompt
  468. task_prompt = await _build_evaluate_prompt(
  469. store, current_trace_id, target_goal_id,
  470. evaluation_input, requirements
  471. )
  472. # 2. 获取父 Trace 信息
  473. parent_trace = await store.get_trace(current_trace_id)
  474. # 3. 处理 continue_from 或创建新 Sub-Trace
  475. if continue_from:
  476. existing_trace = await store.get_trace(continue_from)
  477. if not existing_trace:
  478. return {"status": "failed", "error": f"Continue-from trace not found: {continue_from}"}
  479. sub_trace_id = continue_from
  480. # 获取 mission
  481. goal_tree = await store.get_goal_tree(continue_from)
  482. mission = goal_tree.mission if goal_tree else task_prompt
  483. sub_trace_ids = [{"trace_id": sub_trace_id, "mission": mission}]
  484. else:
  485. sub_trace_id = generate_sub_trace_id(current_trace_id, "evaluate")
  486. sub_trace = Trace(
  487. trace_id=sub_trace_id,
  488. mode="agent",
  489. task=task_prompt,
  490. parent_trace_id=current_trace_id,
  491. parent_goal_id=current_goal_id,
  492. agent_type="evaluate",
  493. uid=parent_trace.uid if parent_trace else None,
  494. model=parent_trace.model if parent_trace else None,
  495. status="running",
  496. context={"subagent_mode": "evaluate", "created_by_tool": "subagent"},
  497. created_at=datetime.now(),
  498. )
  499. await store.create_trace(sub_trace)
  500. await store.update_goal_tree(sub_trace_id, GoalTree(mission=task_prompt))
  501. sub_trace_ids = [{"trace_id": sub_trace_id, "mission": task_prompt}]
  502. # 广播 sub_trace_started
  503. await broadcast_sub_trace_started(
  504. current_trace_id, sub_trace_id, current_goal_id or "",
  505. "evaluate", task_prompt
  506. )
  507. # 4. 更新主 Goal 为 in_progress
  508. await _update_goal_start(store, current_trace_id, current_goal_id, "evaluate", sub_trace_ids)
  509. # 注册为活跃协作者
  510. eval_name = f"评估: {target_goal_id[:20]}"
  511. await _update_collaborator(
  512. store, current_trace_id,
  513. name=eval_name, sub_trace_id=sub_trace_id,
  514. status="running", summary=f"评估 Goal {target_goal_id}",
  515. )
  516. # 5. 执行评估
  517. try:
  518. allowed_tools = _get_allowed_tools_for_mode("evaluate", context)
  519. result = await runner.run_result(
  520. messages=[{"role": "user", "content": task_prompt}],
  521. config=_make_run_config(
  522. trace_id=sub_trace_id,
  523. agent_type="evaluate",
  524. model=parent_trace.model if parent_trace else "gpt-4o",
  525. uid=parent_trace.uid if parent_trace else None,
  526. tools=allowed_tools,
  527. name=f"评估: {target_goal_id}",
  528. ),
  529. )
  530. # 5. 广播 sub_trace_completed
  531. await broadcast_sub_trace_completed(
  532. current_trace_id, sub_trace_id,
  533. result.get("status", "completed"),
  534. result.get("summary", ""),
  535. result.get("stats", {})
  536. )
  537. # 更新协作者状态
  538. await _update_collaborator(
  539. store, current_trace_id,
  540. name=eval_name, sub_trace_id=sub_trace_id,
  541. status=result.get("status", "completed"),
  542. summary=result.get("summary", "")[:80],
  543. )
  544. # 6. 格式化结果
  545. formatted_summary = _format_evaluate_result(result)
  546. # 7. 更新主 Goal 为 completed
  547. await _update_goal_complete(
  548. store, current_trace_id, current_goal_id,
  549. result.get("status", "completed"), formatted_summary, sub_trace_ids
  550. )
  551. # 8. 返回结果
  552. return {
  553. "mode": "evaluate",
  554. "sub_trace_id": sub_trace_id,
  555. "continue_from": bool(continue_from),
  556. **result,
  557. "summary": formatted_summary
  558. }
  559. except Exception as e:
  560. # 错误处理
  561. error_msg = str(e)
  562. await broadcast_sub_trace_completed(
  563. current_trace_id, sub_trace_id,
  564. "failed", error_msg, {}
  565. )
  566. await _update_collaborator(
  567. store, current_trace_id,
  568. name=eval_name, sub_trace_id=sub_trace_id,
  569. status="failed", summary=error_msg[:80],
  570. )
  571. await _update_goal_complete(
  572. store, current_trace_id, current_goal_id,
  573. "failed", f"评估任务失败: {error_msg}", sub_trace_ids
  574. )
  575. return {
  576. "mode": "evaluate",
  577. "status": "failed",
  578. "error": error_msg,
  579. "sub_trace_id": sub_trace_id
  580. }
  581. @tool(description="创建 Sub-Agent 执行任务(evaluate/delegate/explore)")
  582. async def subagent(
  583. mode: str,
  584. task: Optional[str] = None,
  585. target_goal_id: Optional[str] = None,
  586. evaluation_input: Optional[Dict[str, Any]] = None,
  587. requirements: Optional[str] = None,
  588. branches: Optional[List[str]] = None,
  589. background: Optional[str] = None,
  590. continue_from: Optional[str] = None,
  591. context: Optional[dict] = None,
  592. ) -> Dict[str, Any]:
  593. # 1. 验证 context
  594. if not context:
  595. return {"status": "failed", "error": "context is required"}
  596. store = context.get("store")
  597. current_trace_id = context.get("trace_id")
  598. current_goal_id = context.get("goal_id")
  599. runner = context.get("runner")
  600. missing = []
  601. if not store:
  602. missing.append("store")
  603. if not current_trace_id:
  604. missing.append("trace_id")
  605. if not runner:
  606. missing.append("runner")
  607. if missing:
  608. return {"status": "failed", "error": f"Missing required context: {', '.join(missing)}"}
  609. # 2. 验证 mode
  610. if mode not in {"evaluate", "delegate", "explore"}:
  611. return {"status": "failed", "error": "Invalid mode: must be evaluate/delegate/explore"}
  612. # 3. 验证模式特定参数
  613. if mode == "delegate" and not task:
  614. return {"status": "failed", "error": "delegate mode requires task"}
  615. if mode == "explore" and not branches:
  616. return {"status": "failed", "error": "explore mode requires branches"}
  617. if mode == "evaluate" and (not target_goal_id or evaluation_input is None):
  618. return {"status": "failed", "error": "evaluate mode requires target_goal_id and evaluation_input"}
  619. # 4. 路由到模式处理函数
  620. if mode == "explore":
  621. return await _handle_explore_mode(
  622. branches, background, continue_from,
  623. store, current_trace_id, current_goal_id, runner
  624. )
  625. elif mode == "delegate":
  626. return await _handle_delegate_mode(
  627. task, continue_from,
  628. store, current_trace_id, current_goal_id, runner, context
  629. )
  630. else: # evaluate
  631. return await _handle_evaluate_mode(
  632. target_goal_id, evaluation_input, requirements, continue_from,
  633. store, current_trace_id, current_goal_id, runner, context
  634. )