Python SDK

Tools

A tool call is recorded as a paired tool.call / tool.result. There are three ways to capture them, from fully manual to fully automatic.

Manual

Open a tool span with trace.tool(name, ...). Entering the block records a tool.call; record_output sets the result; leaving the block records the terminal tool.result (marked as an error if an exception propagates).

manual_tool.py
with trace.tool("issue_refund", arguments={"order": "4421", "amount": 49.0}) as call:    result = issue_refund(order="4421", amount=49.0)    call.record_output(result)

Linking to the LLM call

When a tool call comes from a model's tool-calling output, pass the LLM's tool_call_id so the call and result link back to the assistant message that requested them.

linked_tools.py
import jsonfor tool_call in response.choices[0].message.tool_calls:    args = json.loads(tool_call.function.arguments)    with trace.tool(tool_call.function.name, tool_call_id=tool_call.id, arguments=args) as call:        result = run_tool(tool_call.function.name, args)        call.record_output(result)

The @tool decorator

Decorate a function with @rollout.tool and every call records a tool.call / tool.result pair, capturing the bound arguments and the return value. It works on sync and async functions and requires an active trace (for example, inside a decorated agent).

decorated_tool.py
import mv37.rollout as rollout@rollout.tool("get_weather")def get_weather(city: str) -> dict:    return {"city": city, "temp": 21}

The name is optional and defaults to the function name. Pass record_input=False or record_output=False to skip capturing arguments or the return value.

Wrapping a registry

If you keep your tools in a registry, wrap_tools instruments them all at once so each call is recorded without a manual with block. It accepts a {name: callable} mapping or an iterable of callables, and supports sync and async functions. Return values and exceptions are preserved exactly.

registry.py
import mv37.rollout as rollouttools = rollout.wrap_tools({    "search": search,    "refund_order": refund_order,})result = tools["search"]("latest invoice")  # recorded automatically

Note

Wrapped tools need an active trace. When you call them outside one — for example from a script — pass implicit_trace=True to wrap_tools(...) and each call opens a one-off trace named after the tool.