Tool calls¶
This page is the rules for what happens between the model asking for a tool and getting its result. To write your first tool, see Give the agent tools.
What the model sees¶
A tool is something the model can call. @tool makes one from a function:
from pathlib import Path
from alpineagents import ToolError, tool
@tool
def read_file(path: str, max_lines: int = 200) -> str:
"""Read a text file
Args:
path: Path relative to the repository root
max_lines: How many lines to return
"""
file = Path(path)
if not file.exists():
raise ToolError(f"No such file: {path}")
return "\n".join(file.read_text().splitlines()[:max_lines])
print(read_file.spec)
| From the function | The model sees |
|---|---|
| Function name | Tool name: read_file |
| First paragraph of the docstring | Tool description |
| Type hints | Input schema (JSON Schema) |
Args: section of the docstring |
Description of each parameter |
| Default values | Optional parameters |
- Every parameter needs a type hint. Supported:
str,int,float,bool,Literal[...],Enum,list[T],dict[str, T],T | None, dataclass,TypedDict, Pydantic model. On Python 3.11, importTypedDictfromtyping_extensions. - Options:
@tool(name="...", description="...", parallel=False, exception_handler=...), and the hintsread_only=,destructive=,idempotent=andopen_world=. - The hints say what a tool does, for your code. The model does not see them. They are explained, with
hints_for, in Write permission rules. - A mistake in the function raises
TypeErrorwhen@toolruns, before any model request. - Other kinds of tools are in Tools on objects and from data:
@toolmethods of an object,Toolsubclasses, and MCP servers (Use MCP servers). An Agent intools=is a tool the model calls to run that Agent: see Subagents.
Return values¶
| The tool returns | The model gets |
|---|---|
str |
The string |
None |
(done) |
Image |
The image |
A list with an Image in it |
Its text and images, in order |
| Anything else | JSON |
A value that cannot become JSON raises TypeError, which stops the run like any exception from a tool.
The result the model gets is a tuple of blocks, such as (TextBlock("..."), Image(image/png, 34.2KB)). It is the
content of the call's ToolResultBlock and ToolResultEntry, and the result a Reporter's on_tool_end gets.
result_text(result) from alpineagents.types turns it into one line of text, with each image as
(image/png, 34.2KB). Images are explained in Tools on objects and from data.
When a call goes wrong¶
| Situation | What happens | The run |
|---|---|---|
| The model sends invalid arguments or an unknown tool name | The model gets (input error: ...) as the result |
Continues |
The tool raises ToolInputError("...") |
The model gets (input error: ...) as the result |
Continues |
The tool raises ToolError("...") |
The model gets the message as an error result | Continues |
The tool raises an exception its exception_handler takes |
The model gets the handler's message as an error result | Continues |
| The tool raises any other exception | The exception propagates out of use_tools and run |
Stops |
| A permission refuses the call | The call does not run, and the model gets the reason as an error result | Continues, unless the permission stopped it |
An exception stops the run because it is usually a bug, and the model cannot fix your code. For a failure the model
can do something about, such as a missing file or an HTTP 404, raise ToolError, or name the exceptions in an
exception_handler. Let the model handle tool failures shows both.
- The type hint of the handler's one parameter says which exceptions it takes.
Exceptiontakes every exception. To let one exception stop the run anyway, raise it in the handler. tool.copy(exception_handler=None)gives a tool without the handler. The Agent itself does not know about handlers.
An error result shows up in three places:
- in the terminal as
error read_file: ..., - as
outcome.kind == "error"in a Reporter, - as a
ToolResultEntrywithoutcome == "error"instate.history(is_erroris true). The entry'serrorholds theToolError, and its__cause__holds the exception the handler took. A store does not saveerror; the entry'scontentkeeps the text.
Several calls in one reply¶
A reply can ask for several tool calls. use_tools runs them like this:
- Tools with
parallel=True(the default) run at the same time, on worker threads. - Then tools with
parallel=Falserun one at a time, in the order the model asked. - The results go into
state.messagesin the order the model asked, together, once the last call has one.
Use parallel=False for tools that must not overlap, such as two writes to the same file.
The State parameter¶
A parameter typed State is hidden from the model and receives the current State:
from alpineagents import State, tool
@tool
def submit(summary: str, state: State) -> None:
"""Submit the finished work"""
state.finish(summary)
A tool can read state.messages, state.extra_data and the rest, and change the State through its methods:
state.finish(...), state.add_message(...) and state.edit_extra_data(). See
Let tools use the State.