ModelRig Docs Leaderboard Console ↗ GitHub Sign up

Recipe: OpenAI Agents SDK

The OpenAI Agents SDK sends its traces to OpenAI by default. Register a trace processor that forwards them to OpenTelemetry — the OpenInference instrumentation for the Agents SDK does this — and export them over OTLP to ModelRig.

Mint a key in the org you want the data in. Sign in at https://app.modelrig.ai, check the organization name in the header badge, and create a key on the API keys page. The rig_sk_ secret is shown once. One key = one org: every row the key exports lands in the organization it was minted in, whichever organization you happen to be viewing in the console. Set it on every service, worker and cron that creates a rig; your provider keys stay where they are.


# Wherever your app reads configuration: .env.local, your platform's env vars, a secret manager. Never in code.
MODELRIG_API_KEY=rig_sk_…

Point your OTLP exporter at ModelRig. ModelRig accepts OpenTelemetry traces at POST /v1/otlp/v1/traces on the ingest host (https://api.modelrig.ai unless you were given another), over OTLP/HTTP as protobuf or JSON, optionally gzipped. Authenticate with a rig_sk_ key that carries the ingest scope; every run and step lands in that key's organization. Each trace becomes one run with one attested step per span: model, provider, operation, timing, status and token usage. Prompts, completions and tool arguments are not read.


# Standard OpenTelemetry exporter variables — most OTel SDKs read them as-is.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.modelrig.ai/v1/otlp/v1/traces
OTEL_EXPORTER_OTLP_TRACES_HEADERS=Authorization=Bearer%20rig_sk_…
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf

Minimal config

Untested config — verify against your SDK and instrumentation versions.


from openinference.instrumentation.openai_agents import OpenAIAgentsInstrumentor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))  # reads OTEL_EXPORTER_OTLP_TRACES_*
OpenAIAgentsInstrumentor().instrument(tracer_provider=provider)

OpenInference marks agent spans with openinference.span.kind = AGENT, which is not an invoke_agent span, so turns are numbered only under a span you open yourself. The TypeScript helper below shows the attributes; in Python open the same span with tracer.start_as_current_span("invoke_agent support-bot", attributes={"gen_ai.operation.name": "invoke_agent", "gen_ai.conversation.id": conversation_id}).

Set gen_ai.conversation.id to your conversation id. A trace becomes a conversation when one of its spans carries gen_ai.conversation.id and it has invoke_agent spans. Each top-level invoke_agent span is a turn, in start order, and every model call under it becomes a step turn:<n>.<k>; the run is marked as a conversation and its episode key is your conversation id. Turns are numbered within a trace: when each user message arrives as its own trace, each shows as its own one-turn conversation run, and the shared conversation id groups them. If your framework does not emit an invoke_agent span itself, wrap each user message in one.


import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("support-bot");

// One span per user message. The model calls your framework makes inside it
// become this conversation's turn steps; the conversation id joins the runs.
export function turn<T>(conversationId: string, fn: () => Promise<T>): Promise<T> {
  return tracer.startActiveSpan(
    "invoke_agent support-bot",
    { attributes: { "gen_ai.operation.name": "invoke_agent", "gen_ai.conversation.id": conversationId } },
    async (span) => {
      try {
        return await fn();
      } finally {
        span.end();
      }
    },
  );
}

What shows up

What does not show up

More on the OTLP door: https://modelrig.dev/otlp-ingest.html.