Tools on objects and from data¶
Give the agent tools showed @tool on a function. This page covers the other ways to make a tool.
Tools on an object¶
@tool works on methods. Pass the object to give the Agent every @tool method, or pass one method:
from pathlib import Path
from alpineagents import Agent, tool
class Workspace:
def __init__(self, root: str):
self.root = Path(root)
@tool
def read_file(self, path: str) -> str:
"""Read a file in the workspace"""
return (self.root / path).read_text()
@tool
def list_files(self) -> list[str]:
"""List the files in the workspace"""
return sorted(p.name for p in self.root.iterdir())
workspace = Workspace("my-repo")
agent = Agent(model="claude-sonnet-5", tools=[workspace])
reader = Agent(model="claude-sonnet-5", tools=[workspace.read_file])
workspaceintools=gives the modelread_fileandlist_files.workspace.read_filegives it only that one.- The model does not see
self.
Use an object when tools share settings, such as a root folder or a client.
Tools from data¶
@tool needs a Python function with type hints. For a tool whose name, description and input schema come as data,
such as rows in a database or an OpenAPI spec, subclass Tool:
import httpx
from alpineagents import Agent, State, Tool, ToolError, ToolInputError
rows = [ # for example from a database or an OpenAPI spec
{
"name": "create_ticket",
"description": "Create a support ticket",
"schema": {
"type": "object",
"properties": {"title": {"type": "string", "description": "A short title"}},
"required": ["title"],
},
"url": "https://example.com/hooks/tickets",
},
]
class Webhook(Tool):
def __init__(self, name: str, description: str, schema: dict, url: str):
super().__init__(name=name, description=description, input_schema=schema, open_world=True)
self.url = url
def run(self, args: dict, state: State) -> dict:
if "title" not in args:
raise ToolInputError("title is required")
response = httpx.post(self.url, json=args)
if response.status_code >= 400:
raise ToolError(f"HTTP {response.status_code}")
return response.json()
agent = Agent(model="claude-sonnet-5", tools=[Webhook(**row) for row in rows])
print(agent.run("Open a ticket: the login page is down"))
super().__init__takesname,description,input_schema(an object schema, left out for no arguments),paralleland the hints.run(args, state)gets the model's arguments as a dict. They are not checked againstinput_schema, so check what you rely on and raiseToolInputErrorwhen it is wrong.- What
runreturns and raises works as for@tool: see Tool calls.runcan beasync def.
@tool makes a FunctionTool and an MCP server's tool is an MCPTool. Both are Tool subclasses.
Images from a tool¶
Return an Image for a tool whose result the model should look at, such as a screenshot or a chart. Put it in a list
to send text with it:
from alpineagents import Agent, Image, tool
@tool
def show_chart(path: str) -> list:
"""Look at a chart image"""
return [f"The chart in {path}", Image.from_path(path)]
agent = Agent(model="claude-sonnet-5", tools=[show_chart])
print(agent.run("What does sales.png show?"))
Image(data)takes the file's bytes.Image.from_path("chart.png")reads a file, andImage.from_base64(text)decodes base64 text. PNG, JPEG, GIF and WebP work. The type is read from the bytes, or passmedia_type="image/png".- In the list, a
stris text, and anything else that is not anImageis sent as JSON text. - The model must read images.
Anthropictakes them in the tool result.OpenAICompatiblesends them in a user message right after it. - An image counts as 1,600 tokens, so
state.clear_tool_results()clears old ones with the text results (Keep the context small). History and a store keep them. - A Reporter gets the result as a tuple of blocks.
result_text(result)fromalpineagents.typesmakes it one line. - To send an image from the person, see Keep the conversation.
Tools that must not overlap¶
A reply can ask for several calls. Tools with parallel=True (the default) run at the same time. Then tools with
parallel=False run one at a time, in the order the model asked. Use parallel=False for two writes to the same
file:
from alpineagents import tool
@tool(parallel=False)
def append_line(path: str, line: str) -> None:
"""Add a line to the end of a file"""
with open(path, "a") as file:
file.write(line + "\n")
Test a tool alone¶
A @tool function runs as a plain function: read_file("missing.py") raises its ToolError. A Tool subclass has
run(args, state): Webhook(**row).run({"title": "x"}, State()). To test a tool inside a run, see
Test your agent.