Skip to Content

Migrate from Arize Phoenix to Rius

Phoenix is built on OpenTelemetry and OpenInference, and Rius understands OpenInference spans. Migrating from Phoenix is therefore a routing change: the OpenInference instrumentors you already run keep producing the same spans, and you point their exporter at Rius instead of at your Phoenix collector. This guide maps the concepts, walks the two migration paths, and ends with a cutover checklist.

TL;DR: Keep your OpenInference instrumentors. Re-point register() or your OTLP exporter at Rius (Path A), or replace register() with rius.init(), which wires the same instrumentors for you (Path B). Run both backends in parallel, then cut over.

Why migrate from Phoenix to Rius

Rius is agent observability. The same OpenInference spans you send Phoenix today get session and user grouping, server-side cost, alerts, and an MCP server so your coding agent can query production traces. With the Rius SDK (Path B) you also see which agent processes are alive, and, with partial spans turned on, runs still in progress. Rius is a managed service, so there is no Phoenix server or database to run. What Phoenix offers beyond tracing is listed under what does not carry over.

Concept mapping: Phoenix vs Rius

PhoenixRiusNotes
Project (project_name, openinference.project.name)Agent (named by service.name)Rius groups traces by agent name, which defaults to the service.name resource attribute, within the workspace your API key belongs to. Phoenix’s project attribute is stored but does not drive grouping. If you use separate projects per environment, give each environment (production, staging) its own workspace and API key instead.
TraceTraceSame OpenTelemetry trace, same tree view.
Span kind (openinference.span.kind)Span kindAGENT, LLM, TOOL, CHAIN, RETRIEVER, EMBEDDING and RERANKER are read as-is. See the attribute reference.
LLM span (llm.model_name, llm.token_count.*)GenerationRead as a generation, so the span gets model, token and cost analytics.
Session (session.id, using_session)SessionSame session.id attribute. Set it on every span of the trace, the root included; see the note under Path A.
User (user.id, using_user)UserSame user.id attribute. See users.
Metadata and tags (using_metadata, using_tags)Span attributesStored as span attributes, filterable by key. No dedicated views. See the attribute reference.
Cost (Phoenix’s model pricing table)CostComputed server-side from model and token counts at the providers’ public list prices. You send tokens, never cost.
Annotations and feedbackNot availableSee what does not carry over.
Evals, datasets, experimentsNot availableSee what does not carry over.
Prompts and prompt playgroundNot availableSee what does not carry over.

Choose your path

  • You want the smallest possible diff: keep phoenix.otel.register() or your own TracerProvider, and re-point the exporter. This is also how you run both backends side by side.
  • You want the Rius defaults (raised span attribute limit, privacy controls, sessions and users as SDK scopes) or the live views (which agent processes are alive, and, with partial spans turned on, runs still in progress): replace register() with rius.init(). Your OpenInference instrumentors stay, because the Rius SDK ships the same ones.

Path A: keep your instrumentation (re-point the exporter)

Phoenix’s collector and Rius both take OTLP over HTTP with a Bearer token, so the change is the endpoint, the key, and a service name. The Rius endpoint takes OTLP/HTTP (protobuf or JSON), not gRPC.

If you use phoenix.otel.register()

register() from arize-phoenix-otel can export to Rius. Pass the full traces URL, the protocol, and your Rius API key; register() sends the key as authorization: Bearer <key>:

# Before: pointing at Phoenix from phoenix.otel import register tracer_provider = register( project_name="my-agent", auto_instrument=True, )
# After: pointing at Rius import os from opentelemetry.sdk.resources import Resource from phoenix.otel import register tracer_provider = register( endpoint="https://ingest.eu.console.rius-glassflow.com/v1/traces", protocol="http/protobuf", api_key=os.environ["RIUS_API_KEY"], batch=True, resource=Resource.create({"service.name": "my-agent"}), auto_instrument=True, )

Three details make the difference between this working and silently not:

  • Pass endpoint and protocol="http/protobuf" in code. Setting only PHOENIX_COLLECTOR_ENDPOINT to the Rius URL sends spans to the wrong port.
  • Set service.name. register() stamps openinference.project.name, which Rius stores but does not group by. Without a service.name, your traces arrive as unknown_service. Passing a resource as above, or setting OTEL_SERVICE_NAME, fixes it.
  • Use batch=True in production. register() defaults to a SimpleSpanProcessor, which exports each span synchronously as it ends.

