Skip to content
Pyrula

Tools

A tool is an async function the model can call. Decorate it with @tool and hand it to the LLM. The first parameter is a ToolContext; the rest are the arguments the model fills in.

from pyrula.agents.decorators.tool import tool
from pyrula.agents.context.tool_context import ToolContext
@tool(retry=0, timeout=10.0)
async def search(ctx: ToolContext, query: str) -> str:
return await backend.search(query)

The signature and docstring become the tool schema the model sees, so type your parameters and say what the tool does. Pass schema= to override it, or name= / description= to set them directly.

OptionWhat it does
retryTimes to retry the tool on failure.
timeoutPer-call wall-clock cap.
cache_ttlCache the result by arguments for this many seconds.
writesMarks the tool as having side effects (affects replay handling).
name / description / schemaOverride what the model sees.

A tool can run again on recovery if it was interrupted before its result was recorded. For anything with a side effect, make it idempotent. ctx.idempotency_key is stable across retries of the same call, so use it as a dedup key:

@tool
async def charge(ctx: ToolContext, amount_cents: int) -> str:
return await payments.charge(amount_cents, key=ctx.idempotency_key)

ctx.cache gives you a small get/set store scoped to the turn for cheaper dedup or memoization within a single run.

This is the same constraint that applies to durable steps. See Determinism for the why.