Tools¶
A tool is a callable plus what an LLM needs to call it: a name, a description and an
argument schema. In code it is a ToolSpec. You never build one by hand.
Two ways in, same result¶
@registry.tool |
registry.wrap(func) |
|
|---|---|---|
| Use for | code you own | code you cannot or will not edit; bound methods |
| Options live | on the decorator | at the call site |
| Returns | the function, unchanged | the ToolSpec |
@registry.tool(kind="retrieval", tags=["search", "public"], prompt_hint="First step for any query.")
def search(query: str, top_k: int = 8) -> list[str]:
"""Search articles.
Args:
query: Search text.
top_k: How many hits.
"""
return [query] * top_k
@registry.tool(kind="action", tags="admin", confirm=True)
def delete_note(note_id: str) -> bool:
"""Delete a saved note.
Args:
note_id: The note to delete.
"""
return True
@registry.tool also works without parentheses. agent_adapter.tool and
agent_adapter.wrap register into agent_adapter.default_registry.
See Wrap existing code for methods, partials and callable objects.
Where the schema comes from¶
| Source | Becomes |
|---|---|
function name (or name=) |
tool name: [A-Za-z_][A-Za-z0-9_]*, max 64 |
docstring text before the first section (or description=) |
tool description; required |
Google-style Args: section |
per-argument descriptions |
Annotated[T, Field(...)] or = Field(...) |
description, constraints, default |
| type hints | argument types |
| defaults | optional arguments |
| return annotation | spec.returns, spec.result_schema() |
A Field(description=...) beats the docstring for that argument.
Rejected at build time¶
build_spec raises ToolDefinitionError for:
*args,**kwargs, positional-only or unannotated parameters;alias=on a parameter (userename=);- no description and no docstring;
- more than one
ToolContextparameter; - an LLM-facing argument name that does not match
[A-Za-z_][A-Za-z0-9_]*(max 64), starts with_, or clashes with a pydanticBaseModelattribute (schema,json,copy,model_*, ...). Hide or rename such parameters.
Shaping options¶
| Option | Effect |
|---|---|
fixed={"lang": "en"} |
bound value, hidden from the LLM |
hide=["debug"] or hide="debug" |
hidden; the plain default applies |
rename={"article_list": "articles"} |
the LLM sees articles; the function gets article_list |
result=to_hits |
transforms the return value; result_schema() becomes None |
name=, description= |
override the function name and docstring |
spec.llm_to_python holds the reverse rename map: {"articles": "article_list"}.
Policy options¶
Stored on the ToolSpec and type-checked at build time. A wrong type or an unknown
option raises ToolDefinitionError. The Executor enforces the execution
options on every call; spec.invoke ignores them.
| Option | Type | Acted on by |
|---|---|---|
kind |
non-empty str (default "tool") |
registry.subset(kinds=); executor RunLimits.per_kind caps |
tags |
one non-empty str, or an iterable of them |
registry.subset(tags=) |
prompt_hint |
str or None |
registry.catalogue() |
portable |
bool (default True) |
registration |
parallel_safe, once_per_run |
bool |
executor: run lock, run guard |
confirm |
bool or (call, ctx) -> bool (sync or async) |
executor → approval port |
timeout, cache_ttl |
finite positive number (seconds) or None |
executor and its tool cache |
retries |
int >= 0 (extra attempts) |
executor |
cache_scope |
"run" (default), "thread" or "global" |
who shares a cached result (executor) |
targets |
one non-empty str, or an iterable of them; default None = every target |
renderers and exporters (Registry) |
effects |
"read_only", "additive", "destructive" or None |
MCP tool annotations |
idempotent |
bool or None |
MCP tool annotations |
Bring your own argument model¶
When the signature cannot describe the arguments well, pass args_model= a pydantic
model. Its field names must equal the LLM-facing parameter names (after fixed, hide,
rename and the context parameter are removed); it must use extra="forbid" and no
aliases. The model's types and descriptions are what validation and the schema use.
Parameter annotations are still required on the function.
class LookupArgs(BaseModel):
model_config = ConfigDict(extra="forbid")
article: str = Field(description="Article topic, e.g. returns.")
def legacy_lookup(article: str, verbose: bool = False) -> str:
"""Look up an article in the legacy system."""
return f"Article {article}"
registry.wrap(legacy_lookup, args_model=LookupArgs, hide="verbose", targets=["anthropic"])
A mismatch raises ToolDefinitionError naming the missing and extra fields.
Tools without a signature¶
spec_from_schema(name, description, input_schema, func, ...) builds a tool from a raw
JSON schema, for tools imported from MCP, LangChain or OpenAPI. See
Schemas.
Context¶
A parameter annotated ToolContext is injected at call time and never appears in the
schema. A subclass, or ToolContext | None, works too. Only one is allowed. If you pass
no ctx, the tool gets an empty ToolContext(). Fields, emit and child are on
Context.
Calling a tool¶
In an application, and for every tool call a model sends, call tools through the Executor so approval, budgets, guards and the other policy options apply. The spec methods run the function alone: use them only in unit tests.
executor = Executor(registry) # in an application: every policy option applies
hits = await executor.run("search", {"query": "returns"}, ctx)
spec = registry.get("search") # in a unit test: the function alone, no policies
alone = await spec.invoke({"query": "returns"}, ctx)
validated = spec.validate_args({"query": "returns", "top_k": 2}) # validate now...
later = await spec.call(validated, ctx) # ...run later
| Method | Does | Raises |
|---|---|---|
validate_args(args) |
strict nulls → defaults, then validate | ToolArgsInvalid |
call(validated, ctx) |
run the function; sync ones in a worker thread | ToolUsageError for another tool's model |
invoke(args, ctx) |
both | both |
json_schema() |
provider-neutral argument schema (a fresh copy; input_schema when set) |
— |
result_schema() |
output_schema, else the return annotation's schema, or None |
— |
tool_def(strict=True) |
the tool as a ToolDef for model ports |
— |
Unknown arguments are rejected. ToolSpec compares and hashes by identity.