Skip to content

fix(clients): normalize OpenAI-wire messages for Ollama native /api/chat#117

Merged
antoinezambelli merged 1 commit into
mainfrom
az/ollama-proxy-normalize
Jun 20, 2026
Merged

fix(clients): normalize OpenAI-wire messages for Ollama native /api/chat#117
antoinezambelli merged 1 commit into
mainfrom
az/ollama-proxy-normalize

Conversation

@antoinezambelli

Copy link
Copy Markdown
Owner

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:

How

An Ollama-local outbound normalizer applied in both send and send_stream
before building the request body:

Native /api/chat is kept — switching to Ollama's /v1 compat shim would force
a rewrite of the native response parsing (thinking field, dict args, NDJSON).

The content-flatten primitive is extracted to clients/base.py and reused in
proxy/convert.py (behavior-preserving); base.decode_tool_args is reused for
argument coercion.

Testing

  • Unit: normalizer + on-the-wire body for send/send_stream; arg edge cases,
    image-drop, no-mutation.
  • Integration: proxy → real OllamaClient (mocked transport), asserting the
    raw-passthrough first-attempt body — the seam that let both bugs ship.
  • Full suite: 1178 passed.
  • Live end-to-end against a real Ollama server: both raw shapes 400 today; both
    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

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>
@antoinezambelli antoinezambelli merged commit d72ad1c into main Jun 20, 2026
2 checks passed
@antoinezambelli antoinezambelli deleted the az/ollama-proxy-normalize branch June 20, 2026 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant