fix: accept blank tool call arguments
Treat blank OpenAI-compatible tool call arguments as an empty dict so parameterless tools such as finish can execute with providers that return an empty string. Also trim model identifiers during config normalization to avoid leading whitespace leaking into requests and snapshots.
This commit is contained in:
11
pytests/config_test/test_model_info_normalization.py
Normal file
11
pytests/config_test/test_model_info_normalization.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from src.config.model_configs import ModelInfo
|
||||
|
||||
|
||||
def test_model_identifier_strips_surrounding_whitespace() -> None:
|
||||
model_info = ModelInfo(
|
||||
api_provider="test-provider",
|
||||
model_identifier=" glm-5.1 ",
|
||||
name="test-model",
|
||||
)
|
||||
|
||||
assert model_info.model_identifier == "glm-5.1"
|
||||
@@ -1,16 +1,59 @@
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from src.config.model_configs import APIProvider, ReasoningParseMode, ToolArgumentParseMode
|
||||
from src.llm_models.model_client.openai_client import (
|
||||
_OpenAIStreamAccumulator,
|
||||
_build_reasoning_key,
|
||||
_default_normal_response_parser,
|
||||
_parse_tool_arguments,
|
||||
_sanitize_messages_for_toolless_request,
|
||||
)
|
||||
from src.llm_models.payload_content.message import Message, RoleType, TextMessagePart
|
||||
from src.llm_models.payload_content.tool_option import ToolCall
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parse_mode", list(ToolArgumentParseMode))
|
||||
def test_parse_tool_arguments_treats_blank_arguments_as_empty_dict(parse_mode: ToolArgumentParseMode) -> None:
|
||||
assert _parse_tool_arguments("", parse_mode, None) == {}
|
||||
assert _parse_tool_arguments(" ", parse_mode, None) == {}
|
||||
|
||||
|
||||
def test_normal_response_parser_accepts_empty_string_arguments_for_parameterless_tool() -> None:
|
||||
response = SimpleNamespace(
|
||||
choices=[
|
||||
SimpleNamespace(
|
||||
finish_reason="tool_calls",
|
||||
message=SimpleNamespace(
|
||||
content=None,
|
||||
tool_calls=[
|
||||
SimpleNamespace(
|
||||
id="finish-call",
|
||||
type="function",
|
||||
function=SimpleNamespace(name="finish", arguments=""),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
usage=None,
|
||||
model="glm-5.1",
|
||||
)
|
||||
|
||||
api_response, usage_record = _default_normal_response_parser(
|
||||
response,
|
||||
reasoning_parse_mode=ReasoningParseMode.AUTO,
|
||||
tool_argument_parse_mode=ToolArgumentParseMode.AUTO,
|
||||
reasoning_key=None,
|
||||
)
|
||||
|
||||
assert len(api_response.tool_calls) == 1
|
||||
assert api_response.tool_calls[0].func_name == "finish"
|
||||
assert api_response.tool_calls[0].args == {}
|
||||
assert usage_record is None
|
||||
|
||||
|
||||
def test_sanitize_messages_for_toolless_request_drops_assistant_tool_call_without_parts() -> None:
|
||||
messages = [
|
||||
Message(
|
||||
|
||||
@@ -351,6 +351,7 @@ class ModelInfo(ConfigBase):
|
||||
Gemini 客户端会按自身支持的字段筛选并映射到 GenerateContentConfig、EmbedContentConfig 或音频请求配置中。"""
|
||||
|
||||
def model_post_init(self, context: Any = None):
|
||||
self.model_identifier = self.model_identifier.strip()
|
||||
if not self.model_identifier:
|
||||
raise ValueError(t("config.model_identifier_empty_generic"))
|
||||
if not self.name:
|
||||
|
||||
@@ -585,6 +585,9 @@ def _parse_tool_arguments(
|
||||
Raises:
|
||||
RespParseException: 当参数无法解析为字典时抛出。
|
||||
"""
|
||||
if not raw_arguments.strip():
|
||||
return {}
|
||||
|
||||
try:
|
||||
if parse_mode == ToolArgumentParseMode.STRICT:
|
||||
arguments: Any = json.loads(raw_arguments)
|
||||
|
||||
Reference in New Issue
Block a user