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

fix: agent67 JSON parsing — handle multiline content in tool calls

LLM outputs JSON with real newlines in string values (e.g. WriteFile
content spanning multiple lines). Standard json.loads() fails on these.

Added _try_parse_json() with 3 strategies:
1. Direct parse
2. Replace real newlines with \\n in string values
3. Aggressive { } depth matching + newline fix

Fixes WriteFile/EditFile calls with multiline content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kenny67nju пре 5 месеци
родитељ
комит
195db744f7
1 измењених фајлова са 41 додато и 3 уклоњено
  1. 41 3
      agentexample/agent67/core/assistant.py

+ 41 - 3
agentexample/agent67/core/assistant.py

@@ -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 都支持