Tool Resolution
Self-correcting tool name resolution with suggestions and opt-in strict mode
Typo a tool name and PraisonAI tells you what you meant — and, with strict mode on, refuses to start until you fix it.
from praisonaiagents import Agent
Agent(name="Search", instructions="Search the web", tools=["internet_serch"])
# WARNING Unknown tool 'internet_serch'. Did you mean 'internet_search'? Run 'praisonai tools list'.Quick Start
- Get a suggestion
python from praisonaiagents import Agent Agent(name="Assistant", instructions="Search the web", tools=["web_serch"]) # WARNING Unknown tool 'web_serch'. Did you mean 'web_search'? Run 'praisonai tools list'. - Fail fast in CI
bash export PRAISONAI_STRICT_TOOLS=true python my_agent.py # raises ToolResolutionError on any typo - Route suggestions yourself
python from praisonaiagents.tools import resolve_tool_names def my_reporter(unknown, suggestions): for name in unknown: print(f"Skipping unknown tool {name}; try {suggestions[name]}") tools = resolve_tool_names(["web_serch"], on_unknown=my_reporter)
How It Works
Every tool name passes through resolve_tool_names, which looks it up, and on a miss computes the closest known names.
The suggestion catalogue is the union of the registry (get_registry().list_tools()), the built-in TOOL_MAPPINGS keys, and praisonai_tools.__all__ when the extra is installed.
Agent.__init__ routes a list/tuple tools= through the per-element resolver, so name strings resolve regardless of whether callables are also present.
Accepted tool shapes
A tools=[...] element can now be a plain function, a callable class instance, or a BaseTool-style instance.
| Shape | Recognised by | Name used |
|---|---|---|
| Plain function | callable(tool) | .name if present, else __name__ |
| Callable class instance | callable(tool) | .name if present, else __name__ |
| BaseTool-style instance | has .name (str) and .run (callable) | .name (preferred) |
BaseTool-style instances — anything exposing a .name attribute and a .run method, such as ClarifyTool — are accepted alongside plain functions and callable classes. When a callable tool also carries a .name attribute, .name wins over __name__.
from praisonaiagents import Agent
class ClarifyTool:
name = "clarify"
def run(self, question: str) -> str:
return f"Please clarify: {question}"
Agent(name="Helper", instructions="Ask before acting", tools=[ClarifyTool()])Mixed tools lists
One tools=[...] list can freely mix callables and tool-name strings.
from praisonaiagents import Agent
def my_tool(x: str) -> str:
"""A local tool."""
return "local:" + x
# Callable + built-in name string in the same list — both work.
agent = Agent(
name="Assistant",
instructions="Use the local tool and read files",
tools=[my_tool, "read_file"],
)
agent.start("Read config.json and process it")Previously, adding a single callable to a list of names silently left every name unresolved; each name is now resolved individually.
Unknown names inside a mixed list behave exactly like an all-strings list — warned and suggested by default, or raised under strict mode.
from praisonaiagents import Agent
def my_tool(x: str) -> str:
return x
# 'no_such_tool' is unknown → warned + suggested (or raised under strict mode),
# my_tool is kept. Same contract as tools=["no_such_tool"].
Agent(name="Assistant", instructions="x", tools=[my_tool, "no_such_tool"])
# WARNING Unknown tool 'no_such_tool'. ... Run 'praisonai tools list'.Resolve a mixed list yourself
Call resolve_tools_list() to resolve a possibly-mixed list without building an agent.
from praisonaiagents.tools.resolver import resolve_tools_list
def my_tool(x: str) -> str:
return x
# Non-string entries keep their position; name strings resolve.
tools = resolve_tools_list([my_tool, "read_file"])
# strict=True raises ToolResolutionError on any unknown name; on_unknown routes suggestions.| Argument | Type | Default | Behaviour |
|---|---|---|---|
items | List[Any] | — | Mixed list of tool-name strings and tool objects. Non-strings pass through in place. |
strict | Optional[bool] | None | True raises ToolResolutionError on any unknown name; False warns and returns resolved; None reads PRAISONAI_STRICT_TOOLS. |
on_unknown | Optional[Callable] | None | Called as (unknown, suggestions) in non-strict mode instead of the default warning. |
resolve_tool_names() is unchanged and now delegates to resolve_tools_list(), so existing code is unaffected.
Choosing a Mode
Pick the behaviour that matches where your agent runs.
Configuring Strict Mode
Turn strict mode on with an environment variable, a keyword argument, or a callback.
Environment variable
export PRAISONAI_STRICT_TOOLS=true # 1 / true / yes (case-insensitive)Argument
from praisonaiagents.tools import resolve_tool_names
resolve_tool_names(["web_serch"], strict=True) # raises ToolResolutionErrorCallback
from praisonaiagents.tools import resolve_tool_names
def _on_unknown(unknown, suggestions):
raise SystemExit(f"unknown tools: {unknown}, suggestions: {suggestions}")
resolve_tool_names(["web_serch"], on_unknown=_on_unknown)The signature reads the environment variable only when strict is left unset:
def resolve_tool_names(
names: List[str],
*,
strict: Optional[bool] = None,
on_unknown: Optional[Callable[[List[str], Dict[str, List[str]]], None]] = None,
) -> List[Any]:
...| Argument | Type | Default | Behaviour |
|---|---|---|---|
names | List[str] | — | Tool name strings to resolve. |
strict | Optional[bool] | None | True raises on any unknown name; False warns and returns resolved tools; None reads PRAISONAI_STRICT_TOOLS. |
on_unknown | Optional[Callable] | None | Called as (unknown, suggestions) in non-strict mode instead of the default warning. |
| Variable | Values (case-insensitive) | Effect |
|---|---|---|
PRAISONAI_STRICT_TOOLS | 1, true, yes → strict | Anything else → non-strict. Unset means non-strict. |
The ToolResolutionError Shape
Strict mode raises ToolResolutionError, carrying the unknown names and their suggestions.
from praisonaiagents import Agent
from praisonaiagents.tools import ToolResolutionError
try:
Agent(tools=["web_serch"]) # with PRAISONAI_STRICT_TOOLS=true
except ToolResolutionError as e:
print(e.unknown) # ['web_serch']
print(e.suggestions) # {'web_serch': ['web_search']}
print(str(e)) # "Unknown tool 'web_serch'. Did you mean 'web_search'? Run 'praisonai tools list'."| Attribute | Type | Description |
|---|---|---|
unknown | list[str] | Names that could not be resolved. |
suggestions | dict[str, list[str]] | Closest known names for each unknown, up to 3 each. |
str(error) | str | Unknown tool 'web_serch'. Did you mean 'web_search'? Run 'praisonai tools list'. |
ToolResolutionError subclasses ValueError, so existing except ValueError: blocks still catch it.
Common Patterns
Break the build on a typo in CI, then keep exploration friendly locally.
# CI entrypoint — fail fast
import os
os.environ["PRAISONAI_STRICT_TOOLS"] = "true"
from praisonaiagents import Agent
Agent(name="CI", instructions="Run checks", tools=["web_search", "read_file"])Ship suggestions through your own logging or alerting for long-running services.
import logging
from praisonaiagents.tools import resolve_tool_names
log = logging.getLogger("agents")
def report(unknown, suggestions):
for name in unknown:
log.error("unknown tool %s — try %s", name, suggestions[name])
resolve_tool_names(["web_serch", "read_fil"], on_unknown=report)Best Practices
Turn on strict mode in CI
Add PRAISONAI_STRICT_TOOLS=true to your CI environment so a mistyped tool name breaks the build instead of quietly shipping a tool-less agent.
Keep warn-and-continue for exploration
Interactive exploration works better with the default — you get the “did you mean …?” hint without losing the running session.
Route suggestions through your own stack
For long-running services, on_unknown sends the suggestion through the logger or alerting your app already uses.
Install praisonai-tools for richer suggestions
The suggestion catalogue unions the registry, TOOL_MAPPINGS, and praisonai_tools.__all__. Installing praisonai-tools broadens the pool suggestions come from.
Gateway operators: use the start-time tool pre-flight
PRAISONAI_STRICT_TOOLS here is the core resolver gate — it raises ToolResolutionError when Agent.__init__ builds tools. The gateway adds a complementary wrapper-level gate one layer up: praisonai gateway start --strict-tools (default on) validates every tool named in gateway.yaml before the gateway binds, failing fast with exit 78. It uses the wrapper resolver (praisonai_code/tool_resolver.py), not this core resolver. See Pre-flight tool check.
Related
Restrict which tools an agent can call.
Tool ResolverHow tool names map to callables across sources.
Tool Call Self-RepairRuntime auto-fix when the LLM emits a drifted tool name during a run.
Gateway Pre-flight Tool CheckWrapper-level --strict-tools gate — fail fast at gateway start (exit 78).