Skip to Content

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, call client.shutdown() first, which drains pending spans and releases the slot, then call init() 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() returns False on 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

  1. Install the SDK with the extra for the library your agent calls, for example pip install "glassflow-rius[anthropic]" anthropic for 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.
  2. In a cell near the top, call init() once, before you create your model clients, and wrap your agent entry point with observe:
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?")
  1. 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 to unknown_service, so set it to find your agent in filters and cost breakdowns.
  • instruments: None enables 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-wide session.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 needs api_key or an Authorization header. 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() returns True, 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_spans on, 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=False and flush at the end of every invocation.

Still stuck? Get help

Last updated on