Преглед изворни кода

fix: websocket events push & generating reason in goal tool

Talegorithm пре 1 месец
родитељ
комит
8b1cf1d0fb
4 измењених фајлова са 85 додато и 42 уклоњено
  1. 10 21
      agent/core/runner.py
  2. 5 2
      agent/execution/fs_store.py
  3. 48 5
      agent/goal/tool.py
  4. 22 14
      agent/tools/builtin/goal.py

+ 10 - 21
agent/core/runner.py

@@ -368,27 +368,16 @@ class AgentRunner:
                         elif tool_args is None:
                             tool_args = {}  # None 转换为空字典
 
-                        # 拦截 goal 工具调用(需要保存更新后的 GoalTree)
-                        if tool_name == "goal":
-                            # 执行 goal 工具
-                            tool_result = await self.tools.execute(
-                                tool_name,
-                                tool_args,
-                                uid=uid or ""
-                            )
-
-                            # 保存更新后的 GoalTree
-                            if self.trace_store and goal_tree:
-                                await self.trace_store.update_goal_tree(trace_id, goal_tree)
-
-                                # TODO: 广播 goal 更新事件
-                        else:
-                            # 执行普通工具
-                            tool_result = await self.tools.execute(
-                                tool_name,
-                                tool_args,
-                                uid=uid or ""
-                            )
+                        # 执行工具(统一处理,传递 context)
+                        tool_result = await self.tools.execute(
+                            tool_name,
+                            tool_args,
+                            uid=uid or "",
+                            context={
+                                "store": self.trace_store,
+                                "trace_id": trace_id
+                            }
+                        )
 
                         # 记录 tool Message
                         tool_msg = Message.create(

+ 5 - 2
agent/execution/fs_store.py

@@ -192,10 +192,12 @@ class FileSystemTraceStore:
         await self.update_goal_tree(trace_id, tree)
 
         # 推送 goal_added 事件
-        await self.append_event(trace_id, "goal_added", {
+        event_data = {
             "goal": goal.to_dict(),
             "parent_id": goal.parent_id
-        })
+        }
+        await self.append_event(trace_id, "goal_added", event_data)
+        print(f"[DEBUG] Pushed goal_added event: goal_id={goal.id}, parent_id={goal.parent_id}")
 
     async def update_goal(self, trace_id: str, goal_id: str, **updates) -> None:
         """更新 Goal 字段"""
@@ -231,6 +233,7 @@ class FileSystemTraceStore:
             "updates": updates,
             "affected_goals": affected_goals
         })
