|
|
@@ -73,6 +73,43 @@ TOOL_REGISTRY = _build_tool_registry()
|
|
|
# 解析 & 执行引擎 (v2)
|
|
|
# ════════════════════════════════════════════════════════════
|
|
|
|
|
|
+def _try_parse_json(json_str: str) -> dict | None:
|
|
|
+ """Try multiple strategies to parse potentially malformed JSON from LLM output."""
|
|
|
+ # Strategy 1: direct parse
|
|
|
+ try:
|
|
|
+ return json.loads(json_str)
|
|
|
+ except json.JSONDecodeError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ # Strategy 2: replace real newlines inside string values with \\n
|
|
|
+ # This handles: {"content": "line1\nline2"} where \n is a real newline
|
|
|
+ try:
|
|
|
+ fixed = json_str.replace("\n", "\\n")
|
|
|
+ # But we need to un-escape \\n that were already escaped (\\\\n → \\n)
|
|
|
+ fixed = fixed.replace("\\\\n", "\\n")
|
|
|
+ return json.loads(fixed)
|
|
|
+ except json.JSONDecodeError:
|
|
|
+ pass
|
|
|
+
|
|
|
+ # Strategy 3: extract just the first { ... } block more aggressively
|
|
|
+ try:
|
|
|
+ depth = 0
|
|
|
+ start = json_str.index("{")
|
|
|
+ for i in range(start, len(json_str)):
|
|
|
+ if json_str[i] == "{":
|
|
|
+ depth += 1
|
|
|
+ elif json_str[i] == "}":
|
|
|
+ depth -= 1
|
|
|
+ if depth == 0:
|
|
|
+ candidate = json_str[start:i+1]
|
|
|
+ fixed = candidate.replace("\n", "\\n").replace("\\\\n", "\\n")
|
|
|
+ return json.loads(fixed)
|
|
|
+ except (json.JSONDecodeError, ValueError):
|
|
|
+ pass
|
|
|
+
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str, bool]:
|
|
|
"""
|
|
|
解析 LLM 输出,提取工具调用并执行。
|
|
|
@@ -87,9 +124,10 @@ def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str,
|
|
|
return llm_output, False
|
|
|
|
|
|
json_str = matches[-1].strip()
|
|
|
- try:
|
|
|
- data = json.loads(json_str)
|
|
|
- except json.JSONDecodeError:
|
|
|
+
|
|
|
+ # 尝试多种方式解析 JSON(LLM 输出的 JSON 经常不规范)
|
|
|
+ data = _try_parse_json(json_str)
|
|
|
+ if data is None:
|
|
|
return f"(无法解析 JSON: {json_str[:100]})\n{llm_output}", False
|
|
|
|
|
|
# 统一格式: action/tool 都支持
|