fix(llm): suppress object-form tool_choice for Ollama (#1062)

- Ollama takes the local-compatible client, like the generic endpoint
This commit is contained in:
Yijia-Xiao
2026-09-17 01:10:48 +00:00
parent 60dcf64723
commit 8ac4371387
3 changed files with 29 additions and 2 deletions

View File

@@ -197,3 +197,28 @@ def test_ollama_offers_custom_model_id():
assert "custom" in values, f"Ollama {mode!r} missing 'custom' option: {entries}"
# Custom option is last so it doesn't push the curated defaults off-screen
assert values[-1] == "custom", f"'custom' should be last entry: {values}"
@pytest.mark.unit
def test_structured_output_suppresses_object_tool_choice(monkeypatch):
"""Ollama rejects the object-form tool_choice like other local servers
(#1062), and a local model ID has no capability entry saying otherwise, so
it takes the same client as the generic local endpoint."""
from langchain_openai import ChatOpenAI
from pydantic import BaseModel
from tradingagents.llm_clients import create_llm_client
class Schema(BaseModel):
x: int
captured = {}
monkeypatch.setattr(
ChatOpenAI,
"with_structured_output",
lambda self, schema, method=None, **kw: captured.update({"method": method, **kw}) or "BOUND",
)
create_llm_client(provider="ollama", model="qwen3:30b").get_llm().with_structured_output(Schema)
assert captured["tool_choice"] is None

View File

@@ -7,6 +7,7 @@ import pytest
from tradingagents.llm_clients.openai_client import (
OPENAI_COMPATIBLE_PROVIDERS,
DeepSeekChatOpenAI,
LocalCompatibleChatOpenAI,
MinimaxChatOpenAI,
NormalizedChatOpenAI,
is_openai_compatible,
@@ -39,7 +40,7 @@ def test_registry_membership():
("kimi", "https://api.moonshot.ai/v1", NormalizedChatOpenAI, False),
("groq", "https://api.groq.com/openai/v1", NormalizedChatOpenAI, False),
("nvidia", "https://integrate.api.nvidia.com/v1", NormalizedChatOpenAI, False),
("ollama", "http://localhost:11434/v1", NormalizedChatOpenAI, False),
("ollama", "http://localhost:11434/v1", LocalCompatibleChatOpenAI, False),
])
def test_registry_spec(provider, base_url, chat_class, responses):
spec = OPENAI_COMPATIBLE_PROVIDERS[provider]

View File

@@ -225,7 +225,8 @@ OPENAI_COMPATIBLE_PROVIDERS: dict[str, ProviderSpec] = {
"groq": ProviderSpec(base_url="https://api.groq.com/openai/v1"),
"nvidia": ProviderSpec(base_url="https://integrate.api.nvidia.com/v1"),
"ollama": ProviderSpec(base_url="http://localhost:11434/v1", base_url_env="OLLAMA_BASE_URL",
key_optional=True, placeholder_key="ollama"),
key_optional=True, placeholder_key="ollama",
chat_class=LocalCompatibleChatOpenAI),
# Generic endpoint: user supplies base_url; key optional (keyless local).
"openai_compatible": ProviderSpec(
require_base_url=True, key_optional=True, chat_class=LocalCompatibleChatOpenAI