+        print(f"[DEBUG] Pushed goal_updated event: goal_id={goal_id}, updates={updates}, affected={len(affected_goals)}")
 
     async def _check_cascade_completion(
         self,

+ 48 - 5
agent/goal/tool.py

@@ -8,11 +8,15 @@ from typing import Optional, List, TYPE_CHECKING
 
 if TYPE_CHECKING:
     from agent.goal.models import GoalTree
+    from agent.execution.protocols import TraceStore
 
 
-def goal_tool(
+async def goal_tool(
     tree: "GoalTree",
+    store: Optional["TraceStore"] = None,
+    trace_id: Optional[str] = None,
     add: Optional[str] = None,
+    reason: Optional[str] = None,
     done: Optional[str] = None,
     abandon: Optional[str] = None,
     focus: Optional[str] = None,
@@ -22,7 +26,10 @@ def goal_tool(
 
     Args:
         tree: GoalTree 实例
+        store: TraceStore 实例(用于推送事件)
+        trace_id: 当前 Trace ID
         add: 添加目标(逗号分隔多个)。添加到当前 focus 的 goal 下作为子目标。
+        reason: 创建理由(逗号分隔多个,与 add 一一对应)
         done: 完成当前目标,值为 summary
         abandon: 放弃当前目标,值为原因
         focus: 切换焦点到指定内部 id
@@ -40,6 +47,13 @@ def goal_tool(
         display_id = tree._generate_display_id(goal)
         changes.append(f"已放弃: {display_id}. {goal.description}")
 
+        # 推送事件
+        if store and trace_id:
+            print(f"[DEBUG] goal_tool: calling store.update_goal for abandon: goal_id={goal.id}")
+            await store.update_goal(trace_id, goal.id, status="abandoned", summary=abandon)
+        else:
+            print(f"[DEBUG] goal_tool: skip event push (store={store}, trace_id={trace_id})")
+
     # 2. 处理 done
     if done is not None:
         if not tree.current_id:
@@ -48,6 +62,13 @@ def goal_tool(
         display_id = tree._generate_display_id(goal)
         changes.append(f"已完成: {display_id}. {goal.description}")
 
+        # 推送事件
+        if store and trace_id:
+            print(f"[DEBUG] goal_tool: calling store.update_goal for done: goal_id={goal.id}")
+            await store.update_goal(trace_id, goal.id, status="completed", summary=done)
+        else:
+            print(f"[DEBUG] goal_tool: skip event push (store={store}, trace_id={trace_id})")
+
         # 检查是否有级联完成的父目标
         if goal.parent_id:
             parent = tree.find(goal.parent_id)
@@ -80,9 +101,25 @@ def goal_tool(
     if add is not None:
         descriptions = [d.strip() for d in add.split(",") if d.strip()]
         if descriptions:
+            # 解析 reasons(与 descriptions 一一对应)
+            reasons = None
+            if reason:
+                reasons = [r.strip() for r in reason.split(",")]
+                # 如果 reasons 数量少于 descriptions,补空字符串
+                while len(reasons) < len(descriptions):
+                    reasons.append("")
+
             # 添加到当前焦点下(如果有焦点),否则添加到顶层
             parent_id = tree.current_id
-            new_goals = tree.add_goals(descriptions, parent_id=parent_id)
+            new_goals = tree.add_goals(descriptions, reasons=reasons, parent_id=parent_id)
+
+            # 推送事件
+            if store and trace_id:
+                print(f"[DEBUG] goal_tool: calling store.add_goal for {len(new_goals)} new goals")
+                for goal in new_goals:
+                    await store.add_goal(trace_id, goal)
+            else:
+                print(f"[DEBUG] goal_tool: skip event push (store={store}, trace_id={trace_id})")
 
             if parent_id:
                 parent_display_id = tree._generate_display_id(tree.find(parent_id))
@@ -116,18 +153,20 @@ def create_goal_tool_schema() -> dict:
         "description": """管理执行计划。
 
 - add: 添加目标(逗号分隔多个)。添加到当前 focus 的 goal 下作为子目标。
+- reason: 创建理由(逗号分隔多个,与 add 一一对应)。说明为什么要做这些目标。
 - done: 完成当前目标,值为 summary
 - abandon: 放弃当前目标,值为原因(会触发 context 压缩)
 - focus: 切换焦点到指定 id(可以是内部 ID 或显示 ID)
 
 示例:
-- goal(add="分析代码, 实现功能, 测试") - 添加顶层目标
-- goal(focus="2", add="设计接口, 实现代码") - 切换到目标2,并添加子目标
+- goal(add="分析代码, 实现功能, 测试", reason="了解现有结构, 完成需求, 确保质量") - 添加顶层目标
+- goal(focus="2", add="设计接口, 实现代码", reason="明确API规范, 编写核心逻辑") - 切换到目标2,并添加子目标
 - goal(done="发现用户模型在 models/user.py") - 完成当前目标
-- goal(abandon="方案A需要Redis,环境没有", add="实现方案B") - 放弃当前并添加新目标
+- goal(abandon="方案A需要Redis,环境没有", add="实现方案B", reason="使用现有技术栈") - 放弃当前并添加新目标
 
 注意:内部 ID 是纯自增数字("1", "2", "3"),显示 ID 是带层级的("1", "2.1", "2.2")。
 focus 参数可以使用任意格式的 ID。
+reason 应该与 add 的目标数量一致,如果数量不一致,缺少的 reason 将为空。
 """,
         "parameters": {
             "type": "object",
@@ -136,6 +175,10 @@ focus 参数可以使用任意格式的 ID。
                     "type": "string",
                     "description": "添加目标(逗号分隔多个)。添加到当前 focus 的 goal 下作为子目标。"
                 },
+                "reason": {
+                    "type": "string",
+                    "description": "创建理由(逗号分隔多个,与 add 一一对应)。说明为什么要做这些目标。"
+                },
                 "done": {
                     "type": "string",
                     "description": "完成当前目标,值为 summary"

+ 22 - 14
agent/tools/builtin/goal.py

@@ -24,32 +24,33 @@ def get_goal_tree():
 
 
 @tool(description="管理执行计划,添加/完成/放弃目标,切换焦点")
-def goal(
+async def goal(
     add: Optional[str] = None,
+    reason: Optional[str] = None,
     done: Optional[str] = None,
     abandon: Optional[str] = None,
     focus: Optional[str] = None,
-    uid: str = ""
+    uid: str = "",
+    context: Optional[dict] = None
 ) -> str:
     """
-    管理执行计划。
+    管理执行计划,添加/完成/放弃目标,切换焦点
 
-    参数:
+    Args:
         add: 添加目标(逗号分隔多个)。添加到当前 focus 的 goal 下作为子目标。
+        reason: 创建理由(逗号分隔多个,与 add 一一对应)。说明为什么要做这些目标。
         done: 完成当前目标,值为 summary
         abandon: 放弃当前目标,值为原因(会触发 context 压缩)
         focus: 切换焦点到指定 id(可以是内部 ID 或显示 ID)
+        context: 工具执行上下文(包含 store 和 trace_id)
 
-    示例:
-        goal(add="分析代码, 实现功能, 测试") - 添加顶层目标
-        goal(focus="2", add="设计接口, 实现代码") - 切换到目标2,并添加子目标
-        goal(done="发现用户模型在 models/user.py") - 完成当前目标
-        goal(abandon="方案A需要Redis,环境没有", add="实现方案B") - 放弃当前并添加新目标
+    Examples:
+        goal(add="分析代码, 实现功能, 测试", reason="了解现有结构, 完成需求, 确保质量")
+        goal(focus="2", add="设计接口, 实现代码", reason="明确API规范, 编写核心逻辑")
+        goal(done="发现用户模型在 models/user.py")
+        goal(abandon="方案A需要Redis,环境没有", add="实现方案B", reason="使用现有技术栈")
 
-    注意: 内部 ID 是纯自增数字("1", "2", "3"),显示 ID 是带层级的("1", "2.1", "2.2")。
-    focus 参数可以使用任意格式的 ID。
-
-    返回:
+    Returns:
         str: 更新后的计划状态文本
     """
     from agent.goal.tool import goal_tool
@@ -58,9 +59,16 @@ def goal(
     if tree is None:
         return "错误:GoalTree 未初始化"
 
-    return goal_tool(
+    # 从 context 获取 store 和 trace_id
+    store = context.get("store") if context else None
+    trace_id = context.get("trace_id") if context else None
+
+    return await goal_tool(
         tree=tree,
+        store=store,
+        trace_id=trace_id,
         add=add,
+        reason=reason,
         done=done,
         abandon=abandon,
         focus=focus