Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/cpp/server/ollama_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,36 @@ json OllamaApi::convert_ollama_to_openai_chat(const json& ollama_request) {
openai_msg["content"] = msg.value("content", "");
}

// Forward tool_calls if present
// Forward tool_calls if present, with two normalisations required
// by llama.cpp's strict OpenAI-spec validation:
// 1. Inject "type":"function" — Ollama clients omit this field.
// 2. Skip tool calls whose arguments are not valid JSON — these
// are streaming artifacts (e.g. arguments="{") that an Ollama
// client may persist to history mid-stream; forwarding them
// causes func_args_not_string() to throw a parse error 500.
if (msg.contains("tool_calls")) {
openai_msg["tool_calls"] = msg["tool_calls"];
json tool_calls = json::array();
for (auto tc : msg["tool_calls"]) {
if (!tc.contains("type")) {
tc["type"] = "function";
}
// Validate arguments JSON if present
if (tc.contains("function") && tc["function"].contains("arguments")) {
Comment on lines 311 to +318
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msg.contains("tool_calls") doesn’t verify the field is an array, and the loop doesn’t guard that each tc is an object. If a malformed Ollama client sends tool_calls as a non-array or includes non-object entries, tc["type"] = "function" will throw a nlohmann::json type_error and still produce a 500. Consider checking msg["tool_calls"].is_array() and skipping any tc that isn’t an object before mutating/inspecting it (and similarly ensure tc["function"] is an object before reading arguments).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

const auto& args = tc["function"]["arguments"];
if (args.is_string()) {
try {
json::parse(args.get<std::string>());
} catch (const std::exception&) {
// Skip this tool call — arguments are not valid JSON
continue;
}
}
}
tool_calls.push_back(tc);
}
if (!tool_calls.empty()) {
openai_msg["tool_calls"] = tool_calls;
}
}

messages.push_back(openai_msg);
Expand Down
Loading