Skip to main content
PraisonAI adapts your tool schemas so local servers can parse them — automatically, per engine. Ollama, LM Studio, llama.cpp, and vLLM reject some tool schemas that hosted providers accept. Since PR #4924, PraisonAI rewrites the affected parts per engine so your tools work on local servers unchanged.

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 calls format_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.
Before the guard, the same request returned 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.
Hosted providers (OpenAI, Anthropic) are unaffected — they can combine the two.

Best Practices

Avoid {"type": ["string", "integer"]} on arguments meant for local models — heterogeneous unions are preserved and the server may reject them. Pick one concrete type.
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.
Inspect exactly what reaches litellm:
Run tools in one turn, then apply output_json / output_pydantic on a second summarising turn. A local engine cannot do both at once.

Local Models

Point PraisonAI at Ollama or any OpenAI-compatible server.

Local Model Resolver

Set llm="local" to auto-discover a running local server.