How do I trace a Python AI agent running in a Jupyter notebook?
Rius traces a Python agent in a Jupyter notebook once you call rius.init() one time in the process and flush spans before the process ends. Install the SDK, call init() early with your API key and a service name, and run your agent code. Model calls from installed integrations and functions wrapped with observe arrive as traces. Because spans export from a background thread, call client.flush() when a run finishes so nothing waits in the queue.
How it works
- One init per process. The OpenTelemetry global provider is write-once. If a cell calls
init()while a client is already active, Rius logs a warning and returns the existing client unchanged, so your new arguments are ignored. To change settings, callclient.shutdown()first, which drains pending spans and releases the slot, then callinit()again. - Background export. Spans queue in memory and export in batches from a background thread. A batch is flushed every 5000 ms by default (
OTEL_BSP_SCHEDULE_DELAY), and up to 2048 spans are buffered (OTEL_BSP_MAX_QUEUE_SIZE). A short run can end before the first scheduled export fires. - Exit and freeze. Pending spans are flushed automatically at interpreter exit. If your runtime can freeze the process or kill it hard, a frozen background thread exports nothing and the exit hook may never run, so you flush explicitly.
- Failures stay out of your code. Exporter errors never propagate into your application.
flush()returnsFalseon timeout or when the latest export was rejected, for example with a 401 from a bad API key, so you can check that your spans left.
Set it up
- Install the SDK with the extra for the library your agent calls, for example
pip install "glassflow-rius[anthropic]" anthropicfor the Anthropic client used below. The extra installs only the instrumentation, so install the library itself too, as this command does. Python 3.10 or newer is required. Installation covers every option, and Integrations lists what is traced automatically. - In a cell near the top, call
init()once, before you create your model clients, and wrap your agent entry point withobserve:
import anthropic
import rius
client = rius.init(api_key="ri_...", service_name="my-agent")
claude = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
@rius.observe
def handle(query: str) -> str:
response = claude.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=200,
messages=[{"role": "user", "content": query}],
)
return response.content[0].text
handle("What should I check when a collector drops spans?")- After a run, force an export. Flush at the end of a run, never inside a function your agent calls in a loop, because
flush()blocks until the export completes:
client.flush(timeout_millis=30_000) # force an export
client.shutdown() # drain, stop the thread, release init()Call shutdown() when you are done or before you reconfigure. It also sends the final stopped heartbeat ping.
5. Pick the settings that matter for your run:
service_name(RIUS_SERVICE_NAME): defaults tounknown_service, so set it to find your agent in filters and cost breakdowns.instruments:Noneenables every installed integration,["openai"]restricts to one,[]turns them all off.capture_content(RIUS_CAPTURE_CONTENT): set it to false to strip prompt and response content at export while metadata still flows.session_id(RIUS_SESSION_ID): a process-widesession.id, useful when the whole process is one session.partial_spans(RIUS_PARTIAL_SPANS): exports a content-free snapshot of each span at start, so a run that is still going or that crashed leaves a record.heartbeat(RIUS_HEARTBEAT): on by default and needsapi_keyor anAuthorizationheader. Without credentials no heartbeat is sent and the agent shows no instances. Pings stop while a process is frozen.
For the full export and shutdown rules, see Reliability.
What you get
- Each model call appears as a span with tokens and cost, grouped under the trace of the function you wrapped with
observe. - Cost is a list-price estimate in US dollars, computed by Rius from the model and token counts. You never send a cost value.
- Spans from a finished run reach Rius as soon as
flush()returnsTrue, so you can open the trace right after the run instead of waiting for the next batch. - With the heartbeat on and credentials set, your agent shows as an instance under its service name in the Agents view, and
shutdown()marks a clean stop. - With
partial_spanson, work that was still open when the process died stays visible as a snapshot.
When something else fits
- If you only need to run a script once, you can rely on the exit hook that flushes at interpreter exit, but an explicit
flush()is the safer habit for short runs. - If you move this code into a serverless function, set
heartbeat=Falseand flush at the end of every invocation.
Related
Last updated on