OpenTelemetry

Already instrumented? Point your exporter at Agent Ping and you are done. Agent Ping accepts standard OTLP/HTTP trace exports, protobuf or JSON, and maps them onto runs and steps. No new SDK, no code changes.

The endpoint

POST https://eu.ingest.agentping.io/v1/traces

Use the host matching your data residency (eu or us). Authenticate with your team API key (apk_...) as a Bearer token; exporters that cannot set Authorization can send X-AgentPing-Key instead. Per-agent ping_ tokens do not work here; OTel traffic can create agents, so it needs the team key.

Generic exporter setup

Any OpenTelemetry SDK works with two environment variables:

export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://eu.ingest.agentping.io/v1/traces"
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="authorization=Bearer apk_eu_..."

How traces map to runs

OpenTelemetry Agent Ping
Trace Run (one run per trace; re-exports are deduplicated)
Root span The run record; its name becomes the run's goal
Span with model/provider attributes LLM call step, with tokens and server-side cost
Span with a tool name Tool call step
Any other span Step
service.name The agent (created automatically on first sight)

LLM calls are recognised across emitter conventions, not just the canonical gen_ai.* set. Agent Ping reads the model from gen_ai.request.model, gen_ai.response.model, OpenInference's llm.model_name, or the Vercel AI SDK's ai.model.id; the provider from gen_ai.system, llm.provider, or ai.model.provider; and token counts from any of gen_ai.usage.input_tokens / output_tokens, the older prompt_tokens / completion_tokens, OpenInference's llm.token_count.*, or Vercel's ai.usage.*. An OpenInference span tagged openinference.span.kind = LLM or TOOL is mapped even without gen_ai.* attributes.

Two attributes are worth setting explicitly:

  • agentping.agent on the resource overrides the agent identity when service.name is not what you want on the dashboard.
  • agentping.goal on the root span tells evaluations what the run was supposed to achieve. Setting it materially improves evaluation quality; without it the root span's name is used.

Conversations

A trace is a run. For a chat that means a run per message, which is rarely what you want: see Conversations for why the session is the better unit.

To make a trace one turn of a longer run, name the run on the trace:

"resource": {"attributes": [
  {"key": "service.name", "value": {"stringValue": "support-chat"}},
  {"key": "agentping.run", "value": {"stringValue": "run_eu_018f3a2b9c1d7e8fa4b9c2d7e8f1a3b6"}},
  {"key": "agentping.idle_timeout_seconds", "value": {"intValue": "900"}},
  {"key": "agentping.idle_timeout_status", "value": {"stringValue": "success"}}
]}
Attribute Effect
agentping.run This trace is a turn of that run, not a run of its own. Its spans become steps on it. Must be a valid run_<region>_<32 hex> id.
agentping.idle_timeout_seconds Close the run once no span has arrived for this long. OTLP has no way to say "finished", so without this a conversation stays open until you call /finish yourself.
agentping.idle_timeout_status What that silence meant. Defaults to timeout; send success for a chat.
agentping.turn On a span: which exchange of the conversation it belongs to, counting from 1. Groups the several spans of one turn.

Any of these can go on the resource (the whole service) or on a span (this turn); the span wins where both are set.

Naming the run changes two things. The settle window below no longer applies: the run is yours to end, not ours to guess at, and the root span becomes an ordinary step rather than the run record, because the run already exists.

Mixing is fine: report turns over OTLP and call POST /v1/runs/{id}/finish on the same id when the user closes the chat.

Run completion

OTel delivers spans in batches with no end-of-run signal, so a run ingested this way completes once its root span has arrived and no new span has landed for two minutes. Costs, health checks, and evaluations then run exactly as they do for SDK-reported runs.

Limits

Span attributes Agent Ping does not map are kept with the step, up to 8KB per step; beyond that the largest attributes are dropped and the step is flagged, never rejected. Prompt and completion attributes count as step content with the usual 64KB allowance.

Verified emitters

Anything that speaks OTLP/HTTP works; each needs only the endpoint and header configuration above. These are the ones we test against recorded fixtures, so an upstream attribute rename is caught by us, not by you:

Emitter What to set Mapped from
OpenLLMetry / Traceloop TRACELOOP_BASE_URL to the endpoint, plus the auth header gen_ai.*
Pydantic AI Enable instrumentation, point the OTLP exporter at the endpoint gen_ai.*
Vercel AI SDK experimental_telemetry: { isEnabled: true } plus an OTLP exporter ai.*
OpenInference (Arize Phoenix, OpenAI Agents SDK) OTLP exporter to the endpoint openinference.span.kind, llm.*
LangGraph (via OpenLLMetry's LangChain instrumentation) OTLP exporter to the endpoint gen_ai.*, node spans as steps
Laravel AI SDK (via laravel-ai-otel) OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS gen_ai.*, invoke_agent and execute_tool spans as steps

CrewAI and LlamaIndex are supported through their OpenLLMetry / OpenInference instrumentation by the same path.

Further reading

For the conceptual background, what OpenTelemetry covers for AI agents, what agent spans contain, and which operational questions OTel deliberately leaves to the backend, see OpenTelemetry for AI agents.