tool.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. """
  2. Goal 工具 - 计划管理
  3. 提供 goal 工具供 LLM 管理执行计划。
  4. """
  5. from typing import Optional, List, TYPE_CHECKING
  6. if TYPE_CHECKING:
  7. from agent.goal.models import GoalTree
  8. from agent.execution.protocols import TraceStore
  9. async def goal_tool(
  10. tree: "GoalTree",
  11. store: Optional["TraceStore"] = None,
  12. trace_id: Optional[str] = None,
  13. add: Optional[str] = None,
  14. reason: Optional[str] = None,
  15. done: Optional[str] = None,
  16. abandon: Optional[str] = None,
  17. focus: Optional[str] = None,
  18. ) -> str:
  19. """
  20. 管理执行计划。
  21. Args:
  22. tree: GoalTree 实例
  23. store: TraceStore 实例(用于推送事件)
  24. trace_id: 当前 Trace ID
  25. add: 添加目标(逗号分隔多个)。添加到当前 focus 的 goal 下作为子目标。
  26. reason: 创建理由(逗号分隔多个,与 add 一一对应)
  27. done: 完成当前目标,值为 summary
  28. abandon: 放弃当前目标,值为原因
  29. focus: 切换焦点到指定内部 id
  30. Returns:
  31. 更新后的计划状态文本
  32. """
  33. changes = []
  34. # 1. 处理 abandon(先处理,因为可能需要在 add 新目标前放弃旧的)
  35. if abandon is not None:
  36. if not tree.current_id:
  37. return "错误:没有当前目标可以放弃"
  38. goal = tree.abandon(tree.current_id, abandon)
  39. display_id = tree._generate_display_id(goal)
  40. changes.append(f"已放弃: {display_id}. {goal.description}")
  41. # 推送事件
  42. if store and trace_id:
  43. print(f"[DEBUG] goal_tool: calling store.update_goal for abandon: goal_id={goal.id}")
  44. await store.update_goal(trace_id, goal.id, status="abandoned", summary=abandon)
  45. else:
  46. print(f"[DEBUG] goal_tool: skip event push (store={store}, trace_id={trace_id})")
  47. # 2. 处理 done
  48. if done is not None:
  49. if not tree.current_id:
  50. return "错误:没有当前目标可以完成"
  51. goal = tree.complete(tree.current_id, done)
  52. display_id = tree._generate_display_id(goal)
  53. changes.append(f"已完成: {display_id}. {goal.description}")
  54. # 推送事件
  55. if store and trace_id:
  56. print(f"[DEBUG] goal_tool: calling store.update_goal for done: goal_id={goal.id}")
  57. await store.update_goal(trace_id, goal.id, status="completed", summary=done)
  58. else:
  59. print(f"[DEBUG] goal_tool: skip event push (store={store}, trace_id={trace_id})")
  60. # 检查是否有级联完成的父目标
  61. if goal.parent_id:
  62. parent = tree.find(goal.parent_id)
  63. if parent and parent.status == "completed":
  64. parent_display_id = tree._generate_display_id(parent)
  65. changes.append(f"自动完成: {parent_display_id}. {parent.description}(所有子目标已完成)")
  66. # 3. 处理 focus(在 add 之前,这样 add 可以添加到新焦点下)
  67. if focus is not None:
  68. # focus 参数可以是内部 ID 或显示 ID
  69. # 先尝试作为内部 ID 查找
  70. goal = tree.find(focus)
  71. # 如果找不到,尝试根据显示 ID 查找
  72. if not goal:
  73. # 通过遍历所有 goal 查找匹配的显示 ID
  74. for g in tree.goals:
  75. if tree._generate_display_id(g) == focus:
  76. goal = g
  77. break
  78. if not goal:
  79. return f"错误:找不到目标 {focus}"
  80. tree.focus(goal.id)
  81. display_id = tree._generate_display_id(goal)
  82. changes.append(f"切换焦点: {display_id}. {goal.description}")
  83. # 4. 处理 add
  84. if add is not None:
  85. descriptions = [d.strip() for d in add.split(",") if d.strip()]
  86. if descriptions:
  87. # 解析 reasons(与 descriptions 一一对应)
  88. reasons = None
  89. if reason:
  90. reasons = [r.strip() for r in reason.split(",")]
  91. # 如果 reasons 数量少于 descriptions,补空字符串
  92. while len(reasons) < len(descriptions):
  93. reasons.append("")
  94. # 添加到当前焦点下(如果有焦点),否则添加到顶层
  95. parent_id = tree.current_id
  96. new_goals = tree.add_goals(descriptions, reasons=reasons, parent_id=parent_id)
  97. # 推送事件
  98. if store and trace_id:
  99. print(f"[DEBUG] goal_tool: calling store.add_goal for {len(new_goals)} new goals")
  100. for goal in new_goals:
  101. await store.add_goal(trace_id, goal)
  102. else:
  103. print(f"[DEBUG] goal_tool: skip event push (store={store}, trace_id={trace_id})")
  104. if parent_id:
  105. parent_display_id = tree._generate_display_id(tree.find(parent_id))
  106. changes.append(f"在 {parent_display_id} 下添加 {len(new_goals)} 个子目标")
  107. else:
  108. changes.append(f"添加 {len(new_goals)} 个顶层目标")
  109. # 如果没有焦点且添加了目标,自动 focus 到第一个新目标
  110. if not tree.current_id and new_goals:
  111. tree.focus(new_goals[0].id)
  112. display_id = tree._generate_display_id(new_goals[0])
  113. changes.append(f"自动切换焦点: {display_id}")
  114. # 返回当前状态
  115. result = []
  116. if changes:
  117. result.append("## 更新")
  118. result.extend(f"- {c}" for c in changes)
  119. result.append("")
  120. result.append("## Current Plan")
  121. result.append(tree.to_prompt())
  122. return "\n".join(result)
  123. def create_goal_tool_schema() -> dict:
  124. """创建 goal 工具的 JSON Schema"""
  125. return {
  126. "name": "goal",
  127. "description": """管理执行计划。
  128. - add: 添加目标(逗号分隔多个)。添加到当前 focus 的 goal 下作为子目标。
  129. - reason: 创建理由(逗号分隔多个,与 add 一一对应)。说明为什么要做这些目标。
  130. - done: 完成当前目标,值为 summary
  131. - abandon: 放弃当前目标,值为原因(会触发 context 压缩)
  132. - focus: 切换焦点到指定 id(可以是内部 ID 或显示 ID)
  133. 示例:
  134. - goal(add="分析代码, 实现功能, 测试", reason="了解现有结构, 完成需求, 确保质量") - 添加顶层目标
  135. - goal(focus="2", add="设计接口, 实现代码", reason="明确API规范, 编写核心逻辑") - 切换到目标2,并添加子目标
  136. - goal(done="发现用户模型在 models/user.py") - 完成当前目标
  137. - goal(abandon="方案A需要Redis,环境没有", add="实现方案B", reason="使用现有技术栈") - 放弃当前并添加新目标
  138. 注意:内部 ID 是纯自增数字("1", "2", "3"),显示 ID 是带层级的("1", "2.1", "2.2")。
  139. focus 参数可以使用任意格式的 ID。
  140. reason 应该与 add 的目标数量一致,如果数量不一致,缺少的 reason 将为空。
  141. """,
  142. "parameters": {
  143. "type": "object",
  144. "properties": {
  145. "add": {
  146. "type": "string",
  147. "description": "添加目标(逗号分隔多个)。添加到当前 focus 的 goal 下作为子目标。"
  148. },
  149. "reason": {
  150. "type": "string",
  151. "description": "创建理由(逗号分隔多个,与 add 一一对应)。说明为什么要做这些目标。"
  152. },
  153. "done": {
  154. "type": "string",
  155. "description": "完成当前目标,值为 summary"
  156. },
  157. "abandon": {
  158. "type": "string",
  159. "description": "放弃当前目标,值为原因"
  160. },
  161. "focus": {
  162. "type": "string",
  163. "description": "切换焦点到指定 goal id(可以是内部 ID 或显示 ID)"
  164. }
  165. },
  166. "required": []
  167. }
  168. }