explore.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. """
  2. Explore 工具 - 并行探索多个方案
  3. 启动多个 Sub-Trace 并行执行不同的探索方向,汇总结果返回。
  4. """
  5. import asyncio
  6. from typing import List, Optional, Dict, Any
  7. from datetime import datetime
  8. from agent.execution.models import Trace, Message
  9. from agent.execution.trace_id import generate_sub_trace_id
  10. from agent.goal.models import Goal
  11. async def explore_tool(
  12. current_trace_id: str,
  13. current_goal_id: str,
  14. branches: List[str],
  15. background: Optional[str] = None,
  16. store=None,
  17. run_agent=None
  18. ) -> str:
  19. """
  20. 并行探索多个方向,汇总结果
  21. Args:
  22. current_trace_id: 当前主 Trace ID
  23. current_goal_id: 当前 Goal ID
  24. branches: 探索方向列表(每个元素是一个探索任务描述)
  25. background: 可选,背景信息(如果提供则用作各 Sub-Trace 的初始 context)
  26. store: TraceStore 实例
  27. run_agent: 运行 Agent 的函数
  28. Returns:
  29. 汇总结果字符串
  30. Example:
  31. >>> result = await explore_tool(
  32. ... current_trace_id="abc123",
  33. ... current_goal_id="2",
  34. ... branches=["JWT 方案", "Session 方案"],
  35. ... store=store,
  36. ... run_agent=run_agent_func
  37. ... )
  38. """
  39. if not store:
  40. raise ValueError("store parameter is required")
  41. if not run_agent:
  42. raise ValueError("run_agent parameter is required")
  43. # 1. 创建 agent_call Goal
  44. goal = Goal(
  45. id=current_goal_id,
  46. type="agent_call",
  47. description=f"并行探索 {len(branches)} 个方案",
  48. reason="探索多个可行方案",
  49. agent_call_mode="explore",
  50. sub_trace_ids=[],
  51. status="in_progress"
  52. )
  53. # 更新 Goal(标记为 agent_call)
  54. await store.update_goal(current_trace_id, current_goal_id,
  55. type="agent_call",
  56. agent_call_mode="explore",
  57. status="in_progress")
  58. # 2. 为每个分支创建 Sub-Trace
  59. sub_traces = []
  60. sub_trace_ids = []
  61. for i, desc in enumerate(branches):
  62. # 生成 Sub-Trace ID
  63. sub_trace_id = generate_sub_trace_id(current_trace_id, "explore")
  64. # 创建 Sub-Trace
  65. sub_trace = Trace(
  66. trace_id=sub_trace_id,
  67. mode="agent",
  68. task=desc,
  69. parent_trace_id=current_trace_id,
  70. parent_goal_id=current_goal_id,
  71. agent_type="explore",
  72. context={
  73. "allowed_tools": ["read", "grep", "glob"], # 探索模式:只读权限
  74. "max_turns": 20,
  75. "background": background
  76. },
  77. status="running",
  78. created_at=datetime.now()
  79. )
  80. # 保存 Sub-Trace
  81. await store.create_trace(sub_trace)
  82. sub_traces.append(sub_trace)
  83. sub_trace_ids.append(sub_trace_id)
  84. # 推送 sub_trace_started 事件
  85. await store.append_event(current_trace_id, "sub_trace_started", {
  86. "trace_id": sub_trace_id,
  87. "parent_trace_id": current_trace_id,
  88. "parent_goal_id": current_goal_id,
  89. "agent_type": "explore",
  90. "task": desc
  91. })
  92. # 更新主 Goal 的 sub_trace_ids
  93. await store.update_goal(current_trace_id, current_goal_id, sub_trace_ids=sub_trace_ids)
  94. # 3. 并行执行所有 Sub-Traces
  95. results = await asyncio.gather(
  96. *[run_agent(st, background=background) for st in sub_traces],
  97. return_exceptions=True
  98. )
  99. # 4. 汇总结果
  100. summary_parts = ["## 探索结果\n"]
  101. for i, (sub_trace, result) in enumerate(zip(sub_traces, results), 1):
  102. branch_name = chr(ord('A') + i - 1) # A, B, C...
  103. if isinstance(result, Exception):
  104. summary_parts.append(f"### 方案 {branch_name}: {sub_trace.task}")
  105. summary_parts.append(f"⚠️ 执行出错: {str(result)}\n")
  106. else:
  107. # 获取 Sub-Trace 的最终状态
  108. updated_trace = await store.get_trace(sub_trace.trace_id)
  109. summary_parts.append(f"### 方案 {branch_name}: {sub_trace.task}")
  110. if updated_trace and updated_trace.status == "completed":
  111. # 从 Sub-Trace 获取总结
  112. summary = result.get("summary", "执行完成") if isinstance(result, dict) else "执行完成"
  113. summary_parts.append(f"{summary}\n")
  114. summary_parts.append(f"📊 统计: {updated_trace.total_messages} 条消息, "
  115. f"{updated_trace.total_tokens} tokens, "
  116. f"成本 ${updated_trace.total_cost:.4f}\n")
  117. else:
  118. summary_parts.append(f"未完成\n")
  119. # 推送 sub_trace_completed 事件
  120. await store.append_event(current_trace_id, "sub_trace_completed", {
  121. "trace_id": sub_trace.trace_id,
  122. "status": "completed" if not isinstance(result, Exception) else "failed",
  123. "summary": result.get("summary", "") if isinstance(result, dict) else ""
  124. })
  125. summary_parts.append("\n---")
  126. summary_parts.append(f"已完成 {len(branches)} 个方案的探索,请根据结果选择继续的方向。")
  127. summary = "\n".join(summary_parts)
  128. # 5. 完成主 Goal
  129. await store.update_goal(current_trace_id, current_goal_id,
  130. status="completed",
  131. summary=f"探索了 {len(branches)} 个方案")
  132. return summary
  133. def create_explore_tool_schema() -> Dict[str, Any]:
  134. """
  135. 创建 explore 工具的 JSON Schema
  136. Returns:
  137. 工具的 JSON Schema
  138. """
  139. return {
  140. "type": "function",
  141. "function": {
  142. "name": "explore",
  143. "description": "并行探索多个方向,汇总结果。用于需要对比多个方案或尝试不同实现方式的场景。",
  144. "parameters": {
  145. "type": "object",
  146. "properties": {
  147. "branches": {
  148. "type": "array",
  149. "items": {"type": "string"},
  150. "description": "探索方向列表,每个元素是一个探索任务的描述",
  151. "minItems": 2,
  152. "maxItems": 5
  153. },
  154. "background": {
  155. "type": "string",
  156. "description": "可选的背景信息,用于初始化各 Sub-Trace 的上下文"
  157. }
  158. },
  159. "required": ["branches"]
  160. }
  161. }
  162. }