Quick Start
1
Tools with Optional arguments now work on Ollama
An
Optional[...] argument no longer breaks the request on a local engine.unit still accepts None in Python — it simply stops advertising itself as nullable to the model.2
Hosted providers are untouched
gpt-4o and claude-* keep the full schema — they support nullable unions.How It Works
The LLM layer picks an adapter per engine, then callsformat_tools() right before the request — only the local adapters collapse nullable unions.
An Optional[str] argument produces {"type": ["string", "null"]}. Ollama models type as a single string and cannot unmarshal that union — it returns HTTP 400 for the whole request, not just that tool. The local adapters collapse the nullable to its one concrete member so the request stays valid.
What collapses vs what doesn’t
Only the nullable case (<type> plus "null") collapses. A genuine multi-type union is preserved — narrowing it would misrepresent the tool’s accepted inputs.
Your tool definitions are never mutated —
collapse_union_param_types returns a new list.
Structured output + tools on local models
Local engines cannot honour both a JSON grammar and a tool schema in the same request — the grammar makes the tool-call tag unemittable, so the model fabricates an answer instead of calling the tool. PraisonAI now refuses this combination up front.HTTP 200 with tool_calls: null and an invented answer under the JSON grammar. The model never ran the tool.
Fix: run the tool call first, then a second turn with output_pydantic on the summarising step.
Best Practices
Don't hand-write union types for local tools
Don't hand-write union types for local tools
Avoid
{"type": ["string", "integer"]} on arguments meant for local models — heterogeneous unions are preserved and the server may reject them. Pick one concrete type.Prefer a sentinel over Optional when absence matters
Prefer a sentinel over Optional when absence matters
Optional[str] collapses to str for local models, so the model no longer sees nullability. If you need the model to explicitly signal “no value”, use a plain str with a sentinel like "none" and handle it in the tool body.Verify the collapsed schema in a REPL
Verify the collapsed schema in a REPL
Inspect exactly what reaches litellm:
Split structured output from tool calls on local engines
Split structured output from tool calls on local engines
Run tools in one turn, then apply
output_json / output_pydantic on a second summarising turn. A local engine cannot do both at once.Related
Local Models
Point PraisonAI at Ollama or any OpenAI-compatible server.
Local Model Resolver
Set
llm="local" to auto-discover a running local server.
