From c5935bef522b5365c2ed31fba2ce8ae010d332de Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Fri, 5 Jun 2026 19:04:50 +0800 Subject: [PATCH 1/3] Update entities.py --- astrbot/core/provider/entities.py | 34 +++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/astrbot/core/provider/entities.py b/astrbot/core/provider/entities.py index 8e12683ffb..f3ff3411ce 100644 --- a/astrbot/core/provider/entities.py +++ b/astrbot/core/provider/entities.py @@ -452,18 +452,24 @@ def to_openai_tool_calls(self) -> list[dict]: """Convert to OpenAI tool calls format. Deprecated, use to_openai_to_calls_model instead.""" ret = [] for idx, tool_call_arg in enumerate(self.tools_call_args): + tool_name = self.tools_call_name[idx] + tool_id = self.tools_call_ids[idx] + if not tool_name or not tool_id: + logger.warning( + f"Skipping tool call at index {idx} because function.name or id is empty/None. " + f"tool_call_id={tool_id!r}, tool_name={tool_name!r}, arguments={tool_call_arg!r}" + ) + continue payload = { - "id": self.tools_call_ids[idx], + "id": tool_id, "function": { - "name": self.tools_call_name[idx], + "name": tool_name, "arguments": json.dumps(tool_call_arg), }, "type": "function", } - if self.tools_call_extra_content.get(self.tools_call_ids[idx]): - payload["extra_content"] = self.tools_call_extra_content[ - self.tools_call_ids[idx] - ] + if self.tools_call_extra_content.get(tool_id): + payload["extra_content"] = self.tools_call_extra_content[tool_id] ret.append(payload) return ret @@ -471,17 +477,23 @@ def to_openai_to_calls_model(self) -> list[ToolCall]: """The same as to_openai_tool_calls but return pydantic model.""" ret = [] for idx, tool_call_arg in enumerate(self.tools_call_args): + tool_name = self.tools_call_name[idx] + tool_id = self.tools_call_ids[idx] + if not tool_name or not tool_id: + logger.warning( + f"Skipping tool call at index {idx} because function.name or id is empty/None. " + f"tool_call_id={tool_id!r}, tool_name={tool_name!r}, arguments={tool_call_arg!r}" + ) + continue ret.append( ToolCall( - id=self.tools_call_ids[idx], + id=tool_id, function=ToolCall.FunctionBody( - name=self.tools_call_name[idx], + name=tool_name, arguments=json.dumps(tool_call_arg), ), # the extra_content will not serialize if it's None when calling ToolCall.model_dump() - extra_content=self.tools_call_extra_content.get( - self.tools_call_ids[idx] - ), + extra_content=self.tools_call_extra_content.get(tool_id), ), ) return ret From 0214d6ae5be4b7497e59afacd2e019575abc2086 Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Fri, 5 Jun 2026 19:07:11 +0800 Subject: [PATCH 2/3] Update openai_source.py --- .../core/provider/sources/openai_source.py | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 8aa2778f1b..b98313227d 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -935,6 +935,7 @@ async def _parse_openai_completion( func_name_ls = [] tool_call_ids = [] tool_call_extra_content_dict = {} + skipped_count = 0 for tool_call in choice.message.tool_calls: if isinstance(tool_call, str): # workaround for #1359 @@ -945,6 +946,19 @@ async def _parse_openai_completion( raise Exception("工具集未提供") if tool_call.type == "function": + if ( + not tool_call.function + or not tool_call.function.name + or not tool_call.id + ): + logger.warning( + f"Skipping tool call with missing function, name or id: " + f"function={tool_call.function!r}, " + f"name={getattr(tool_call.function, 'name', None)!r}, " + f"id={tool_call.id!r}" + ) + skipped_count += 1 + continue # workaround for #1454 if isinstance(tool_call.function.arguments, str): try: @@ -966,11 +980,17 @@ async def _parse_openai_completion( if extra_content 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 - llm_response.tools_call_ids = tool_call_ids - llm_response.tools_call_extra_content = tool_call_extra_content_dict + if args_ls: + llm_response.role = "tool" + llm_response.tools_call_args = args_ls + llm_response.tools_call_name = func_name_ls + llm_response.tools_call_ids = tool_call_ids + llm_response.tools_call_extra_content = tool_call_extra_content_dict + elif skipped_count > 0: + logger.warning( + f"All {skipped_count} function tool call(s) were skipped due to " + "missing name, id, or function field. Treating as non-tool-call response." + ) # specially handle finish reason if choice.finish_reason == "content_filter": raise Exception( From 1011ebe60a25769568dcde4d9eb473180ed34fff Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Fri, 5 Jun 2026 19:08:14 +0800 Subject: [PATCH 3/3] Update tool_loop_agent_runner.py --- astrbot/core/agent/runners/tool_loop_agent_runner.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/astrbot/core/agent/runners/tool_loop_agent_runner.py b/astrbot/core/agent/runners/tool_loop_agent_runner.py index 3f74f0ec9b..0c7cf6c60f 100644 --- a/astrbot/core/agent/runners/tool_loop_agent_runner.py +++ b/astrbot/core/agent/runners/tool_loop_agent_runner.py @@ -997,6 +997,12 @@ def _append_tool_call_result(tool_call_id: str, content: str) -> None: llm_response.tools_call_args, llm_response.tools_call_ids, ): + if not func_tool_name or not func_tool_id: + logger.warning( + f"Skipping tool call with missing name or id: " + f"name={func_tool_name!r}, id={func_tool_id!r}, args={func_tool_args!r}" + ) + continue tool_result_blocks_start = len(tool_call_result_blocks) tool_call_streak = self._track_tool_call_streak(func_tool_name) yield _HandleFunctionToolsResult.from_message_chain(