wrap()
rollout.wrap(client) instruments a provider client in place and returns it. There is no global monkeypatching — only the object you pass in is touched — and wrapping is idempotent. Each instrumented call records an llm span with the model, provider, options, input, output, token usage, latency, and errors. If there is an active trace the span attaches to it; otherwise an implicit single-call trace is created.
import mv37.rollout as rolloutfrom openai import OpenAIrollout.init(api_key="...", agent_name="support_agent")openai_client = rollout.wrap(OpenAI()) # or client.wrap(OpenAI()) with an explicit clientresponse = openai_client.responses.create(model="gpt-4.1-mini", input="Hello")The original provider response (or stream) is returned unchanged, and provider exceptions propagate untouched. wrap auto-detects supported clients and instruments both sync and async variants.
OpenAI
For OpenAI, wrap instruments both responses.create and chat.completions.create — sync and async, streaming and non-streaming. AsyncOpenAI is supported the same way.
from openai import OpenAIopenai_client = rollout.wrap(OpenAI())response = openai_client.chat.completions.create( model="gpt-4.1-mini", messages=[{"role": "user", "content": "Hello"}],)Anthropic
Anthropic clients work identically — wrap instruments messages.create on Anthropic and AsyncAnthropic. The same span captures model and options, the input (messages, system, tools), output, token usage (including cache tokens), latency, errors, and streaming preview.
from anthropic import Anthropicclaude = rollout.wrap(Anthropic())message = claude.messages.create( model="claude-sonnet-4-5", max_tokens=1024, messages=[{"role": "user", "content": "Hello"}],)OpenAI-compatible gateways
Gateways such as OpenRouter use the OpenAI SDK, so the same wrapper applies. Force the integration with provider="openai" and tag the upstream provider via attributes.
openrouter = rollout.wrap( OpenAI(base_url="https://openrouter.ai/api/v1", api_key="..."), provider="openai", attributes={"provider": "openrouter"},)The @agent boundary
@rollout.agent marks a custom agent entry point. It opens a fresh trace per call (named after the function), captures the arguments and return value, flushes on completion, and records and re-raises errors. Combined with rollout.wrap, this is the whole setup for a hand-written agent loop:
openai_client = rollout.wrap(OpenAI())@rollout.agentdef run_agent(user_message: str) -> str: response = openai_client.responses.create(model="gpt-4.1-mini", input=user_message) return response.output_textIt works on sync and async functions. Pass capture_input=False / capture_output=False to skip recording arguments or the return value.
Errors & explicit entry points
wrap never silently no-ops. If it is handed an object it does not recognize, it raises UnsupportedIntegrationError rather than returning it unwrapped. The beta supports provider="openai" (including OpenAI-compatible gateways) and provider="anthropic".
| Exception | Raised when |
|---|---|
UnsupportedIntegrationError | wrap() receives an object (or provider string) it cannot instrument. |
MissingDependencyError | An integration is used without its optional dependency installed. |
Tip
You can also call the explicit entry points directly: mv37.rollout.integrations.openai.wrap_openai(...) and mv37.rollout.integrations.anthropic.wrap_anthropic(...). Both accept attributes, capture_input, and capture_output.