Skip to content

Ollama backend: native raw-message passthrough sends tool_call arguments as JSON strings → 400 on multi-turn tool sessions #111

@imwldev

Description

@imwldev

Summary

With --backend ollama (native capability), forge forwards the verbatim OpenAI
transcript to Ollama. Assistant history messages carry
tool_calls[].function.arguments as a JSON string (OpenAI wire format), but
Ollama's /api/chat requires them as an object (dict). Every multi-turn
tool session fails on the 2nd+ turn with:

400 {"error":"Value looks like object, but can't find closing '}' symbol"}

First turn succeeds (no tool history yet); as soon as an assistant tool_calls
turn enters the history, all subsequent requests 400. Symptom from the client
(opencode) side: the proxy returns a non-OpenAI error chunk and the client's
schema validation crashes.

Minimal repro (directly against Ollama, bypassing forge)

# args as STRING  -> 400
curl -s http://localhost:11434/api/chat -d '{"model":"qwen3-coder:30b","stream":false,
 "messages":[{"role":"user","content":"x"},
  {"role":"assistant","tool_calls":[{"function":{"name":"t","arguments":"{\"x\":\"1\"}"}}]},
  {"role":"tool","content":"ok"}],
 "tools":[{"type":"function","function":{"name":"t","parameters":{"type":"object","properties":{"x":{"type":"string"}}}}}]}'

# args as DICT    -> 200   (same request, arguments as object)

Root cause

  • proxy/handler.py: in native_passthrough, raw_messages_for_backend = _raw_openai_messages(request_messages) forwards arguments unchanged.
  • clients/ollama.py send() / send_stream() post these messages to /api/chat without coercing arguments str→dict.
  • proxy/convert.py already does this coercion on the inbound path (if isinstance(args, str): args = json.loads(args)), but the raw-passthrough path bypasses it.

Suggested fix

Coerce tool_calls[].function.arguments from str→dict for the Ollama backend
(e.g. in OllamaClient.send/send_stream before building the request body, or
suppress raw passthrough for Ollama so the normal format="ollama" serialization
is used).

Env

forge-guardrails 0.7.5, Ollama 0.30.8, model qwen3-coder (custom), client: opencode (OpenAI protocol, streaming).

--- Above message is written by Opus4.8 and it fixed my local env automatically and confirm working. ---
I with this info will help to improve this project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions