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.

python
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'.
Diagram

Tool Resolution

Yes

No

No

Yes

🤖 Agent tools

🔎 resolve_tool_names

✅ Known?

🛠️ Resolved Tools

💡 Closest Names

⚙️ Strict?

⚠️ Warn + Suggest

⛔ ToolResolutionError

Quick Start

  1. 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'.
  2. Fail fast in CI
    bash
    export PRAISONAI_STRICT_TOOLS=true
    python my_agent.py   # raises ToolResolutionError on any typo
  3. 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.

Diagram
registry / TOOL_MAPPINGS / praisonai_toolsresolve_tool_namesAgent.__init__Userregistry / TOOL_MAPPINGS / praisonai_toolsresolve_tool_namesAgent.__init__Useralt[strict mode][non-strict]Agent(tools=[“web_serch”])resolve_tool_names([…])lookup each namemiss for ‘web_serch’difflib closest namesraise ToolResolutionErrorwarn + suggest, return resolvedAgent ready (or error)

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.

ShapeRecognised byName used
Plain functioncallable(tool).name if present, else __name__
Callable class instancecallable(tool).name if present, else __name__
BaseTool-style instancehas .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__.

python
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.

python
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.

Diagram

Mixed tools list

callable / instance

known name

unknown name

🧰 tools=[my_tool, ‘read_file’, ‘oops’]

Per element

✅ Pass through in place

🛠️ Resolve to tool

⚠️ Warn + suggest / ⛔ strict raise

📦 Final tools list

Unknown names inside a mixed list behave exactly like an all-strings list — warned and suggested by default, or raised under strict mode.

python
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.

python
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.
ArgumentTypeDefaultBehaviour
itemsList[Any]—Mixed list of tool-name strings and tool objects. Non-strings pass through in place.
strictOptional[bool]NoneTrue raises ToolResolutionError on any unknown name; False warns and returns resolved; None reads PRAISONAI_STRICT_TOOLS.
on_unknownOptional[Callable]NoneCalled 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.

Diagram

Notebook / prototype

CI / production

Custom UX / alerting

🤖 Unknown tool name

Where does it run?

⚠️ Default: warn + suggest

⛔ strict=True or env var

🔧 on_unknown callback


Configuring Strict Mode

Turn strict mode on with an environment variable, a keyword argument, or a callback.

Environment variable

bash
export PRAISONAI_STRICT_TOOLS=true   # 1 / true / yes (case-insensitive)

Argument

python
from praisonaiagents.tools import resolve_tool_names

resolve_tool_names(["web_serch"], strict=True)   # raises ToolResolutionError

Callback

python
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:

python
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]:
    ...
ArgumentTypeDefaultBehaviour
namesList[str]—Tool name strings to resolve.
strictOptional[bool]NoneTrue raises on any unknown name; False warns and returns resolved tools; None reads PRAISONAI_STRICT_TOOLS.
on_unknownOptional[Callable]NoneCalled as (unknown, suggestions) in non-strict mode instead of the default warning.
VariableValues (case-insensitive)Effect
PRAISONAI_STRICT_TOOLS1, true, yes → strictAnything else → non-strict. Unset means non-strict.

The ToolResolutionError Shape

Strict mode raises ToolResolutionError, carrying the unknown names and their suggestions.

python
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'."
AttributeTypeDescription
unknownlist[str]Names that could not be resolved.
suggestionsdict[str, list[str]]Closest known names for each unknown, up to 3 each.
str(error)strUnknown 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.

python
# 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.

python
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