If you wire OpenInference instrumentors yourself

If you already build a plain OpenTelemetry TracerProvider, or you want to drop arize-phoenix-otel entirely, use a standard OTLP/HTTP exporter. This is the same setup as the OpenInference page:

import os from opentelemetry import trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import SpanLimits, TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from openinference.instrumentation.openai import OpenAIInstrumentor provider = TracerProvider( resource=Resource.create({"service.name": "my-agent"}), span_limits=SpanLimits(max_span_attributes=4096), ) provider.add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint="https://ingest.eu.console.rius-glassflow.com/v1/traces", headers={"Authorization": f"Bearer {os.environ['RIUS_API_KEY']}"}, ) ) ) trace.set_tracer_provider(provider) OpenAIInstrumentor().instrument(tracer_provider=provider)

Your using_session, using_user and using_attributes context managers from openinference-instrumentation keep working on either setup: they stamp session.id and user.id, which Rius reads directly. Set both on every span of the trace, the root span included: the top-level span of the trace, usually your request handler. The trace list shows a trace under the session and user of its root span. The catch is that using_session and using_user only stamp spans created by OpenInference: the instrumentors, and the tracer you get from the provider register() returns (tracer_provider.get_tracer(__name__)). A root span you start from a plain OpenTelemetry tracer misses them, so the trace shows up as a single-trace session with no user, even though the LLM span inside carries the right ids. Start your root spans from that OpenInference tracer, or set the attributes on the root yourself:

with tracer.start_as_current_span( "handle-turn", attributes={"session.id": conversation_id, "user.id": user_id}, ): run_agent(message)

Raise the span attribute limit on either setup. OpenInference writes one attribute per message and tool field, and OpenTelemetry keeps 128 per span by default, so a long agent loop silently loses the model, usage or output on later turns. Set OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=4096, or pass SpanLimits as above. See raise the span attribute limit.

Side by side: send to Phoenix and Rius

For an evaluation period, keep register() pointed at Phoenix and add Rius as a second span processor on the same provider:

import os from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace.export import BatchSpanProcessor from phoenix.otel import register # Phoenix, configured as today (PHOENIX_COLLECTOR_ENDPOINT, PHOENIX_API_KEY) tracer_provider = register( project_name="my-agent", batch=True, resource=Resource.create({"service.name": "my-agent"}), auto_instrument=True, ) # Rius, as a second exporter tracer_provider.add_span_processor( BatchSpanProcessor( OTLPSpanExporter( endpoint="https://ingest.eu.console.rius-glassflow.com/v1/traces", headers={"Authorization": f"Bearer {os.environ['RIUS_API_KEY']}"}, ) ), replace_default_processor=False, )

Pass replace_default_processor=False. The TracerProvider that register() returns removes its Phoenix exporter when you add a span processor, unless you tell it not to, and your Phoenix project goes quiet.

Both backends now receive identical spans, so you can compare trace trees, token counts and cost on the same traffic. If you already route through an OpenTelemetry Collector, fan out there instead with one extra otlphttp exporter; the Collector page has the config.

Path B: replace register() with rius.init()

The Rius SDK for Python installs and enables the same OpenInference instrumentors as optional extras, for example openinference-instrumentation-openai behind glassflow-rius[openai]. On top of the exporter, it sets the 4096 span attribute limit, adds privacy controls, gives you session and user scopes, and sends heartbeats so you see which agent processes are alive. Turn on partial spans to also see runs still in progress.

pip uninstall arize-phoenix-otel pip install "glassflow-rius[openai]" export RIUS_API_KEY="<your API key>"
# Before from phoenix.otel import register from openinference.instrumentation import using_session register(project_name="my-agent", auto_instrument=True) with using_session(conversation_id): run_agent(message)
# After import rius rius.init(service_name="my-agent") with rius.session(conversation_id): run_agent(message)

rius.init() instruments every supported library it finds installed, so your own OpenAIInstrumentor().instrument() calls and the auto_instrument=True flag go away with register(). The endpoint defaults to EU ingest, so RIUS_API_KEY is usually the only setting. See installation for every option and the integrations list for which libraries are covered.

