Tools and client tools
Tools that run within the session, and client tools, answered through a callback.
Guide: Tools.
| Name | What it is |
|---|---|
agents.tool | Decorate a module-level function as an SDK tool, a PythonTool. |
PythonTool | A tool that runs a Python function where the agent runs. |
ToolDefinition | The description of a tool: what the model sees of it (name, description, input schema) and what a policy can read about it (title, annotations, source). |
ToolAnnotations | Hints about what a tool does, as the Model Context Protocol (MCP) defines them, by MCP's names as aliases: readOnlyHint for read_only_hint. |
ToolResult | What a tool returns: content for the model, structured output, and metadata for the client. |
ToolError | Fail the tool call with a message the model sees as written. |
ToolModelAccess | How the model calls a tool: "programmatic" from run_typescript, "direct" from its tool list, or "both". |
agents.client_tool | Declare a tool the model can call, answered through a callback, not within the session. |
ClientTool | A tool the model can call, answered through a callback, not within the session. |
ClientToolAnswer | The function that answers one client tool's calls, bound with @<tool>.answer. |
ClientToolAnswers | Your functions that answer client tools, one per tool, and a default for every other call. |
ClientToolCall | One call of a client tool, as on_client_tool receives it and OpenClientToolCall.call holds it. |
Tools
agents.toolagents.tool
def agents.tool(
handler: Callable[..., Any] | None = None,
/,
*,
name: str | None = None,
namespace: str = 'client',
description: str | None = None,
input_schema: type[BaseModel] | None = None,
output_schema: type[BaseModel] | None = None,
model_access: ToolModelAccess | None = None,
annotations: ToolAnnotations | None = None,
title: str | None = None,
) -> AnyDecorate a module-level function as an SDK tool, a PythonTool.
Use it bare, @agents.tool, to take the name from the function, the description from its
docstring, the input schema from its parameters and the output schema from its return
annotation, or call it to override any of them. The tool stays callable like the function.
annotations=agents.ToolAnnotations(read_only_hint=True) declares hints that every approval
of a call to the tool carries. title="Send email" is a name for people, for example in an
approval prompt; the model does not see it.
namespace groups the tool: its ID is <namespace>.<name>, client.<name> by default.
The ID is what ToolCall.name and permission rules use, and what run_typescript calls
(tools.<namespace>.<name>(...)). A namespace is letters, digits, _ or $, not
starting with a digit. self, file_system, skill, process, agents,
subagent, connectors, guardrail and any name starting with connector_ raise
SdkError: the agent's built-in tools and the SDK's connector and guardrail tools use them.
client is the default namespace of every tool your code declares, whether it runs within the
session or is answered through a callback.
The function may take an agents.ToolCallbackContext as its first, positional-only parameter:
def search(ctx: agents.ToolCallbackContext, /, query: str). The SDK fills it for each call.
When the agent runs the tool, an exception the function raises follows Agent(tool_errors=...):
by default the model reads The tool failed with an unexpected error., and your logs get the
traceback. Raise agents.ToolError("...") for a failure the model should read. A direct call
raises the exception as the function does.
| Parameter | Type | Default | Description |
|---|---|---|---|
handler | Callable[..., Any] | None | None | The function the tool runs. Without it, tool(...) returns a decorator. |
name | str | None | None | The name the model calls the tool by. Defaults to the function's name. |
namespace | str | 'client' | The tool's group: its ID is <namespace>.<name>. |
description | str | None | None | What the model reads about the tool. Defaults to the function's docstring. |
input_schema | type[BaseModel] | None | None | A Pydantic model for the arguments, which the function then receives as args. Defaults to a schema built from the function's parameters. |
output_schema | type[BaseModel] | None | None | A Pydantic model the result must match. Defaults to a schema built from the return annotation. |
model_access | ToolModelAccess | None | None | How the model calls the tool, an agents.ToolModelAccess: "programmatic" from run_typescript, "direct" from its tool list, or "both". Defaults to "programmatic", or "direct" when the agent turns programmatic tool calling off with Builtins and programmatic_tool_calling=False. Such an agent rejects an explicit "programmatic". |
annotations | ToolAnnotations | None | None | Hints about what the tool does, such as read_only_hint, which every approval of a call to the tool carries. |
title | str | None | None | A name for people, such as "Send email"; the model does not see it. |
PythonToolPythonTool
class PythonTool[P, R]A tool that runs a Python function where the agent runs.
It is callable like the function itself: tool(...) takes the function's parameters and
returns what the function returns, nothing added. tool_definition describes the tool: what the
model sees, and its title for people.
name defaults to the function's name and description to its docstring. Without
input_schema, the input schema is built from the function's parameters and the function
receives each argument by name; with it, the function receives the validated model as args.
The output schema is built from the return annotation, unless output_schema is given.
Both arguments are Pydantic models. annotations are the tool's hints, such as
read_only_hint: every approval of a call to the tool carries them, in
request.tool.annotations. title is a name for people, such as "Send email", for
example in an approval prompt. A tool cannot be changed after it is built.
A function whose first parameter is a positional-only agents.ToolCallbackContext gets the
context of each call. Called directly, such a tool takes the context or leaves it out:
search(ctx, "q") and search("q") both work, the second with ToolCallbackContext.local().
Each field of tool_definition can also be read on the tool: tool.name is
tool.tool_definition.name, and tool.input_schema is the JSON Schema, not the Pydantic class.
| Attribute | Type | Description |
|---|---|---|
tool_definition | ToolDefinition | What the model sees of the tool: its name, description, input schema and output schema; and its title, for people only. |
namespace | str | The tool's group: its ID is <namespace>.<name>, as in ToolCall.name and permission rules. |
session_inputs | dict[str, type[SessionInput]] | The session inputs the function declares with Annotated[T, agents.Depends()], by parameter name. |
name | str | tool_definition.name: the name the model calls the tool by. |
title | str | None | tool_definition.title: a name for people, for example in an approval prompt. |
display_title | str | None | tool_definition.display_title: the name to show people, as on an MCP or connector tool. |
description | str | tool_definition.description: what the tool does, as the model reads it. |
input_schema | dict[str, JsonValue] | tool_definition.input_schema: the JSON Schema of the arguments, built from the function's parameters. |
output_schema | dict[str, JsonValue] | None | tool_definition.output_schema: the JSON Schema of the result, or None. |
annotations | ToolAnnotations | tool_definition.annotations: hints about what the tool does, for a policy to read. |
source | str | None | tool_definition.source: None, as the agent defines the tool in its own code. |
PythonTool.invokePythonTool.invoke
async def invoke(context: ToolCallbackContext | None = None, /, **kwargs: Any) -> AnyCall the tool with the model's arguments, as the agent does.
context reaches a function that takes a ToolCallbackContext. Without it, that function
gets ToolCallbackContext.local(). A tool whose function takes no context ignores it.
A tool that declares session inputs also takes their values as
injected_session_inputs={input_id: value}, and raises ValueError without it.
ToolDefinitionToolDefinition
class ToolDefinition(
*,
name: str,
title: str | None = None,
description: str = '',
input_schema: dict[str, JsonValue] = {'type': 'object', 'properties': {}},
output_schema: dict[str, JsonValue] | None = None,
annotations: ToolAnnotations = ToolAnnotations(),
source: str | None = None,
icons: list[dict[str, JsonValue]] | None = None,
meta: dict[str, JsonValue] | None = None,
execution: dict[str, JsonValue] | None = None,
)The description of a tool: what the model sees of it (name, description, input schema) and what a policy can read about it (title, annotations, source).
It describes any tool, whether your Python function runs it (a PythonTool holds one) or a
connector or an MCP server does. Its fields are the Model Context Protocol's (MCP) Tool
fields, in snake case, plus source.
| Attribute | Type | Description |
|---|---|---|
display_title | str | None | The tool's name for people, for example in an approval prompt: title, else the annotations' title, which some servers list instead. None when it has neither. |
ToolAnnotationsToolAnnotations
class ToolAnnotations(
*,
read_only_hint: bool = False,
destructive_hint: bool = True,
idempotent_hint: bool = False,
open_world_hint: bool = True,
)Hints about what a tool does, as the Model Context Protocol (MCP) defines them, by MCP's
names as aliases: readOnlyHint for read_only_hint.
A hint the tool does not declare takes MCP's default, which assumes the worst: the tool writes, may destroy data, is not safe to repeat, and reaches systems outside the agent's own. A policy that trusts only declared hints therefore refuses a tool that declares none. They are hints: rely on them only for tools from sources you trust.
ToolResultToolResult
class ToolResult[T](
*,
content: Sequence[ContentBlock | mcp.types.ContentBlock | Mapping[str, Any]] = [],
structured_content: object = None,
is_error: bool = False,
meta: Mapping[str, JsonValue] = {},
)What a tool returns: content for the model, structured output, and metadata for the client.
A tool annotated -> ToolResult[T] gets T's JSON Schema as its output schema.
ToolResult[T] does not type structured_content: the tool's output schema checks it when
the tool returns.
ToolResult.errorToolResult.error
@classmethod
def error(
message: str,
*,
structured_content: JsonValue | BaseModel = None,
meta: Mapping[str, JsonValue] | None = None,
) -> SelfA tool execution error the model sees as message and can act on.
ToolErrorToolError
class ToolError(Exception)Fail the tool call with a message the model sees as written.
Raise it in a tool, a client tool's answer or on_client_tool. The model gets a failed call
with message only, whatever Agent(tool_errors=...) says, and can act on it, for example
by asking the user or calling the tool again with other arguments. structured_content and
meta go with the failed result as they do with ToolResult; the model never sees meta.
ToolResult.error(message), returned instead, does the same. Both check structured_content
and meta where they are built: a value that is not JSON raises pydantic.ValidationError
there, in your code.
| Attribute | Type | Description |
|---|---|---|
message | str | What the model sees. |
structured_content | JsonValue | BaseModel | Machine-readable output that goes with the failed result, as in ToolResult. |
meta | dict[str, JsonValue] | Metadata for the client, as MCP's _meta. Never shown to the model. |
ToolModelAccessToolModelAccess
type ToolModelAccess = Literal['direct', 'programmatic', 'both']How the model calls a tool: "programmatic" from run_typescript, "direct" from its tool list, or "both".
With "programmatic", the model calls the tool from code it writes, as tools.<namespace>.<name>(...), so it can chain several calls in one step and loop over their results. "direct" suits a tool the model calls once per turn.
Client tools
agents.client_toolagents.client_tool
def agents.client_tool(
function: Callable[..., Any] | None = None,
/,
*,
name: str | None = None,
namespace: str = 'client',
description: str | None = None,
input_schema: type[BaseModel] | None = None,
output_schema: type[BaseModel] | None = None,
model_access: ToolModelAccess | None = None,
annotations: ToolAnnotations | None = None,
title: str | None = None,
) -> AnyDeclare a tool the model can call, answered through a callback, not within the session.
Put @agents.client_tool on a stub whose body is raise NotImplementedError: the name
comes from the function, the description from its docstring, the input schema from its
parameters and the output schema from its return annotation. Call it with name= or
description= to override them. The function never runs: it only describes the tool, and
its answer takes the same parameters. A stub that takes an agents.ToolCallbackContext or a
session input raises SdkError. Take the context in the answer, and read session inputs in
your application.
Or declare the tool without a function, for example from a JSON contract: pass name=,
description=, input_schema= and output_schema=, Pydantic models. The answer then
receives the validated model. Without output_schema=, the model gets no output schema and
the answer's return is not typed, so the SDK warns with a UserWarning.
namespace groups the tool: its ID is <namespace>.<name>, client.<name> by default,
as ClientToolCall.name and ToolCall.name give it. A namespace is letters, digits, _ or
$, not starting with a digit. self, file_system, skill, process, agents,
subagent, connectors, guardrail and any name starting with connector_ raise
SdkError: the agent's built-in tools and the SDK's connector and guardrail tools use them.
client is the default namespace of every tool your code declares, whether it runs within the
session or is answered through a callback.
annotations are the tool's hints, which every approval of a call to the tool carries in
request.tool.annotations. title is a name for people, for example in an approval prompt.
A client tool is answered through a callback, not within the session: the turn waits for the answer.
Answer it with a function bound by @<tool>.answer, or by hand with
turn.answer(callback.id, result) when a run returns Blocked.
| Parameter | Type | Default | Description |
|---|---|---|---|
function | Callable[..., Any] | None | None | The stub that describes the tool; it never runs. Without it, pass name=, description=, input_schema= and output_schema=, or use client_tool(...) as a decorator. |
name | str | None | None | The name the model calls the tool by. Defaults to the function's name. |
namespace | str | 'client' | The tool's group: its ID is <namespace>.<name>. |
description | str | None | None | What the model reads about the tool. Defaults to the function's docstring. |
input_schema | type[BaseModel] | None | None | A Pydantic model for the arguments, for a tool declared without a function. |
output_schema | type[BaseModel] | None | None | A Pydantic model the result must match, for a tool declared without a function. |
model_access | ToolModelAccess | None | None | How the model calls the tool, an agents.ToolModelAccess: "programmatic" from run_typescript, "direct" from its tool list, or "both". Defaults to "programmatic", or "direct" when the agent turns programmatic tool calling off with Builtins and programmatic_tool_calling=False. Such an agent rejects an explicit "programmatic". |
annotations | ToolAnnotations | None | None | Hints about what the tool does, which every approval of a call to the tool carries. |
title | str | None | None | A name for people, for example in an approval prompt; the model does not see it. |
ClientToolClientTool
class ClientTool[P, R]A tool the model can call, answered through a callback, not within the session.
The agent holds only its declaration: no code runs for it where the agent runs. When the model
calls it, the turn waits for the callback's answer. Bind the function that answers
it with @<tool>.answer, and pass that function to Agent.run in
on_client_tool=agents.ClientToolAnswers(...).
Build one with @agents.client_tool on a stub function, or with
agents.client_tool(name=..., description=..., input_schema=...).
| Attribute | Type | Description |
|---|---|---|
tool_definition | ToolDefinition | What the model sees of the tool: its name, description, and the JSON Schemas of its input and output, as on a PythonTool; and its title, for people only. |
name | str | The name the model calls the tool by. |
display_title | str | None | tool_definition.display_title: the name to show people, as on an MCP or connector tool. |
namespace | str | The tool's group: its ID is <namespace>.<name>, as ClientToolCall.name gives it. |
ClientTool.answerClientTool.answer
def answer(handler: Callable[..., Any], /) -> ClientToolAnswer[P, R]Bind the function that answers this tool's calls, in the process that runs the agent.
It takes the tool's parameters and returns its result, synchronously or not: a value of
the stub's return type, or an agents.ToolResult to choose what the model sees. It
returns agents.KeepCallbackOpen() to leave the call open: nothing is sent, and a run returns
Blocked on the call, which turn.answer or another client answers. Pass
what this returns to Agent.run in on_client_tool=agents.ClientToolAnswers(...).
It may take an agents.ToolCallbackContext first, positional-only, as a tool does:
def answer(ctx: agents.ToolCallbackContext, /, city: str). It gets the session and the
call's ID there; its sandbox is unbound.
Its other parameters are not checked here. An answer whose parameter names or kinds do not
match fails at its first call, as a failed tool call the model sees. An answer that only
types a parameter differently is still called, with the value the declaration validated.
ClientTool.calledClientTool.called
def called(callback: OpenCallback, /) -> TypeIs[OpenClientToolCall[P, R]]Whether callback is an open call of this tool: an OpenClientToolCall whose
call.name is this tool's ID, <namespace>.<name>.
A type checker then types callback.run(fn) by this stub: fn takes the stub's
parameters and returns its return type:
for callback in state.callbacks:
if get_location.called(callback):
await turn.answer(callback.id, await callback.run(read_gps))ClientToolAnswerClientToolAnswer
class ClientToolAnswer[P, R]The function that answers one client tool's calls, bound with @<tool>.answer.
| Attribute | Type | Description |
|---|---|---|
tool | ClientTool[P, R] | The client tool it answers. |
handler | Callable[..., Any] | The function that answers the tool's calls. |
ClientToolAnswersClientToolAnswers
class ClientToolAnswers(
*answers: ClientToolAnswer[..., Any],
default: Callable[[ClientToolCall], Any] | None = None,
)Your functions that answer client tools, one per tool, and a default for every other call.
Pass it as on_client_tool= to agent.run, agent.start_session or agent.session(id):
answers = agents.ClientToolAnswers(get_location.answer(read_gps), default=handle)
state = await agent.run("Find a shop near me", on_client_tool=answers)Each answer comes from <tool>.answer(function), which a type checker checks against the
tool's stub. default gets the calls of every other client tool, as a plain
on_client_tool= handler does: a tool's own answer wins over it.
A plain function runs in a separate thread, so a blocking call such as input() does not stop
the session. An async def one runs on the event loop: do not block in it. The answers also
get the client tool calls of the session's subagents.
Raises SdkError here when two answers are for the same tool. Before the session starts, the
agent raises SdkError for an answer to a stub that is not one of its tools or its subagents'.
A deployed agent loaded with Agent.load_from skips that check: it has no tool objects.
| Parameter | Type | Default | Description |
|---|---|---|---|
*answers | ClientToolAnswer[..., Any] | () | The answers, each from <tool>.answer(function), one per tool. |
default | Callable[[ClientToolCall], Any] | None | None | The handler of every other client tool call. None leaves those calls open: the run returns Blocked. |
ClientToolCallClientToolCall
class ClientToolCall(name: str, arguments: JsonValue)One call of a client tool, as on_client_tool receives it and OpenClientToolCall.call holds it.
name is the tool's ID, its namespace then the name it was declared with, such as
client.get_weather (client is the default namespace) or ui.get_location: the same
as agents.ToolCall.name in Completed.tool_calls.
arguments holds the call's arguments, not validated: the model's, or the ones a
pre_tool_call hook set. The handler checks them.
A tool with its own answer in agents.ClientToolAnswers never reaches the default handler:
its answer wins.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | required | The tool's ID, its namespace then the name it was declared with, such as client.get_weather (client is the default namespace) or ui.get_location: the same as agents.ToolCall.name in Completed.tool_calls. |
arguments | JsonValue | required | The call's arguments, not validated: the model's, or the ones a pre_tool_call hook set. The handler checks them. |