Skip to main content
When the model calls WebSearch but your tool is registered as web_search, PraisonAI fixes the name automatically — and when it can’t, it tells the model exactly which tools exist and how to call them.

Quick Start

1

Case and separator drift auto-fixes

Both dispatch to web_search. No configuration required.
2

Unknown names return a corrective message

The message is what the model sees, so on the next turn it retries with a valid name.
3

Bind failures echo the parameters

The model learns which arguments to send next. This hint fires only when the kwargs cannot bind — a genuine argument-binding error.
4

Value errors pass through unchanged

The kwargs bound cleanly, so the model isn’t told to change the parameter names — it’s told to fix the value.
No new API and no new Agent(...) parameter. Self-repair is a runtime behavior of the existing tool-dispatch path, triggered whenever an LLM tool call arrives.

Async parity

max_tool_repairs and force_tool_usage apply identically whether the agent is called synchronously (agent.start(...)) or awaited (agent.chat(...) / agent.astart(...)). Before PraisonAI PR #5359, both settings — set automatically by OllamaAdapter and passable via the llm dict — were honoured on the sync path but silently ignored the moment the agent was awaited. They now take effect on both paths.
Agent(..., force_tool_usage=..., max_tool_repairs=...) is still not valid — both settings go on the llm dict (or on an LLM(...) instance), documented on Ollama → force_tool_usage / max_tool_repairs.

How It Works

Every tool call runs through Agent.execute_tool; a dispatch miss triggers normalisation, repair, or a corrective error before the result returns to the model. Normalisation compares str(name).lower().replace('_', '').replace('-', '').replace(' ', ''). Repair fires only when exactly one active tool normalises to the same key — ambiguous matches skip repair and fall through to the corrective error, so there is never an ambiguous dispatch.

Four failure modes, four responses

Silent auto-fix. The tool runs and returns its normal result. A debug log records the repair:

Text-based tool-call recovery (local models)

Small local models often emit a tool call as text inside content instead of using the native tool_calls[] field. The adapters for Ollama and OpenAI-compatible local servers (LM Studio, vLLM, llama.cpp) parse that text and dispatch the call anyway — with no extra LLM round trip.
Extended in PraisonAI PR #5215 (merged 2026-09-23). Two new dialects are now recovered — <tool_call>{...}</tool_call> and <function=name>{...}</function> — on top of the existing whole-content JSON path. Two safety guarantees apply: fenced code-block masking on all three dialects (a call shown as documentation is never run) and an advertised-name allowlist on the two tag dialects (a model hallucinating a tool name in a <tool_call> or <function=…> block can never trigger execution). The whole-content JSON path dropped its allowlist requirement in PR #5359 — see the safety-guarantees section below. Both the sync path (agent.start() / agent.chat()) and the async path (agent.astart() / agent.achat()) get the same coverage — async parity was first landed in PR #4940. Streaming still does not have this recovery.

Three dialects, one code path

Both {...} and [{...}, {...}] shapes work.
Interleaved dialects are salvaged in text order (sorted by match offset) — a side-effecting or dependent call is never reordered ahead of one it relies on.

Safety guarantees

A name recovered from a <tool_call> or <function=…> block must match a tool the model was actually offered. A hallucinated name in one of those blocks is silently discarded.
The whole-content JSON path is looser: since PraisonAI PR #5359 it recovers any dict with a name field without an allowlist check, so a legitimate call the model emits as JSON in prose is picked up even before name-repair runs. Argument filtering and downstream tool-name repair still apply to whatever it recovers.
A call inside a Markdown fence (``` or ~~~, length ≥ 3) is treated as documentation and never run — including calls that name a real tool.
An unclosed fence is treated as running to end-of-content, so a truncated example that opens a fence and names a real tool cannot slip through.
<TOOL_CALL>, <Tool_Call>, and <FUNCTION=name> all match — a model that emits the tags in any case still recovers just like the lowercase form.
Clean provider-native responses return None after a single lowercase marker check — no regex runs on prose that never mentions a call. There is no per-turn cost to fear.

Adapter coverage

Hosted models deliberately do not get salvage — a hosted model returning JSON prose must never have it silently interpreted as a tool call. Streaming does not yet have this recovery on any adapter; all three dialects apply on the non-streaming path only, sync and async. If the response also carries a native tool_calls[] entry, that is dispatched — text-based recovery only runs when tool_calls[] is empty. So a well-formed native call is never overridden by prose the model happened to also include. Recovery participates in the same repair budget as name-and-parameter repair — controlled by max_tool_repairs (default 2 for local adapters, 0 for hosted). Since PraisonAI PR #5359, recovering a tool call from JSON content inside a prose reply no longer requires the tool’s name to be in an internal allowlist — any dict carrying a name field is a candidate for recovery, so a legitimate call the model buries in prose is picked up more reliably.

Tool return values that can’t be JSON-serialised

If your tool returns a set, a datetime, a Path, or any other value that json.dumps() can’t handle, the tool-result message falls back to str(tool_result) — the turn survives and the model gets a readable string. No configuration required.
When your tool returns {"error": "…"} (or a list starting with one), the message the model sees is a standardised apology-oriented instruction: “Error: <your error>. Please inform the user that the operation could not be completed.” — so the model explains the failure naturally instead of echoing the raw error. When your tool returns None, the model sees “Function returned an empty output” as the result. For Ollama specifically, the same information is delivered as a role: "user" natural-language turn instead of role: "tool" — see Ollama → How Ollama is handled differently.

Why the hints reach the model

ToolExecutionError preserves only its message through conversion, so the corrective payload is folded into the message string itself.
The Did you mean '<nearest>'? hint and the Available tools: [...] list live inside the error string, so the follow-up model turn sees them via the raised ToolExecutionError.message.
The bind-failure branch appends Expected parameters for '<tool>' — required: [...], optional: [...]. to the message string. The same names are also returned as a structured expected_parameters dict on the impl-level result.
A TypeError or ValueError raised inside a successfully-bound tool is a value problem, not a parameter problem. The runtime gates the schema hint on inspect.signature(target).bind(**kwargs) — the required/optional names only appear in the error message when the kwargs actually failed to bind. Otherwise the tool’s own error string reaches the model unchanged, so the model fixes the value instead of renaming the parameters.
No new public API, no new Agent parameters, and no new environment variables. The prior behavior on unknown tools was a bare Tool 'X' is not callable — not a stable contract, so no valid callers depended on it. When signature introspection itself is impossible (e.g. a C-implemented callable), the gate fails closed — it omits the hint rather than echo a misleading one.

MCP tools participate too

MCP-container tools are expanded into their contained tools, so their names appear in the corrective inventory and can be name-repaired — not just the opaque MCP container.
If none of the MCP tools match, the corrective error’s Available tools: list contains the MCP-provided tool names instead of the container. Only MCP instances that iterate into per-tool callables exposing __name__ or name benefit — the standard praisonaiagents.mcp.MCP satisfies this.

Init-time vs runtime

Two resolvers cover two different moments. Self-repair complements — it does not replace — init-time Tool Resolution.

Tool Resolution

Init-time typo detection when you pass tool names to Agent(tools=[...])

Error Handling

Catch and act on ToolExecutionError and other structured errors

MCP

Connect model-context-protocol servers as tools

Allowed Tools

Restrict which tools an agent can call