fix(llm): forward tool definitions to Ollama

OllamaProvider built the /api/chat payload without the tools from
LLMInput, so Ollama models were never told any tools existed and a
ReActAgent backed by Ollama never ran one. The response side already
parsed message.tool_calls.

Send the tools in the OpenAI function format that /api/chat accepts,
matching the other providers.

Fixes #3274

Signed-off-by: FenjuFu <fufenjupku@gmail.com>
This commit is contained in:
FenjuFu
2026-09-30 20:38:19 +08:00
parent c70874fae9
commit 74836ce65f
2 changed files with 33 additions and 0 deletions
+2
View File
@@ -78,6 +78,8 @@ class OllamaProvider(LLMProvider):
options["num_predict"] = input.max_tokens
if options:
payload["options"] = options
if input.tools:
payload["tools"] = [tool.to_openai_tool() for tool in input.tools]
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
+31
View File
@@ -168,6 +168,37 @@ def test_ollama_provider_serializes_generation_options(
assert output.stop_reason == "stop"
def test_ollama_provider_serializes_tools_and_parses_tool_calls(monkeypatch):
requests = []
def fake_urlopen(request, timeout):
requests.append(request)
return BytesIO(
b'{"message": {"content": "", "tool_calls": [{"function": '
b'{"name": "search", "arguments": {"query": "ollama"}}}]}, "done_reason": "stop"}'
)
monkeypatch.setattr(urllib.request, "urlopen", fake_urlopen)
provider = OllamaProvider(base_url="http://localhost:11434", default_model="qwen3")
output = provider.generate(LLMInput(messages=[Message(role=Role.USER, content="hi")], tools=[_tool()]))
assert json.loads(requests[0].data)["tools"] == [
{
"type": "function",
"function": {
"name": "search",
"description": "Search",
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}},
"strict": True,
},
}
]
assert output.has_tool_calls
assert output.tool_calls[0].name == "search"
assert output.tool_calls[0].arguments == {"query": "ollama"}
def test_claude_provider_serializes_tools_for_messages_api():
provider = ClaudeProvider(api_key="test")
client = _AnthropicClient()