Kaynağa Gözat

fix: agent67 — show actual tool name instead of 'unknown'

parse_and_execute now returns (result, is_done, tool_name) 3-tuple.
chat() uses the returned tool_name directly instead of re-parsing
JSON with a regex that couldn't handle nested objects.

Display: "🔧 [步骤 3/15] 调用工具: WriteFile" instead of "unknown"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kenny67nju 5 ay önce
ebeveyn
işleme
9dc74cfa04
1 değiştirilmiş dosya ile 11 ekleme ve 38 silme
  1. 11 38
      agentexample/agent67/core/assistant.py

+ 11 - 38
agentexample/agent67/core/assistant.py

@@ -215,22 +215,17 @@ def _fix_json_newlines(s: str) -> str:
     return ''.join(result)
 
 
-def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str, bool]:
+def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str, bool, str]:
     """
     解析 LLM 输出,提取工具调用并执行。
 
-    v2: 统一 action/tool 格式,支持内置工具 + macOS 工具
-    策略:尝试所有 ```json 块(从第一个开始),找到第一个可执行的
+    Returns: (result_text, is_done, tool_name)
     """
-    # 尝试从 ```json ... ``` 代码块提取
-    # 使用贪婪匹配找到最外层的 ```json...``` 对
-    # 注意:内容中可能含有 ``` 标记(如 markdown 代码块),需要特殊处理
     matches = _extract_json_blocks(llm_output)
     if not matches:
-        # fallback: 找内联 JSON
         matches = re.findall(r'\{[^{}]*(?:"action"|"tool")\s*:.*?\}', llm_output, re.DOTALL)
     if not matches:
-        return llm_output, False
+        return llm_output, False, ""
 
     # 尝试每个 match(从第一个开始),找到第一个包含 action/tool 的可解析 JSON
     data = None
@@ -242,7 +237,7 @@ def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str,
             break
 
     if data is None:
-        return f"(无法解析 JSON: {matches[0][:100]})\n{llm_output}", False
+        return f"(无法解析 JSON: {matches[0][:100]})\n{llm_output}", False, ""
 
     # 统一格式: action/tool 都支持
     tool_name = data.get("action", data.get("tool", ""))
@@ -250,12 +245,12 @@ def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str,
     # 完成信号
     if tool_name in ("done", "terminate"):
         answer = data.get("summary", data.get("input", {}).get("answer", "")) if isinstance(data.get("input"), dict) else data.get("summary", "任务完成")
-        return str(answer) if answer else "任务完成", True
+        return (str(answer) if answer else "任务完成"), True, tool_name
 
     # 路由到工具
     tool = TOOL_REGISTRY.get(tool_name)
     if not tool:
-        return f"❌ 未知工具: {tool_name}. 可用: {', '.join(sorted(TOOL_REGISTRY.keys())[:15])}...", False
+        return f"❌ 未知工具: {tool_name}. 可用: {', '.join(sorted(TOOL_REGISTRY.keys())[:15])}...", False, tool_name
 
     # 准备输入
     tool_input = data.get("input", {})
@@ -291,7 +286,7 @@ def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str,
                    output=output_wrapper, duration_ms=duration_ms, ctx=None)
         result = output_wrapper["value"]
 
-    return str(result), False
+    return str(result), False, tool_name
 
 
 # ════════════════════════════════════════════════════════════
@@ -416,7 +411,7 @@ class PersonalAssistant:
                 print(f"  ✅ {_step_label} 思考完成 ({think_ms:.0f}ms)          ")
 
             # 解析并执行 (v2: 支持内置工具 + hooks)
-            result, is_done = parse_and_execute(str(llm_output), self.hooks)
+            result, is_done, tool_name = parse_and_execute(str(llm_output), self.hooks)
 
             if is_done:
                 text_part = re.sub(r'```json.*?```', '', str(llm_output), flags=re.DOTALL).strip()
@@ -427,42 +422,20 @@ class PersonalAssistant:
             if result != str(llm_output):
                 # 工具被执行
                 text_part = re.sub(r'```json.*?```', '', str(llm_output), flags=re.DOTALL).strip()
-                json_matches = re.findall(r'\{[^{}]*(?:"action"|"tool")\s*:.*?\}', str(llm_output))
-                tool_call = json_matches[-1] if json_matches else ""
-
-                tool_name = ""
-                try:
-                    tool_name = json.loads(tool_call).get("action", json.loads(tool_call).get("tool", ""))
-                except Exception:
-                    pass
 
                 # 显示思考内容
                 if text_part and not is_streaming:
                     print(f"  💭 {text_part[:200]}")
 
-                # 显示工具调用(带进度)
-                t0_tool = _time.time()
-                print(f"  🔧 {_step_label} 调用工具: {tool_name or 'unknown'}")
-
-                # 工具输入预览
-                try:
-                    tool_input_data = json.loads(tool_call).get("input", {})
-                    if isinstance(tool_input_data, dict):
-                        input_preview = json.dumps(tool_input_data, ensure_ascii=False)[:120]
-                    else:
-                        input_preview = str(tool_input_data)[:120]
-                    print(f"     输入: {input_preview}")
-                except Exception:
-                    pass
-
-                tool_ms = (_time.time() - t0_tool) * 1000
+                # 显示工具调用
+                print(f"  🔧 {_step_label} 调用工具: {tool_name or '?'}")
 
                 # 工具结果预览
                 result_preview = result[:300].replace("\n", "\n     ")
                 print(f"     结果: {result_preview}")
                 print()
 
-                observations.append(f"[工具调用] {tool_call}\n[执行结果] {result}")
+                observations.append(f"[工具: {tool_name}]\n[执行结果] {result}")
             else:
                 # 纯文本回复,没有工具调用
                 # 检查是否应该继续(用户任务可能还没完成)