From bc7b934b9a6a27bfb3f040fc51e3bee485473f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=9B=E8=96=87Lovie?= Date: Sun, 15 Mar 2026 20:00:29 +0800 Subject: [PATCH 1/2] fix: handle tool_calls correctly in streaming mode for OpenAI provider --- .../core/provider/sources/openai_source.py | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index c40234ed47..826232e465 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -311,6 +311,7 @@ async def _query_stream( state.handle_chunk(chunk) except Exception as e: logger.warning("Saving chunk state error: " + str(e)) + if len(chunk.choices) == 0: continue delta = chunk.choices[0].delta @@ -507,23 +508,30 @@ async def _parse_openai_completion( # Should be unreachable raise Exception("工具集未提供") for tool in tools.func_list: + # Fix: Both ParsedFunctionToolCall(type=None) and ChatCompletionMessageFunctionToolCall(type='function') + # can be returned by get_final_completion(). Check function.name instead of type. + tool_func = getattr(tool_call, "function", None) + tool_call_id = getattr(tool_call, "id", None) if ( - tool_call.type == "function" - and tool.name == tool_call.function.name + tool_func is not None + and hasattr(tool_func, "name") + and tool.name == tool_func.name ): + tool_func_args = getattr(tool_func, "arguments", None) # workaround for #1454 - if isinstance(tool_call.function.arguments, str): - args = json.loads(tool_call.function.arguments) + if isinstance(tool_func_args, str): + args = json.loads(tool_func_args) else: - args = tool_call.function.arguments + args = tool_func_args args_ls.append(args) - func_name_ls.append(tool_call.function.name) - tool_call_ids.append(tool_call.id) + func_name_ls.append(tool_func.name) + if tool_call_id is not None: + tool_call_ids.append(tool_call_id) # gemini-2.5 / gemini-3 series extra_content handling extra_content = getattr(tool_call, "extra_content", None) - if extra_content is not None: - tool_call_extra_content_dict[tool_call.id] = extra_content + if extra_content is not None and tool_call_id is not None: + tool_call_extra_content_dict[tool_call_id] = extra_content llm_response.role = "tool" llm_response.tools_call_args = args_ls llm_response.tools_call_name = func_name_ls From 8dab4b281fd1d0bf2d440e936975ef4cb108d105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B4=9B=E8=96=87Lovie?= Date: Sun, 15 Mar 2026 20:15:08 +0800 Subject: [PATCH 2/2] refactor: simplify tool call matching condition --- astrbot/core/provider/sources/openai_source.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 826232e465..1fb60cf6e9 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -512,11 +512,7 @@ async def _parse_openai_completion( # can be returned by get_final_completion(). Check function.name instead of type. tool_func = getattr(tool_call, "function", None) tool_call_id = getattr(tool_call, "id", None) - if ( - tool_func is not None - and hasattr(tool_func, "name") - and tool.name == tool_func.name - ): + if tool_func and getattr(tool_func, "name", None) == tool.name: tool_func_args = getattr(tool_func, "arguments", None) # workaround for #1454 if isinstance(tool_func_args, str):