Skip to content

fix(serve): a tool whose function is not an object answered 500 - #1710

Merged
JustVugg merged 1 commit into
JustVugg:devfrom
kevin9327:fix/serve-tool-function-non-object
Sep 23, 2026
Merged

JustVugg merged 1 commit into
JustVugg:devfrom
kevin9327:fix/serve-tool-function-non-object

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

What happens

POST /v1/chat/completions
{"model": "...", "messages": [...],
 "tools": [{"type": "function", "function": "search"}]}
HTTP/1.1 500 Internal Server Error
{"error":{"message":"The colibri engine failed to process the request.",
          "type":"server_error","param":null,"code":"engine_error"}}
[api] 127.0.0.1 - request failed: 'str' object has no attribute 'items'

Writing the name where the object goes is the same client slip #1598
covered on tool_choice. The server already has an answer for it:
generation_options() raises 400 "Tool function must be an object at index 0." It never runs on /v1/chat/completions, because the prompt
is rendered first:

fn = tool.get("function", tool) if isinstance(tool, dict) else {}
clean = {k: v for k, v in fn.items() if k not in ("defer_loading", "strict")}

That AttributeError reaches do_POST's catch-all, and the request
comes back as a 5xx that OpenAI SDKs retry, against an engine that was
never asked anything.

Reach

arch before after
glm, glm53, deepseek_v4, deepseek_v41 500 (renderer) 400
kimi, qwen38 400 (generation_options) 400
olmoe, inkling, qwen36 400 ("tools unsupported") 400
/v1/completions, any arch 400 (generation_options) 400

/v1/messages is unaffected: anthropic_tools() builds its own
function object.

The change

Read the member, then check it, through one small helper, so the four
declaration blocks that share fn.items() share the guard and the
existing 400 is what runs. A well-formed function object still renders
its name; that is asserted.

Verification

$ python -m unittest tests.test_openai_server.TemplateTest.test_tool_function_that_is_not_an_object_is_a_400 tests.test_openai_server.HTTPTest.test_tool_with_a_non_object_function_is_a_client_error tests.test_openai_server.HTTPTest.test_a_well_formed_tool_function_still_runs
# before: FAILED (failures=12, errors=4)
#   AttributeError: 'str' object has no attribute 'items'   (render_chat)
#   AssertionError: 500 != 400                            (glm/glm53/v4/v41)
# after:  Ran 3 tests ... OK

Full module and neighbours, on Windows (Python 3.11):

tests/test_openai_server.py        OK
tests/test_anthropic_messages.py   29 OK

The HTTPTest cases drive a real APIServer over a socket, so what is
asserted is the status the client sees.

AI-assisted (Grok)

{"type": "function", "function": "search"} on tools[] is the same
client slip JustVugg#1598 covered on tool_choice. generation_options already
has 400 "Tool function must be an object". The GLM and DeepSeek
declaration blocks did fn.items() first, so do_POST answered 500
"engine failed" for a request the engine never saw.
@JustVugg
JustVugg merged commit b8869a2 into JustVugg:dev Sep 23, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants