Bug
POST /v1/chat/completions with OpenAI-format tools or tool_choice is rejected by the validator before reaching the model:
code=621 msg=worker: invalid http request: has unknown field 'tools'
code=621 msg=worker: invalid http request: has unknown field 'tool_choice'
In runners/helpers/ValidateRequest.cpp:1138-1144 both field handlers are written but gated behind if (false) { ... }, so check_unprocessed_fields (line 463) treats them as unknown and rejects.
Second issue: even after the validator accepts the fields, sglang in spec/spec-worker/cocoon-sglang.service:8 is launched without --tool-call-parser, so it would return function calls as plain text inside content instead of as a structured message.tool_calls array. For Qwen3 use --tool-call-parser qwen3 (or hermes as fallback).
Reproduce
No networking needed — validate_client_request (runners/helpers/ValidateRequest.h:47) is the same code path the worker uses, callable directly:
#include "runners/helpers/ValidateRequest.h"
#include <iostream>
int main() {
std::string body = R"({
"model": "Qwen/Qwen3-32B",
"messages": [{"role":"user","content":"hi"}],
"tools": [{"type":"function","function":{"name":"get_weather","parameters":{"type":"object"}}}],
"tool_choice": "auto"
})";
auto r = cocoon::validate_client_request(
"/v1/chat/completions", "application/json", body,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
std::cout << (r.is_error() ? r.move_as_error().to_string() : "OK") << "\n";
return 0;
}
Today: [Error : 0 : protoviolation : /v1/chat/completions has unknown field 'tool_choice']. Drop tool_choice from the body, same shape with 'tools'.
Fix
--- a/runners/helpers/ValidateRequest.cpp
+++ b/runners/helpers/ValidateRequest.cpp
@@ -1138,11 +1138,9 @@ static td::Status process_chat_completions(Ctx &ctx) {
TRY_STATUS(ctx.process_obj_field("temperature", false, process_double));
- if (false) {
- TRY_STATUS(ctx.process_obj_field("tool_choice", false, [](Ctx &ctx) { return td::Status::OK(); }));
- }
- if (false) {
- TRY_STATUS(ctx.process_obj_field(
- "tools", false, [](Ctx &ctx) { return ctx.process_array(false, [](Ctx &ctx) { return td::Status::OK(); }); }));
- }
+ TRY_STATUS(ctx.process_obj_field("tool_choice", false, [](Ctx &ctx) { return td::Status::OK(); }));
+ TRY_STATUS(ctx.process_obj_field(
+ "tools", false, [](Ctx &ctx) { return ctx.process_array(false, [](Ctx &ctx) { return td::Status::OK(); }); }));
TRY_STATUS(ctx.process_obj_field("top_logprobs", false, process_integer));
--- a/spec/spec-worker/cocoon-sglang.service
+++ b/spec/spec-worker/cocoon-sglang.service
@@ -8 +8 @@
-ExecStart=docker run ... --served-model-name $MODEL_NAME --enable-cache-report
+ExecStart=docker run ... --served-model-name $MODEL_NAME --enable-cache-report --tool-call-parser qwen3
Plus worker image rebuild + redeploy.
Verify
After the fix, a chat completion with tools should return a structured tool_calls array:
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_xxx",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" }
}]
},
"finish_reason": "tool_calls"
}]
}
Bug
POST /v1/chat/completionswith OpenAI-formattoolsortool_choiceis rejected by the validator before reaching the model:In
runners/helpers/ValidateRequest.cpp:1138-1144both field handlers are written but gated behindif (false) { ... }, socheck_unprocessed_fields(line 463) treats them as unknown and rejects.Second issue: even after the validator accepts the fields, sglang in
spec/spec-worker/cocoon-sglang.service:8is launched without--tool-call-parser, so it would return function calls as plain text insidecontentinstead of as a structuredmessage.tool_callsarray. For Qwen3 use--tool-call-parser qwen3(orhermesas fallback).Reproduce
No networking needed —
validate_client_request(runners/helpers/ValidateRequest.h:47) is the same code path the worker uses, callable directly:Today:
[Error : 0 : protoviolation : /v1/chat/completions has unknown field 'tool_choice']. Droptool_choicefrom the body, same shape with'tools'.Fix
Plus worker image rebuild + redeploy.
Verify
After the fix, a chat completion with
toolsshould return a structuredtool_callsarray:{ "choices": [{ "message": { "role": "assistant", "content": null, "tool_calls": [{ "id": "call_xxx", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\":\"Paris\"}" } }] }, "finish_reason": "tool_calls" }] }