feat(OpenAICompatProvider): enhance tool call handling with provider-specific fields

This commit is contained in:
Yohei Nishikubo
2026-03-25 10:00:29 +08:00
committed by Xubin Ren
parent 263069583d
commit 7b720ce9f7
2 changed files with 116 additions and 9 deletions
+54
View File
@@ -29,6 +29,29 @@ def _fake_chat_response(content: str = "ok") -> SimpleNamespace:
return SimpleNamespace(choices=[choice], usage=usage)
def _fake_tool_call_response() -> SimpleNamespace:
"""Build a minimal chat response that includes Gemini-style provider fields."""
function = SimpleNamespace(
name="exec",
arguments='{"cmd":"ls"}',
provider_specific_fields={"inner": "value"},
)
tool_call = SimpleNamespace(
id="call_123",
index=0,
function=function,
provider_specific_fields={"thought_signature": "signed-token"},
)
message = SimpleNamespace(
content=None,
tool_calls=[tool_call],
reasoning_content=None,
)
choice = SimpleNamespace(message=message, finish_reason="tool_calls")
usage = SimpleNamespace(prompt_tokens=10, completion_tokens=5, total_tokens=15)
return SimpleNamespace(choices=[choice], usage=usage)
def test_openrouter_spec_is_gateway() -> None:
spec = find_by_name("openrouter")
assert spec is not None
@@ -110,6 +133,37 @@ async def test_standard_provider_passes_model_through() -> None:
assert call_kwargs["model"] == "deepseek-chat"
@pytest.mark.asyncio
async def test_openai_compat_preserves_provider_specific_fields_on_tool_calls() -> None:
"""Gemini thought signatures must survive parsing so they can be sent back."""
mock_create = AsyncMock(return_value=_fake_tool_call_response())
spec = find_by_name("gemini")
with patch("nanobot.providers.openai_compat_provider.AsyncOpenAI") as MockClient:
client_instance = MockClient.return_value
client_instance.chat.completions.create = mock_create
provider = OpenAICompatProvider(
api_key="test-key",
api_base="https://generativelanguage.googleapis.com/v1beta/openai/",
default_model="google/gemini-3.1-pro-preview",
spec=spec,
)
result = await provider.chat(
messages=[{"role": "user", "content": "run exec"}],
model="google/gemini-3.1-pro-preview",
)
assert len(result.tool_calls) == 1
tool_call = result.tool_calls[0]
assert tool_call.provider_specific_fields == {"thought_signature": "signed-token"}
assert tool_call.function_provider_specific_fields == {"inner": "value"}
serialized = tool_call.to_openai_tool_call()
assert serialized["provider_specific_fields"] == {"thought_signature": "signed-token"}
assert serialized["function"]["provider_specific_fields"] == {"inner": "value"}
def test_openai_model_passthrough() -> None:
"""OpenAI models pass through unchanged."""
spec = find_by_name("openai")