|
|
@@ -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:
|
|
|
# 纯文本回复,没有工具调用
|
|
|
# 检查是否应该继续(用户任务可能还没完成)
|