fix: 修复 tool_call.function 类型错误和合并冲突

- tool.py: 重构 openai_schema 避免 dict[str, str] 类型推断问题
- openai_source.py: 使用 getattr 安全访问 tool_call.function
- 解决 origin/dev 中的合并冲突
This commit is contained in:
LIghtJUNction
2026-03-27 20:45:35 +08:00
parent 14a1e37261
commit 0c194576c4
2 changed files with 23 additions and 29 deletions

View File

@@ -224,19 +224,18 @@ class ToolSet:
"""Convert tools to OpenAI API function calling schema format."""
result = []
for tool in self.tools:
func_def: dict[str, Any] = {
"type": "function",
"function": {"name": tool.name},
}
function_dict: dict[str, Any] = {"name": tool.name}
if tool.description:
func_def["function"]["description"] = tool.description
function_dict["description"] = tool.description
if tool.parameters is not None:
if (
tool.parameters and tool.parameters.get("properties")
) or not omit_empty_parameter_field:
func_def["function"]["parameters"] = tool.parameters
function_dict["parameters"] = tool.parameters
func_def: dict[str, Any] = {
"type": "function",
"function": function_dict,
}
result.append(func_def)
return result

View File

@@ -11,7 +11,6 @@ from pathlib import Path
from typing import Any, Literal
from urllib.parse import unquote, urlparse
import aiofiles
import httpx
from openai import AsyncAzureOpenAI, AsyncOpenAI
from openai._exceptions import NotFoundError
@@ -29,6 +28,7 @@ from astrbot.core.agent.message import ContentPart, ImageURLPart, Message, TextP
from astrbot.core.agent.tool import ToolSet
from astrbot.core.message.message_event_result import MessageChain
from astrbot.core.provider.entities import LLMResponse, TokenUsage, ToolCallsResult
from astrbot.core.provider.register import register_provider_adapter
from astrbot.core.utils.io import download_image_by_url
from astrbot.core.utils.network_utils import (
create_proxy_client,
@@ -37,8 +37,6 @@ from astrbot.core.utils.network_utils import (
)
from astrbot.core.utils.string_utils import normalize_and_dedupe_strings
from ..register import register_provider_adapter
@register_provider_adapter(
"openai_chat_completion",
@@ -736,23 +734,28 @@ class ProviderOpenAIOfficial(Provider):
# Should be unreachable
raise Exception("工具集未提供")
for tool in tools.list_tools():
# Safely access function attribute (ChatCompletionMessageCustomToolCall doesn't have it)
tool_call_function = getattr(tool_call, "function", None)
if not tool_call_function:
continue
if (
tool_call.type == "function"
and tool.name == tool_call.function.name
and tool_call_function.name == tool.name
):
# workaround for #1454
if isinstance(tool_call.function.arguments, str):
args = json.loads(tool_call.function.arguments)
arguments = tool_call_function.arguments
if isinstance(arguments, str):
args = json.loads(arguments)
else:
args = tool_call.function.arguments
args = arguments
args_ls.append(args)
func_name_ls.append(tool_call.function.name)
func_name_ls.append(tool_call_function.name)
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
# 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
llm_response.role = "tool"
llm_response.tools_call_args = args_ls
llm_response.tools_call_name = func_name_ls
@@ -1127,7 +1130,6 @@ class ProviderOpenAIOfficial(Provider):
) -> dict:
"""组装成符合 OpenAI 格式的 role 为 user 的消息段"""
# 构建内容块列表
content_blocks = []
@@ -1177,18 +1179,11 @@ class ProviderOpenAIOfficial(Provider):
async def encode_image_bs64(self, image_url: str) -> str:
"""将图片转换为 base64"""
<<<<<<< ours
if image_url.startswith("base64://"):
return image_url.replace("base64://", "data:image/jpeg;base64,")
async with aiofiles.open(image_url, "rb") as f:
image_bs64 = base64.b64encode(await f.read()).decode("utf-8")
return "data:image/jpeg;base64," + image_bs64
=======
image_data = await self._image_ref_to_data_url(image_url, mode="strict")
if image_data is None:
raise RuntimeError(f"Failed to encode image data: {image_url}")
return image_data
>>>>>>> theirs
async def terminate(self):
if self.client: