Migrate from Helicone to Rius
Helicone and Rius capture LLM calls in different ways. Helicone is mainly a
proxy: your client sends every request through a Helicone base URL, and
Helicone logs it on the way through, with features configured by
Helicone-* request headers. Rius is OpenTelemetry-based: the
Rius SDK instruments your LLM client inside your process and
exports spans (one record per step, such as an LLM
call), so nothing sits between your code and the provider.
Migrating is a small code change: add the Rius SDK, point the client back at the provider if you no longer need the gateway, and move the values you sent as Helicone headers onto Rius sessions, users and span attributes. This guide maps the concepts, walks the paths, and ends with a cutover checklist.
TL;DR: Call rius.init(), then remove the Helicone base URL and
Helicone-Auth header, or keep them if you rely on the gateway. Move your
Helicone-* session, user and property headers onto Rius equivalents (see
the checklist). Rius is not a
gateway: caching, rate limits, retries and fallbacks stay with whatever
serves them today.
Why migrate from Helicone to Rius
Rius is agent observability. You get:
- traces with the full tree of agent, tool, retrieval and model steps, not LLM requests only
- sessions, per-user views and server-side cost
- alerts
- with the Rius SDK, which agent processes are alive, and, with partial spans turned on, runs still in progress
- an MCP server so your coding agent can query production traces
What Helicone offers beyond observability is covered in what does not carry over.
Concept mapping: Helicone vs Rius
| Helicone | Rius | Notes |
|---|---|---|
| Request (one logged LLM call) | Generation | An LLM span with model, tokens, and messages. Auto-instrumentation creates one per call. |
Session trace (Helicone-Session-Path) | Trace | A span started inside another span becomes its child, so the tree comes from your code’s call structure, not from a path string. |
Session (Helicone-Session-Id) | Session | rius.session(id) stamps session.id on every span in scope. |
Session name (Helicone-Session-Name) | Root span name or attribute | No separate session name field. Name the span that wraps the work, or set an attribute. |
User (Helicone-User-Id) | User | rius.user(id) stamps user.id; the Users page lists each user with their traces, failures, error rate and cost. |
Custom properties (Helicone-Property-*) | Span or resource attributes | Filterable in the trace list. See custom properties. |
| Cost | Cost | Computed server-side from the model and token counts. |
Omit logs (Helicone-Omit-Request, Helicone-Omit-Response) | capture_content=False | Strips prompts and completions at export; metadata still flows. See privacy controls. |
| Alerts | Alerts | Rule-based alerts with webhook, email and Slack channels. |
| Caching, rate limits, retries, fallbacks | Not available | Rius is not a gateway. See what does not carry over. |
| Prompts, datasets, scores, feedback | Not available | See what does not carry over. |
Choose your path
- You call through the Helicone proxy or AI Gateway (
oai.helicone.ai,anthropic.helicone.ai,ai-gateway.helicone.ai, or aHelicone-Authheader): instrument with the Rius SDK, then decide whether to keep the gateway in front of the provider. - You use Helicone’s async logger (
helicone-asyncor@helicone/async): replace the logger with the Rius SDK. Both instrument your client in process, so the swap is one call.
Path A: instrument your client with the Rius SDK
Install the SDK with the integrations for the providers you call. In Python
the extras install the instrumentation. In TypeScript you install the
instrumentation packages next to the SDK: the
@arizeai/openinference-instrumentation-* packages are the OpenInference
instrumentations the Rius TypeScript SDK attaches for each provider.
Python
pip install "glassflow-rius[openai,anthropic]"
export RIUS_API_KEY="ri_..."Point the client back at the provider
With Helicone, the provider client carries a Helicone base URL and an auth
header. With Rius, the client is the provider’s default one, and init()
traces every call it makes:
# Before: through the Helicone proxy
import os
from openai import OpenAI
from anthropic import Anthropic
openai_client = OpenAI(
base_url="https://oai.helicone.ai/v1",
default_headers={"Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}"},
)
anthropic_client = Anthropic(
base_url="https://anthropic.helicone.ai",
default_headers={"Helicone-Auth": f"Bearer {os.environ['HELICONE_API_KEY']}"},
)Python
# After: direct to the provider, traced by Rius
import rius
from openai import OpenAI
from anthropic import Anthropic
rius.init(service_name="my-agent") # reads RIUS_API_KEY
openai_client = OpenAI() # default base URL, OPENAI_API_KEY
anthropic_client = Anthropic() # default base URL, ANTHROPIC_API_KEYEach call becomes a generation with the model, input and output tokens (including cache reads and writes), and the messages; Rius computes cost from the model and tokens. Calling a provider other than OpenAI or Anthropic? Check the integrations list, or trace the call as a manual generation.
If you use the AI Gateway with Helicone-managed provider keys or credits, switch to your own provider keys before you remove the gateway. Rius never holds provider keys, so without your own keys the calls fail.
Keep the gateway and run side by side
If you rely on gateway features, such as caching, rate limits or fallbacks,
leave the Helicone base URL and headers in place and add rius.init()
anyway. The Rius integrations patch the client library in your process, not
the network path, so calls are traced whatever base URL the client uses.
This is also the side-by-side setup: every call is logged by Helicone on the
network path and traced by Rius in your process, so you can compare request
counts, token totals and cost on the same traffic, then remove the Helicone
base URL when you are ready.
Two differences when you keep the gateway:
- Cache hits: Rius instruments the client in your process, so a response Helicone serves from its cache is recorded like any other call: a generation with the token counts the response carries, priced from those tokens at the provider’s rates. During side by side, Rius cost is higher than Helicone’s by your cache-hit share.
- Model names: use plain model names (
gpt-4o-mini), not the gateway’sgpt-4o-mini/openaiform, or calls show tokens but no cost.
Path B: replace the Helicone async logger
Helicone’s async logger instruments your provider client in process and sends the calls to Helicone without the proxy. The Rius SDK works the same way, so the swap is one call. Install the SDK as in Path A, then:
Python
# Before: Helicone async logger
from helicone_async import HeliconeAsyncLogger
logger = HeliconeAsyncLogger(api_key=HELICONE_API_KEY)
logger.init()# After: Rius SDK
import rius
rius.init(service_name="my-agent") # reads RIUS_API_KEYReplace logger.set_properties(...) (withProperties in TypeScript) with
the session, user and attribute equivalents in
map Helicone headers and properties.
Remove the Helicone logger rather than running both in one process: both
register the process-wide OpenTelemetry tracer provider, OpenTelemetry keeps
only the first registration, and the instrumented calls reach only one of the
two tools. To
compare the two backends, run the logger in one service and the Rius SDK in
another, or compare a deploy on each.
Map Helicone headers and properties
These replace the Helicone-* headers (or the async logger’s properties),
whichever path you took.
Sessions
Helicone groups requests with Helicone-Session-Id and arranges them with
Helicone-Session-Path. In Rius you scope a session around the work, and the
tree comes from how your spans nest:
# Before: Helicone session headers on each request
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
extra_headers={
"Helicone-Session-Id": session_id,
"Helicone-Session-Path": "/ticket/answer",
"Helicone-Session-Name": "Support ticket",
},
)Python
# After: a Rius session scope and a parent span
with rius.session(session_id):
with rius.start_as_current_span("support_ticket"):
response = openai_client.chat.completions.create(
model="gpt-4o-mini", messages=messages,
)Every span started inside the scope, including the auto-instrumented LLM
call, carries session.id. To keep the structure of a
Helicone-Session-Path such as /ticket/answer, nest spans the same way: a
ticket span with an answer span started inside it. Sessions covers scopes and the
process-wide default.
Users
Helicone-User-Id maps to a user scope:
Python
with rius.user(request.user_id):
handle(request) # every span of the request carries user.idUsers appear on the Users page and as a user filter on the trace list.
Custom properties
A Helicone-Property-* header tags one request. In Rius, tags are span
attributes. How you carry them over depends on what the property describes:
-
Per request (a ticket id, a conversation, a feature): set it on the span that wraps the work.
with rius.start_as_current_span("support_ticket") as span: span.set_attribute("app.ticket_id", ticket_id) span.set_attribute("app.feature", "auto-reply") response = openai_client.chat.completions.create(...)The trace list finds the trace when any of its spans carries the value.
-
Per deployment (app version, region): put it on the resource (the attributes that describe the process, set with the standard OpenTelemetry
OTEL_RESOURCE_ATTRIBUTESvariable), so every span from the process carries it. Keep each environment (production, staging) in its own workspace with its own API key rather than tagging it here.export OTEL_RESOURCE_ATTRIBUTES="service.version=1.4.2,app.region=eu"
Filter on either kind in the trace list with Add filter; see search and filter, and declare an attribute with a type to filter it numerically.
What does not carry over from Helicone
Rius is an observability platform, not a gateway, and today it has no equivalent for:
- Gateway features: no proxy, so no response caching, no rate limits, no automatic retries, no provider or model fallbacks, and no unified API across providers. If you depend on these, keep the Helicone gateway or move them to another gateway or your own client code. Rius traces the calls either way.
- Provider key management and credits: Rius never holds your provider keys and never bills for model usage. You call providers with your own keys.
- Prompt management: no versioned prompts, prompt deployment or prompt playground. Prompt content on spans is captured and inspectable, but there is no registry.
- Scores, feedback, datasets and evals: no score or feedback objects on traces, no curated datasets, and no eval runs.
- Security and moderation: no prompt-injection detection or moderation layer, since those run in the request path.
- Property and product analytics: attributes are filterable, but there is no cost or metric breakdown per custom property, and no active-user, retention or cohort analytics.
- Self-hosting: Rius is a hosted service only.
- Historical data: existing traces stay in Helicone; Rius starts from the first span you send it.
If caching, rate limits or fallbacks are load-bearing in production, do not remove the gateway as part of the migration. Add Rius next to it first, then replace the gateway as a separate project if you want to.
Helicone to Rius cutover checklist
- 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.
- Install the Rius SDK with your providers’ integrations and call
rius.init()at startup (installation). - Keep both backends on the same traffic for now. With the proxy or AI Gateway, keep the Helicone base URL and headers (side by side). With the async logger, run it and the Rius SDK in separate services or deploys (Path B).
- Verify one call end to end in Rius: the trace appears under the agent
named by the
service_nameyou passed torius.init()(serviceNamein TypeScript), the generation shows model and token counts, and cost appears. - Replace
Helicone-Session-Idwithrius.session(),Helicone-User-Idwithrius.user()(withSessionandwithUserin TypeScript), andHelicone-Property-*with span or resource attributes. Check the session and user show on the trace. - Compare both backends on real traffic for a few days: request and LLM call counts, token totals, cost.
- Decide what happens to gateway features you use. Keep the gateway, or move caching, retries and fallbacks elsewhere first.
- Move anything that reads from Helicone (dashboards, alerts, the query API) to the Rius equivalents, or deliberately leave it behind.
- Remove the Helicone base URL,
Helicone-*headers and async logger, and revoke the Helicone API key once nothing sends to it.
Frequently asked questions
Can I point the Helicone proxy at Rius?
No. The Helicone proxy logs to Helicone, and Rius does not accept Helicone’s request logs. Rius receives OpenTelemetry spans, which the Rius SDK (or any OpenTelemetry instrumentation) creates in your process.
Do I have to remove the Helicone gateway to use Rius?
No. The Rius SDK instruments your client library, so calls are traced whether they go to the provider directly or through the Helicone gateway. Keep the gateway as long as you need its features.
Does Rius add latency to my LLM calls?
The Rius SDK exports spans in the background after each span ends, so the call itself does not wait on Rius, and nothing proxies the request. If Rius is unreachable, your calls still go through; see reliability.
Can I move my Helicone history into Rius?
No. There is no import from Helicone. Keep Helicone, or an export from its query API, for history, and start Rius traces from the cutover.