Skip to Content

How do I move my instrumentation code from Traceloop to Rius?

Rius takes the OpenTelemetry traces that Traceloop’s OpenLLMetry already emits, so you keep your instrumentation code and change only where it exports. You change the base URL and the auth header, and in Python you also turn off metrics. Your workflow, task, agent and tool decorators keep working. Switching to the Rius SDK is optional, and the decorators map one to one.

How it works

OpenLLMetry is plain OpenTelemetry, so it exports OTLP to whichever endpoint you give it. Rius accepts OTLP over HTTP with a Bearer API key, which makes this a settings change. Here is how each Traceloop concept appears in Rius:

  • Workflow and task spans appear as generic steps (the CHAIN span kind).
  • Agent spans appear as agent spans, named after the decorated function.
  • Tool spans appear as tool spans, named after the decorated function.
  • Entity input and output (traceloop.entity.input and output) show on the span’s Input / Output tab.
  • LLM calls appear as generations with model, token counts and cost. Rius computes the cost from the spans, as a list-price estimate in US dollars.
  • Conversations (@conversation, set_conversation_id, withConversation) become sessions, because OpenLLMetry writes gen_ai.conversation.id and Rius reads it as the session.
  • Association properties such as user_id and chat_id are stored as traceloop.association.properties.* attributes. You can inspect and filter on them, but Rius does not treat them as users or sessions.
  • Project and environment become an agent, named by service.name. Give each environment its own workspace and API key.

In Python, re-pointing OpenLLMetry at Rius switches off Traceloop’s platform client. get_prompt(), guardrails, user feedback, datasets and experiments stop working. If your code calls any of them, keep OpenLLMetry pointed at Traceloop and add Rius as a second destination through a Collector instead.

The Traceloop migration guide covers every detail, and Migrate to Rius compares the paths for other tools.

Set it up

  1. Create a Rius workspace and an API key. Rius keys start with ri_.

  2. Re-point the exporter (Path A). Remove TRACELOOP_API_KEY from your deployment and set these instead:

    export TRACELOOP_BASE_URL="https://ingest.eu.console.rius-glassflow.com" export TRACELOOP_HEADERS="Authorization=Bearer%20<your Rius API key>" export TRACELOOP_METRICS_ENABLED="false" # Python SDK only

    TRACELOOP_HEADERS takes comma-separated key=value pairs, with %20 for the space. OpenLLMetry appends /v1/traces itself. Use an http or https URL, because Rius accepts OTLP over HTTP only.

  3. Or set the same values in code. In TypeScript:

    import * as traceloop from '@traceloop/node-server-sdk' traceloop.initialize({ appName: 'my-agent', baseUrl: 'https://ingest.eu.console.rius-glassflow.com', headers: { Authorization: `Bearer ${process.env.RIUS_API_KEY}` }, })

    appName (app_name in Python) becomes service.name, which names the agent in Rius. In Python, leftover TRACELOOP_BASE_URL and TRACELOOP_HEADERS values override the arguments to Traceloop.init(), so clear old ones. TypeScript works the other way round.

  4. Turn off metrics in Python. The Python SDK also exports OTLP metrics to the same base URL. Rius ingests traces only, so each export logs a 404. TRACELOOP_METRICS_ENABLED=false stops it, and traces are unaffected either way.

  5. Set user.id if you want users. OpenLLMetry does not set it. Set it with the OpenTelemetry API inside your outermost decorated function, because Rius lists a trace under the user of its root span:

    from opentelemetry import trace trace.get_current_span().set_attribute("user.id", user_id)

    Set the conversation id before the root span starts for the same reason.

  6. Optionally swap to the Rius SDK (Path B). The decorators map onto observe with a span kind:

    import rius from rius import SpanKind rius.init(service_name="trip-planner") # reads RIUS_API_KEY @rius.observe(kind=SpanKind.TOOL, tool_name="lookup_weather") def lookup_weather(city: str) -> str: ... @rius.observe(kind=SpanKind.AGENT, agent_name="weather_agent") def weather_agent(city: str) -> str: ...

    @workflow and @task become a bare @rius.observe(name=...). @conversation becomes with rius.session(cid):. Association properties for users become with rius.user(uid):. In TypeScript, withWorkflow, withTask, withAgent and withTool become observe wrappers with the matching kind, and withConversation becomes withSession.

    Remove OpenLLMetry in the same process, or LLM calls are recorded twice. Span names also change: unnamed agent and tool spans become invoke_agent and execute_tool names, and workflow and task spans use their plain name. Update saved searches that match on span names.

  7. Verify one request. Check that the trace tree shows your workflow, agent and tool spans, that the generation has model and token counts, that cost appears, and that traces with a conversation id share a session.

What you get

  • Your existing OpenLLMetry spans, decorators included, arrive with their structure intact. You rewrite no instrumentation.
  • Each model call shows its model, tokens and cost, computed by Rius.
  • Conversation ids group traces into sessions, and user.id groups them by user.
  • Alerts and the Rius MCP server work on the same traces, so your coding agent can query production runs.
  • With the Rius SDK you also see which agent processes are alive, and with partial spans turned on you see runs still in progress.
  • During evaluation you can send the same spans to both backends and cut over only when Rius shows what you need.

When something else fits

  • Keep Traceloop for anything that has no counterpart in Rius: monitors and evaluators, guardrails, the prompt registry and playgrounds, datasets and experiments, user feedback and annotations, and Traceloop Hub. Rius is a managed service only, so it has no self-hosted option.
  • Existing traces stay where they are. Rius starts from the first span you send.
  • If you depend on those platform features, fan out to both backends through an OpenTelemetry Collector rather than cutting over fully.

Still stuck? Get help

Last updated on