goal.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. """
  2. Goal 工具 - 执行计划管理
  3. 提供 LLM 可调用的 goal 工具,用于管理执行计划(GoalTree)。
  4. """
  5. from typing import Optional, TYPE_CHECKING
  6. from agent.tools import tool
  7. if TYPE_CHECKING:
  8. from agent.models.goal import GoalTree
  9. # 全局 GoalTree 引用(由 AgentRunner 注入)
  10. _current_goal_tree = None
  11. def set_goal_tree(tree):
  12. """设置当前 GoalTree(由 AgentRunner 调用)"""
  13. global _current_goal_tree
  14. _current_goal_tree = tree
  15. def get_goal_tree():
  16. """获取当前 GoalTree"""
  17. return _current_goal_tree
  18. @tool(description="管理执行计划,添加/完成/放弃目标,切换焦点")
  19. async def goal(
  20. add: Optional[str] = None,
  21. reason: Optional[str] = None,
  22. after: Optional[str] = None,
  23. under: Optional[str] = None,
  24. done: Optional[str] = None,
  25. abandon: Optional[str] = None,
  26. focus: Optional[str] = None,
  27. context: Optional[dict] = None
  28. ) -> str:
  29. """
  30. 管理执行计划,添加/完成/放弃目标,切换焦点。
  31. Args:
  32. add: 添加目标(逗号分隔多个)
  33. reason: 创建理由(逗号分隔多个,与 add 一一对应)。说明为什么要做这些目标。
  34. after: 在指定目标后面添加(同层级)。使用目标的 ID,如 "2" 或 "2.1"。
  35. under: 为指定目标添加子目标。使用目标的 ID,如 "2" 或 "2.1"。
  36. done: 完成当前目标,值为 summary
  37. abandon: 放弃当前目标,值为原因(会触发 context 压缩)
  38. focus: 切换焦点到指定目标。使用目标的 ID,如 "2" 或 "2.1"。
  39. context: 工具执行上下文(包含 store 和 trace_id)
  40. 位置控制(优先使用 after):
  41. - 不指定 after/under: 添加到当前 focus 下作为子目标(无 focus 时添加到顶层)
  42. - after="X": 在目标 X 后面添加兄弟节点(同层级)
  43. - under="X": 为目标 X 添加子目标
  44. - after 和 under 不能同时指定
  45. 执行顺序:
  46. - done → focus → abandon → add
  47. - 如果同时指定 done 和 focus,会先完成当前目标,再切换焦点到新目标
  48. Examples:
  49. goal(add="分析代码, 实现功能, 测试") # 添加顶层目标
  50. goal(add="设计接口, 实现代码", under="2") # 为目标2添加子目标
  51. goal(add="编写文档", after="3") # 在目标3后面添加同级任务
  52. goal(add="集成测试", after="2.2") # 在目标2.2后面添加同级任务
  53. goal(done="发现用户模型在 models/user.py") # 完成当前目标
  54. goal(done="已完成调研", focus="2") # 完成当前目标,切换到目标2
  55. goal(abandon="方案A需要Redis,环境没有") # 放弃当前目标
  56. Returns:
  57. str: 更新后的计划状态文本
  58. """
  59. tree = get_goal_tree()
  60. if tree is None:
  61. return "错误:GoalTree 未初始化"
  62. # 从 context 获取 store 和 trace_id
  63. store = context.get("store") if context else None
  64. trace_id = context.get("trace_id") if context else None
  65. changes = []
  66. # 1. 处理 done(完成当前目标)
  67. if done is not None:
  68. if not tree.current_id:
  69. return f"错误:没有当前目标可以完成。当前焦点为空,请先使用 focus 参数切换到要完成的目标。\n\n当前计划:\n{tree.to_prompt()}"
  70. # 完成当前目标
  71. # 如果同时指定了 focus,则不清空焦点(后面会切换到新目标)
  72. # 如果只有 done,则清空焦点
  73. clear_focus = (focus is None)
  74. goal_obj = tree.complete(tree.current_id, done, clear_focus=clear_focus)
  75. display_id = tree._generate_display_id(goal_obj)
  76. changes.append(f"已完成: {display_id}. {goal_obj.description}")
  77. # 推送事件
  78. if store and trace_id:
  79. await store.update_goal(trace_id, goal_obj.id, status="completed", summary=done)
  80. # 检查是否有级联完成的父目标(complete方法已经处理,这里只需要记录)
  81. if goal_obj.parent_id:
  82. parent = tree.find(goal_obj.parent_id)
  83. if parent and parent.status == "completed":
  84. parent_display_id = tree._generate_display_id(parent)
  85. changes.append(f"自动完成: {parent_display_id}. {parent.description}(所有子目标已完成)")
  86. # 2. 处理 focus(切换焦点到新目标)
  87. if focus is not None:
  88. goal_obj = tree.find_by_display_id(focus)
  89. if not goal_obj:
  90. return f"错误:找不到目标 {focus}\n\n当前计划:\n{tree.to_prompt()}"
  91. tree.focus(goal_obj.id)
  92. display_id = tree._generate_display_id(goal_obj)
  93. changes.append(f"切换焦点: {display_id}. {goal_obj.description}")
  94. # 3. 处理 abandon(放弃当前目标)
  95. if abandon is not None:
  96. if not tree.current_id:
  97. return f"错误:没有当前目标可以放弃。当前焦点为空。\n\n当前计划:\n{tree.to_prompt()}"
  98. goal_obj = tree.abandon(tree.current_id, abandon)
  99. display_id = tree._generate_display_id(goal_obj)
  100. changes.append(f"已放弃: {display_id}. {goal_obj.description}")
  101. # 推送事件
  102. if store and trace_id:
  103. await store.update_goal(trace_id, goal_obj.id, status="abandoned", summary=abandon)
  104. # 4. 处理 add
  105. if add is not None:
  106. # 检查 after 和 under 互斥
  107. if after is not None and under is not None:
  108. return "错误:after 和 under 参数不能同时指定"
  109. descriptions = [d.strip() for d in add.split(",") if d.strip()]
  110. if descriptions:
  111. # 解析 reasons(与 descriptions 一一对应)
  112. reasons = None
  113. if reason:
  114. reasons = [r.strip() for r in reason.split(",")]
  115. # 如果 reasons 数量少于 descriptions,补空字符串
  116. while len(reasons) < len(descriptions):
  117. reasons.append("")
  118. # 确定添加位置
  119. if after is not None:
  120. # 在指定 goal 后面添加(同层级)
  121. target_goal = tree.find_by_display_id(after)
  122. if not target_goal:
  123. return f"错误:找不到目标 {after}\n\n当前计划:\n{tree.to_prompt()}"
  124. new_goals = tree.add_goals_after(target_goal.id, descriptions, reasons=reasons)
  125. changes.append(f"在 {tree._generate_display_id(target_goal)} 后面添加 {len(new_goals)} 个同级目标")
  126. elif under is not None:
  127. # 为指定 goal 添加子目标
  128. parent_goal = tree.find_by_display_id(under)
  129. if not parent_goal:
  130. return f"错误:找不到目标 {under}\n\n当前计划:\n{tree.to_prompt()}"
  131. new_goals = tree.add_goals(descriptions, reasons=reasons, parent_id=parent_goal.id)
  132. changes.append(f"在 {tree._generate_display_id(parent_goal)} 下添加 {len(new_goals)} 个子目标")
  133. else:
  134. # 默认行为:添加到当前焦点下(如果有焦点),否则添加到顶层
  135. parent_id = tree.current_id
  136. new_goals = tree.add_goals(descriptions, reasons=reasons, parent_id=parent_id)
  137. if parent_id:
  138. parent_display_id = tree._generate_display_id(tree.find(parent_id))
  139. changes.append(f"在 {parent_display_id} 下添加 {len(new_goals)} 个子目标")
  140. else:
  141. changes.append(f"添加 {len(new_goals)} 个顶层目标")
  142. # 推送事件
  143. if store and trace_id:
  144. for goal_obj in new_goals:
  145. await store.add_goal(trace_id, goal_obj)
  146. # 如果没有焦点且添加了目标,自动 focus 到第一个新目标
  147. if not tree.current_id and new_goals:
  148. tree.focus(new_goals[0].id)
  149. display_id = tree._generate_display_id(new_goals[0])
  150. changes.append(f"自动切换焦点: {display_id}")
  151. # 返回当前状态
  152. result = []
  153. if changes:
  154. result.append("## 更新")
  155. result.extend(f"- {c}" for c in changes)
  156. result.append("")
  157. result.append("## Current Plan")
  158. result.append(tree.to_prompt())
  159. return "\n".join(result)