How do I switch from Arize Phoenix to Rius for Pydantic AI tracing?
To switch from Arize Phoenix, replace your register() call with rius.init() and call Agent.instrument_all(True), and Rius traces your Pydantic AI agents. Pydantic AI emits its own OpenTelemetry spans, so your agent code does not change. Each run arrives as a trace with the agent run, every model request with tokens and cost, and every tool call.
How it works
Pydantic AI is OpenTelemetry-native. It emits spans for agent runs, model requests and tool calls on its own, so Rius needs no auto-instrumentation extra for it.
rius.init()installs the global OpenTelemetry tracer provider and the exporter.Agent.instrument_all(True)tells Pydantic AI to emit its spans through that provider.- Rius reads the spans’ OpenTelemetry GenAI attributes directly and computes cost on the server. You send tokens, never a cost value.
The Phoenix concepts map across like this:
register(project_name=...)becomesrius.init(service_name=...). Rius groups traces into an agent named by the service name. Phoenix’s project name is stored but does not drive grouping.using_session(session_id)becomeswith rius.session(session_id):. Pydantic AI’s spans inherit the scope like any other span.- Evals, datasets and prompt tools have no equivalent. See the last section.
The Pydantic AI integration page covers the framework setup in full. The integrations overview lists every other framework and library Rius covers.
Set it up
-
Create a Rius account and an API key in the console, then export the key. The endpoint defaults to EU ingest, so the key is usually the only setting.
export RIUS_API_KEY="<your API key>" -
Swap the packages. The base SDK is enough for Pydantic AI, so you need no extra. Uninstall
arize-phoenix-otelonly if nothing else uses it.pip uninstall arize-phoenix-otel pip install glassflow-rius pydantic-ai -
Replace the Phoenix setup. Your Phoenix code probably looks like this:
from phoenix.otel import register register(project_name="my-agent", auto_instrument=True)Swap it for
rius.init()andAgent.instrument_all(True). Your agent and tool definitions stay as they are:import rius from pydantic_ai import Agent rius.init(api_key="ri_...", service_name="my-agent") Agent.instrument_all(True) support_agent = Agent( "openai:gpt-5-mini", instructions="Answer support questions; check accounts with the tool.", ) @support_agent.tool_plain def account_status(email: str) -> str: """Fetch a customer's account standing by email.""" return lookup(email) with rius.session(conversation_id): result = support_agent.run_sync(ticket) -
If you used
using_sessionorusing_user, switch torius.sessionandrius.userin the same change. The OpenInference context managers do not stamp spans you start withrius.start_as_current_spanor@rius.observe, which are usually the root. -
If you want both backends to receive the same spans while you compare, do this in place of steps 2 and 3: keep
arize-phoenix-oteland Phoenix’sregister(), add Rius as a second span processor, and callAgent.instrument_all(True). Make the swap torius.init()once Rius shows what you need. The Phoenix migration guide shows that setup, including thereplace_default_processor=Falseargument it needs. -
Run one request that calls a tool and open the trace in the console.
What you get
Each Pydantic AI run shows up as a trace with spans named after your agents, models and tools:
invoke_agent support_agent 6.0s
├─ chat gpt-5-mini 3.3s decides to call the tool
├─ execute_tool account_status 0ms
└─ chat gpt-5-mini 2.7s answers- The root span is named
invoke_agentfollowed by the agent name. - Each model request is a generation named
chatfollowed by the model, with tokens and cost. Cost is a list-price estimate in US dollars, and a model Rius does not know gets an empty cost, not zero. - Each tool call is a span named
execute_toolfollowed by the tool name, with no wrapping on your side. - Time to first token and reasoning tokens show up for Pydantic AI runs.
- Traces sit under the session you set with
rius.session.
Two behaviours differ from what you may expect:
- Span statuses arrive as UNSET rather than OK on successful spans, while the trace status still reads Ok.
- What the spans carry (prompts, completions, tool arguments) is controlled on the Pydantic AI side by its
InstrumentationSettings, for exampleinclude_content, on top of the SDK’s own privacy controls.
The agent’s traces are grouped under the name you gave service_name. Set it, or they arrive as unknown_service.
When something else fits
- If you depend on Phoenix evals, datasets, experiments, annotations or the prompt playground, keep Phoenix for those, since Rius does not offer them. Run both side by side rather than cutting over completely.
- If you want the smallest possible diff, keep
register()and re-point its exporter at Rius. The Phoenix migration guide covers that path, and the migration overview covers other tools. - Existing Phoenix traces stay in Phoenix. Rius starts from the first span you send and cannot import history.