Tools
Tools give your agent the ability to take actions — search the web, query a database, call an API, or anything you can write a function for.
Python — @tool decorator
The @tool decorator turns a function into a tool. It uses the function name, docstring, and type hints to generate the JSON Schema automatically.
from papayya import tool
@tool
def search_web(query: str, max_results: int = 10) -> str:
"""Search the web for information."""
return call_search_api(query, max_results)
@tool
def get_weather(location: str) -> dict:
"""Look up current weather for a location."""
return {"temp_f": 72, "conditions": f"Sunny in {location}"}How it works
- Name → function name (
search_web) - Description → docstring (
"Search the web for information.") - Parameters → inferred from type hints (
query: str→{"type": "string"}) - Required → parameters without defaults are required
Pydantic support
For complex inputs, use a Pydantic model as the single parameter:
from pydantic import BaseModel, Field
class SearchInput(BaseModel):
query: str = Field(description="Search query")
max_results: int = Field(default=10, description="Max results to return")
language: str = Field(default="en", description="Language code")
@tool
def search_web(input: SearchInput) -> str:
"""Search the web with advanced options."""
return call_search_api(input.query, input.max_results, input.language)The Pydantic model's model_json_schema() generates a richer JSON Schema with field descriptions, defaults, and validation. The raw dict from the LLM is automatically validated and coerced into the Pydantic model before your function is called.
Using tools with an Agent
from papayya import Agent
agent = Agent(
name="my-agent",
model="claude-sonnet-4-20250514", # display label only — your code picks the provider
instructions="You help with research.",
tools=[search_web, get_weather],
)Your tool functions are invoked from inside your own LLM-calling code. Papayya does not execute tools on your behalf — it checkpoints the code that does.
TypeScript — defineTool()
import { defineTool, collectTools } from "papayya";
const searchWeb = defineTool(
{
name: "search_web",
description: "Search the web for information",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
maxResults: { type: "number", description: "Max results" },
},
required: ["query"],
},
},
async (input) => {
const { query, maxResults } = input as { query: string; maxResults?: number };
return callSearchAPI(query, maxResults ?? 10);
},
);Options
| Field | Type | Description |
|---|---|---|
name | string | Tool name (alphanumeric + underscores) |
description | string | What the tool does (shown to the LLM) |
parameters | object | JSON Schema for the tool's input |
ExecutionContext
Tool handlers receive an ExecutionContext as the second argument:
async function myTool(input: unknown, ctx: ExecutionContext) {
console.log(ctx.agent_name); // "research-bot"
console.log(ctx.task_id); // UUID of the current run
console.log(ctx.step_index); // Current step number
console.log(ctx.mode); // "local" or "cloud"
ctx.signal.throwIfAborted(); // Check for cancellation
}collectTools()
Validates a list of tools (checks for duplicate names):
const tools = collectTools(searchWeb, getWeather, summarize);Zod integration
Use zodToToolParameters to generate JSON Schema from a Zod schema:
import { z } from "zod";
import { zodToToolParameters, defineTool } from "papayya";
const params = zodToToolParameters(z.object({
query: z.string().describe("Search query"),
maxResults: z.number().optional().describe("Max results"),
}));
const searchWeb = defineTool(
{ name: "search_web", description: "Search the web", parameters: params },
async (input) => { /* ... */ },
);Tips for writing tools
- Clear descriptions — the LLM uses the description to decide when to call the tool
- Typed parameters — use
requiredin JSON Schema so the LLM always provides critical inputs - Error handling — return error messages as strings rather than throwing; the agent can often recover
- Idempotent — tools may be called multiple times on replay; make them safe to re-execute