fix(clients): normalize OpenAI-wire messages for Ollama native /api/chat#117
Merged
Conversation
OllamaClient runs the proxy's native passthrough, so on the clean first attempt the client's verbatim OpenAI-wire messages reach Ollama's native /api/chat unmodified. Ollama's schema is stricter than the OpenAI wire format, producing 400s the converted path never hit: - multi-part array content -> "cannot unmarshal array ... of type string" - tool_calls[].function.arguments as a JSON string -> "Value looks like object, but can't find closing '}'" (every multi-turn tool session) Add an Ollama-local outbound normalizer applied in both send and send_stream before building the request body: flatten multi-part content to text (text-only, images dropped) and coerce string tool-call arguments to dicts (only when they decode to an object; malformed payloads pass through untouched). The native /api/chat endpoint is kept; switching to Ollama's /v1 compat shim would require rewriting response parsing. Extract the content-flatten primitive to clients/base.py and reuse it in proxy/convert.py (behavior-preserving); reuse base.decode_tool_args for the argument coercion. Closes #111 Closes #115 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes two Ollama proxy bugs (#111, #115) of the same class. OllamaClient runs
native passthrough, so the client's verbatim OpenAI-wire messages reach
Ollama's native
/api/chat, whose schema is stricter than the OpenAI wire format:content→400 cannot unmarshal array ... of type stringtool_calls[].function.argumentsas a JSON string →400 Value looks like object, but can't find closing '}'(every multi-turntool session, from the 2nd turn on)
How
An Ollama-local outbound normalizer applied in both
sendandsend_streambefore building the request body:
contentarrays to text (text-only — images dropped,consistent with the existing converted path; full multi-modal support is Add multi-part / image content support (text-only today; proxy↔WorkflowRunner parity) #116)
tool_calls[].function.argumentsfrom JSON string to dict, only whenit decodes to an object (malformed/non-object payloads pass through untouched)
Native
/api/chatis kept — switching to Ollama's/v1compat shim would forcea rewrite of the native response parsing (thinking field, dict args, NDJSON).
The content-flatten primitive is extracted to
clients/base.pyand reused inproxy/convert.py(behavior-preserving);base.decode_tool_argsis reused forargument coercion.
Testing
send/send_stream; arg edge cases,image-drop, no-mutation.
OllamaClient(mocked transport), asserting theraw-passthrough first-attempt body — the seam that let both bugs ship.
succeed through forge's normalizer.
Known limitation
Images in multi-part content are dropped (text-only), matching prior behavior.
Real multi-modal support is tracked separately in #116.
Closes #111
Closes #115
🤖 Generated with Claude Code