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
web_search. No configuration required.2
Unknown names return a corrective message
3
Bind failures echo the parameters
4
Value errors pass through unchanged
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.How It Works
Every tool call runs throughAgent.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
- Case / separator drift
- Unknown name
- Bind failure
- Runtime error inside the tool
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 sometimes emit a tool call as JSON text in thecontent field instead of using the tool_calls[] field. The adapters for Ollama and local OpenAI-compatible servers (LM Studio, vLLM, llama.cpp) parse that JSON and dispatch the call anyway.
name and arguments:
{...} and [{...}, {...}] shapes work. Hosted OpenAI-compatible endpoints, Anthropic, and Gemini deliberately do not get this behaviour — a hosted model returning JSON prose must never have it silently interpreted as a tool call.
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).
Tool return values that can’t be JSON-serialised
If your tool returns aset, 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.
{"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.
Corrective inventory in the message
Corrective inventory in the message
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.Parameter hint in the message
Parameter hint in the 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.Only true bind failures get the hint
Only true bind failures get the hint
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.Backward compatible
Backward compatible
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 opaqueMCP container.
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.
Related
Tool Resolution
Init-time typo detection when you pass tool names to
Agent(tools=[...])Error Handling
Catch and act on
ToolExecutionError and other structured errorsMCP
Connect model-context-protocol servers as tools
Allowed Tools
Restrict which tools an agent can call