Mapping the rest of the Phoenix tracing API:

Phoenix / OpenInferenceRius SDK
register(project_name=...)rius.init(service_name=...)
using_session(session_id)with rius.session(session_id):
using_user(user_id)with rius.user(user_id):
tracer.start_as_current_span(..., openinference_span_kind="chain")rius.start_as_current_span(name); pass kind= for other span kinds
@tracer.chain@rius.observe
@tracer.agent@rius.observe(kind=rius.SpanKind.AGENT)
@tracer.tool@rius.observe(kind=rius.SpanKind.TOOL)
Manual LLM span with llm.* attributesrius.start_as_current_generation(model=..., provider=...), or let the instrumentor capture it

Switch using_session and using_user to rius.session and rius.user in the same change. The OpenInference context managers do not stamp spans you start with rius.start_as_current_span or @rius.observe, which are usually the root, so the trace loses its session and user (see the note under Path A). rius.session and rius.user stamp every span in scope, the root and the instrumentors’ spans included. The full API is in the tracing guide.

What does not carry over from Phoenix

Rius has no equivalent today for:

  • Evals: no LLM-as-judge or code evaluators, and no eval results on spans. If you run phoenix.evals today, keep that workflow in Phoenix or a dedicated eval tool.
  • Datasets and experiments: no datasets built from traces or uploads, and no experiment runs comparing app versions.
  • Prompt management and the prompt playground: no versioned prompts, no prompt deployment, and no playground or span replay. Prompt content on spans is captured and inspectable, but there is no registry.
  • Annotations: no human labels or feedback on spans, and no annotation API.
  • Self-hosting: Rius is a managed service only. Phoenix can be self-hosted.
  • Custom model prices: Phoenix lets you add or override model prices. Rius prices calls at the providers’ public list prices, and a model it does not know stays unpriced (empty, not $0). See track LLM cost.
  • Historical data: Existing traces stay in Phoenix; Rius starts from the first span you send it. There is no import of historical Phoenix data.
  • Phoenix-only attributes: openinference.project.name, metadata and tags are stored and can be filtered on by key, but they do not drive grouping, views or analytics.

If evals, datasets or annotations are load-bearing in your workflow, run the side-by-side setup and keep Phoenix for those pieces rather than cutting over completely.

Phoenix to Rius cutover checklist

  1. Create a Rius workspace and API key (quickstart). New organizations start with a free trial (30 days or 500,000 spans, whichever comes first), then a paid plan; see Plans and billing.
  2. Pick your path: re-point the exporter or switch to rius.init().
  3. Run both backends in parallel (side by side).
  4. Set service.name on every service, so traces do not land under unknown_service.
  5. Raise the span attribute limit if you are not on the Rius SDK (see Path A).
  6. Verify one traced request end to end: the trace tree looks right, the LLM span shows as a generation with model and token counts, cost appears, and the trace sits in the session and under the user you expect (see the root span note). If fields are empty, check the supported-conventions matrix.
  7. Compare both backends on real traffic for a few days: trace counts, token totals, cost.
  8. Move anything that reads from Phoenix (dashboards, eval jobs, internal tools) to its Rius equivalent, or deliberately leave it in Phoenix.
  9. Remove the Phoenix exporter, uninstall arize-phoenix-otel if nothing else uses it, and revoke the Phoenix API key once nothing sends to it.

Frequently asked questions

Do I have to re-instrument my code to migrate from Phoenix?

No. Phoenix and Rius both read OpenInference spans, so your instrumentors stay. You change where the exporter sends spans, and add a service.name.

Can I keep using phoenix.otel.register() with Rius?

Yes, as long as you pass endpoint (the full /v1/traces URL), protocol="http/protobuf" and your Rius key as api_key in code. Setting only PHOENIX_COLLECTOR_ENDPOINT sends spans to the wrong port.

Can I run Phoenix and Rius at the same time?

Yes. Add Rius as a second span processor with replace_default_processor=False, or fan out from an OpenTelemetry Collector. The migration is reversible until you remove the Phoenix exporter.

Can I import my existing Phoenix traces into Rius?

No. Rius ingests traces as they are exported. Keep your Phoenix instance, or an export of it, for as long as you need the history.

Last updated on