Bug Description
When using Ollama with qwen2.5-coder:14b (and likely other Ollama models), tool calls are returned as a JSON string in the content field rather than as a structured tool_calls array. This causes Hermes to treat the response as a regular text reply and never actually execute the tools.
Environment
- Hermes version: (latest)
- Ollama version: (Mac app, latest)
- Model: qwen2.5-coder:14b
- OS: macOS
Steps to Reproduce
- Configure Hermes with Ollama provider using qwen2.5-coder:14b
- Send a message that requires a tool (e.g., "搜索今天北京的天气")
- Model returns tool call as plain text in content field
Actual Response Format from Ollama
{
"choices": [{
"message": {
"role": "assistant",
"content": "{\"name\": \"search\", \"arguments\": {\"query\": \"北京今天的天气\"}}",
"finish_reason": "stop"
}
}]
}
Note: no tool_calls field — the tool call is serialized as a JSON string inside content.
Expected Response Format (OpenAI standard)
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_xxx",
"type": "function",
"function": {
"name": "search",
"arguments": "{\"query\": \"北京今天的天气\"}"
}
}],
"finish_reason": "tool_calls"
}
}]
}
Root Cause
Hermes checks for tool calls via:
response.choices[0].message.tool_calls (OpenAI SDK attribute)
msg.get("tool_calls") (dict key lookup)
When a model returns the tool call embedded in content as a JSON string, neither check fires, so Hermes never routes to handle_function_call().
Suggested Fix
Add a fallback in the API response parsing: if no tool_calls found but content is a valid JSON string matching the pattern {"name": "<tool>", "arguments": {...}}, parse it and treat it as a tool call.
This would handle Ollama's non-standard tool call format without breaking standard tool_calls behavior.
Bug Description
When using Ollama with qwen2.5-coder:14b (and likely other Ollama models), tool calls are returned as a JSON string in the content field rather than as a structured tool_calls array. This causes Hermes to treat the response as a regular text reply and never actually execute the tools.
Environment
Steps to Reproduce
Actual Response Format from Ollama
{ "choices": [{ "message": { "role": "assistant", "content": "{\"name\": \"search\", \"arguments\": {\"query\": \"北京今天的天气\"}}", "finish_reason": "stop" } }] }Note: no
tool_callsfield — the tool call is serialized as a JSON string insidecontent.Expected Response Format (OpenAI standard)
{ "choices": [{ "message": { "role": "assistant", "content": null, "tool_calls": [{ "id": "call_xxx", "type": "function", "function": { "name": "search", "arguments": "{\"query\": \"北京今天的天气\"}" } }], "finish_reason": "tool_calls" } }] }Root Cause
Hermes checks for tool calls via:
response.choices[0].message.tool_calls(OpenAI SDK attribute)msg.get("tool_calls")(dict key lookup)When a model returns the tool call embedded in
contentas a JSON string, neither check fires, so Hermes never routes tohandle_function_call().Suggested Fix
Add a fallback in the API response parsing: if no
tool_callsfound butcontentis a valid JSON string matching the pattern{"name": "<tool>", "arguments": {...}}, parse it and treat it as a tool call.This would handle Ollama's non-standard tool call format without breaking standard tool_calls behavior.