From 08eb517a953cabe378714572cefa27a9236d49d5 Mon Sep 17 00:00:00 2001 From: alei37 Date: Sun, 22 Mar 2026 01:17:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20tool=5Fcall=20=E5=8F=82=E6=95=B0=20JSON?= =?UTF-8?q?=20=E8=A7=A3=E6=9E=90=E5=A4=B1=E8=B4=A5=E6=97=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20fallback=20=E5=B9=B6=E4=BF=AE=E5=A4=8D=20KeyError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 parsed_ok 标志分离成功/失败路径,统一在 if parsed_ok 块内执行 final_tool_calls.append 和 yield,避免在 except 块内直接处理成功路径 - Fallback 阶段尝试补全常见截断后缀(}, ], "}, "]),提升健壮性 - 改用 tool_use_buffer.pop(event.index, None) 替代 del,避免 KeyError - 日志同时输出原始 JSON 的前120字符和后120字符,便于定位截断位置 - 修复前:MiniMax API 返回的 tool input 为空字典,json.loads 失败后只用 空 {} 继续,导致工具调用缺少参数并可能触发 KeyError --- .../core/provider/sources/anthropic_source.py | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/astrbot/core/provider/sources/anthropic_source.py b/astrbot/core/provider/sources/anthropic_source.py index 203d0610ff..ca4eabb627 100644 --- a/astrbot/core/provider/sources/anthropic_source.py +++ b/astrbot/core/provider/sources/anthropic_source.py @@ -418,19 +418,40 @@ async def _query_stream( if event.index in tool_use_buffer: # 解析完整的工具调用 tool_info = tool_use_buffer[event.index] + parsed_ok = False + try: if "input_json" in tool_info: tool_info["input"] = json.loads(tool_info["input_json"]) - - # 添加到最终结果 - final_tool_calls.append( - { - "id": tool_info["id"], - "name": tool_info["name"], - "input": tool_info["input"], - }, + else: + tool_info["input"] = {} + parsed_ok = True + + except json.JSONDecodeError as e: + raw_json = tool_info.get("input_json", "") + logger.warning( + f"工具调用参数 JSON 解析失败: {e}。" + f"len={len(raw_json)}; head={raw_json[:120]!r}; tail={raw_json[-120:]!r}" ) - + # Fallback:尝试补全可能截断的尾部 + for suffix in ["}", "]", '"}', '"]']: + if raw_json and not raw_json.rstrip().endswith(suffix): + try: + tool_info["input"] = json.loads(raw_json + suffix) + parsed_ok = True + logger.info( + f"工具调用参数 JSON 解析成功 (fallback,修复后缀 '{suffix}')" + ) + break + except json.JSONDecodeError: + pass + + if parsed_ok: + final_tool_calls.append({ + "id": tool_info["id"], + "name": tool_info["name"], + "input": tool_info["input"], + }) yield LLMResponse( role="tool", completion_text="", @@ -441,12 +462,12 @@ async def _query_stream( usage=usage, id=id, ) - except json.JSONDecodeError: - # JSON 解析失败,跳过这个工具调用 - logger.warning(f"工具调用参数 JSON 解析失败: {tool_info}") + else: + logger.warning( + f"工具调用参数 JSON 解析最终失败,跳过工具调用: {tool_info['name']}" + ) - # 清理缓冲区 - del tool_use_buffer[event.index] + tool_use_buffer.pop(event.index, None) elif event.type == "message_delta": if event.usage: