Created
April 22, 2026 08:17
-
-
Save sagikazarmark/ecc70619d4cbaeb456e3167c4dd96e89 to your computer and use it in GitHub Desktop.
Claude Code OpenInference OTEL connector config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| receivers: | |
| otlp: | |
| protocols: | |
| grpc: | |
| endpoint: 0.0.0.0:4317 | |
| http: | |
| endpoint: 0.0.0.0:4318 | |
| processors: | |
| # Drop traces from Docker Compose itself (it picks up OTEL_EXPORTER_OTLP_ENDPOINT | |
| # from the environment and sends its own cli/logs, cli/ps, HEAD /_ping traces) | |
| filter: | |
| error_mode: ignore | |
| traces: | |
| span: | |
| - 'resource.attributes["service.name"] == "compose"' | |
| transform: | |
| trace_statements: | |
| - context: span | |
| statements: | |
| ## ─── Claude Code → OpenInference ─────────────────────────── | |
| # Span kind mapping | |
| - set(attributes["openinference.span.kind"], "LLM") where attributes["span.type"] == "llm_request" | |
| - set(attributes["openinference.span.kind"], "AGENT") where attributes["span.type"] == "interaction" | |
| - set(attributes["openinference.span.kind"], "TOOL") where attributes["span.type"] == "tool" | |
| - set(attributes["openinference.span.kind"], "TOOL") where attributes["span.type"] == "tool.execution" | |
| - set(attributes["openinference.span.kind"], "CHAIN") where attributes["span.type"] == "tool.blocked_on_user" | |
| - set(attributes["openinference.span.kind"], "CHAIN") where attributes["span.type"] == "hook" | |
| # LLM attributes | |
| - set(attributes["llm.model_name"], attributes["model"]) where attributes["span.type"] == "llm_request" | |
| - set(attributes["llm.token_count.prompt"], attributes["input_tokens"]) where attributes["span.type"] == "llm_request" | |
| - set(attributes["llm.token_count.completion"], attributes["output_tokens"]) where attributes["span.type"] == "llm_request" | |
| # Cache token counts | |
| - set(attributes["llm.token_count.cache_read"], attributes["cache_read_tokens"]) where attributes["span.type"] == "llm_request" | |
| - set(attributes["llm.token_count.cache_creation"], attributes["cache_creation_tokens"]) where attributes["span.type"] == "llm_request" | |
| # Tool attributes | |
| - set(attributes["tool.name"], attributes["tool_name"]) where attributes["span.type"] == "tool" | |
| - set(attributes["input.value"], attributes["full_command"]) where attributes["span.type"] == "tool" and attributes["full_command"] != nil | |
| - set(attributes["input.value"], attributes["file_path"]) where attributes["span.type"] == "tool" and attributes["file_path"] != nil and attributes["full_command"] == nil | |
| # Interaction input (if user_prompt is present via OTEL_LOG_USER_PROMPTS=1) | |
| - set(attributes["input.value"], attributes["user_prompt"]) where attributes["span.type"] == "interaction" | |
| # Set span status from success attribute (Claude Code leaves status Unset) | |
| - set(status.code, 1) where attributes["success"] == true | |
| - set(status.code, 2) where attributes["success"] == false | |
| - set(status.message, attributes["error"]) where attributes["success"] == false and attributes["error"] != nil | |
| ## ─── OTel GenAI semconv (gen_ai.*) → OpenInference ───────── | |
| ## For agents that use standard gen_ai.* attributes | |
| # Span kind | |
| - set(attributes["openinference.span.kind"], "LLM") where attributes["gen_ai.system"] != nil | |
| # Model | |
| - set(attributes["llm.model_name"], attributes["gen_ai.request.model"]) where attributes["gen_ai.request.model"] != nil | |
| # Token counts | |
| - set(attributes["llm.token_count.prompt"], attributes["gen_ai.usage.input_tokens"]) where attributes["gen_ai.usage.input_tokens"] != nil | |
| - set(attributes["llm.token_count.completion"], attributes["gen_ai.usage.output_tokens"]) where attributes["gen_ai.usage.output_tokens"] != nil | |
| ## ─── OpenCode / Vercel AI SDK (ai.*) → OpenInference ─────── | |
| ## OpenCode uses Vercel AI SDK which emits ai.* attributes | |
| # Span kind mapping by span name | |
| - set(attributes["openinference.span.kind"], "LLM") where name == "ai.streamText" | |
| - set(attributes["openinference.span.kind"], "LLM") where name == "ai.streamText.doStream" | |
| - set(attributes["openinference.span.kind"], "LLM") where name == "ai.generateText" | |
| - set(attributes["openinference.span.kind"], "LLM") where name == "ai.generateText.doGenerate" | |
| - set(attributes["openinference.span.kind"], "TOOL") where name == "ai.toolCall" | |
| # Model | |
| - set(attributes["llm.model_name"], attributes["ai.model.id"]) where attributes["ai.model.id"] != nil | |
| - set(attributes["llm.model_name"], attributes["ai.model.provider"]) where attributes["ai.model.provider"] != nil and attributes["ai.model.id"] == nil | |
| # Token counts | |
| - set(attributes["llm.token_count.prompt"], attributes["ai.usage.promptTokens"]) where attributes["ai.usage.promptTokens"] != nil | |
| - set(attributes["llm.token_count.completion"], attributes["ai.usage.completionTokens"]) where attributes["ai.usage.completionTokens"] != nil | |
| # LLM input/output content | |
| - set(attributes["input.value"], attributes["ai.prompt.messages"]) where attributes["ai.prompt.messages"] != nil | |
| - set(attributes["output.value"], attributes["ai.response.text"]) where attributes["ai.response.text"] != nil | |
| # Tool attributes | |
| - set(attributes["tool.name"], attributes["ai.toolCall.name"]) where attributes["ai.toolCall.name"] != nil | |
| - set(attributes["input.value"], attributes["ai.toolCall.args"]) where attributes["ai.toolCall.args"] != nil | |
| - set(attributes["output.value"], attributes["ai.toolCall.result"]) where attributes["ai.toolCall.result"] != nil | |
| # Tool definitions (for context in Phoenix detail view) | |
| - set(attributes["tool.parameters"], attributes["ai.prompt.tools"]) where attributes["ai.prompt.tools"] != nil | |
| ## ─── Codex CLI (codex.*) → OpenInference ─────────────────── | |
| ## Codex uses custom codex.* attributes. Most data is in log | |
| ## events, not trace spans — these cover what lands on spans. | |
| ## UNVERIFIED: needs validation with debug exporter + real Codex traces. | |
| # Session/exec spans | |
| - set(attributes["openinference.span.kind"], "AGENT") where name == "codex.session" | |
| - set(attributes["openinference.span.kind"], "AGENT") where name == "codex.exec" | |
| # Model (common attribute on codex events/spans) | |
| - set(attributes["llm.model_name"], attributes["model"]) where resource.attributes["service.name"] == "codex-cli" and attributes["model"] != nil | |
| # Token counts (from codex.sse_event spans/events) | |
| - set(attributes["llm.token_count.prompt"], attributes["input_token_count"]) where attributes["input_token_count"] != nil | |
| - set(attributes["llm.token_count.completion"], attributes["output_token_count"]) where attributes["output_token_count"] != nil | |
| - set(attributes["llm.token_count.cache_read"], attributes["cached_token_count"]) where attributes["cached_token_count"] != nil | |
| # Tool results | |
| - set(attributes["tool.name"], attributes["tool_name"]) where resource.attributes["service.name"] == "codex-cli" and attributes["tool_name"] != nil | |
| - set(attributes["input.value"], attributes["arguments"]) where resource.attributes["service.name"] == "codex-cli" and attributes["arguments"] != nil | |
| - set(attributes["output.value"], attributes["output"]) where resource.attributes["service.name"] == "codex-cli" and attributes["output"] != nil | |
| # Prompt content (requires otel.log_user_prompt = true in codex config) | |
| - set(attributes["input.value"], attributes["prompt"]) where resource.attributes["service.name"] == "codex-cli" and attributes["prompt"] != nil | |
| # Status | |
| - set(status.code, 1) where resource.attributes["service.name"] == "codex-cli" and attributes["success"] == true | |
| - set(status.code, 2) where resource.attributes["service.name"] == "codex-cli" and attributes["success"] == false | |
| # Promote SpanEvent content to span attributes so Phoenix can display them | |
| - context: spanevent | |
| statements: | |
| - set(span.attributes["output.value"], attributes["output"]) where name == "tool.output" and attributes["output"] != nil | |
| - set(span.attributes["output.value"], attributes["diff"]) where name == "tool.output" and attributes["diff"] != nil and attributes["output"] == nil | |
| # Transform all agent formats → gen_ai.* semconv for Langfuse | |
| transform/genai: | |
| trace_statements: | |
| - context: span | |
| statements: | |
| ## ─── Claude Code → gen_ai.* ──────────────────────────────── | |
| - set(attributes["gen_ai.system"], "anthropic") where attributes["span.type"] == "llm_request" | |
| - set(attributes["gen_ai.request.model"], attributes["model"]) where attributes["span.type"] == "llm_request" | |
| - set(attributes["gen_ai.usage.input_tokens"], attributes["input_tokens"]) where attributes["span.type"] == "llm_request" | |
| - set(attributes["gen_ai.usage.output_tokens"], attributes["output_tokens"]) where attributes["span.type"] == "llm_request" | |
| ## ─── OpenCode / Vercel AI SDK → gen_ai.* ─────────────────── | |
| - set(attributes["gen_ai.system"], "openai") where attributes["ai.model.provider"] != nil | |
| - set(attributes["gen_ai.request.model"], attributes["ai.model.id"]) where attributes["ai.model.id"] != nil | |
| - set(attributes["gen_ai.usage.input_tokens"], attributes["ai.usage.promptTokens"]) where attributes["ai.usage.promptTokens"] != nil | |
| - set(attributes["gen_ai.usage.output_tokens"], attributes["ai.usage.completionTokens"]) where attributes["ai.usage.completionTokens"] != nil | |
| ## ─── Codex CLI → gen_ai.* ────────────────────────────────── | |
| - set(attributes["gen_ai.system"], "openai") where resource.attributes["service.name"] == "codex-cli" and attributes["model"] != nil | |
| - set(attributes["gen_ai.request.model"], attributes["model"]) where resource.attributes["service.name"] == "codex-cli" and attributes["model"] != nil | |
| - set(attributes["gen_ai.usage.input_tokens"], attributes["input_token_count"]) where attributes["input_token_count"] != nil | |
| - set(attributes["gen_ai.usage.output_tokens"], attributes["output_token_count"]) where attributes["output_token_count"] != nil | |
| # gen_ai.* passthrough — already correct, no transform needed | |
| exporters: | |
| otlp/phoenix: | |
| endpoint: phoenix:4317 | |
| tls: | |
| insecure: true | |
| otlp/tempo: | |
| endpoint: tempo:4317 | |
| tls: | |
| insecure: true | |
| # Langfuse: OTLP HTTP only, requires Basic Auth | |
| otlphttp/langfuse: | |
| endpoint: http://langfuse-web:3000/api/public/otel | |
| headers: | |
| Authorization: "Basic cGstbGYtYWlvMTF5OnNrLWxmLWFpbzExeQ==" | |
| debug: | |
| verbosity: detailed | |
| service: | |
| pipelines: | |
| # Phoenix: transformed to OpenInference | |
| traces/phoenix: | |
| receivers: [otlp] | |
| processors: [filter, transform] | |
| exporters: [otlp/phoenix, debug] | |
| # Tempo: raw traces, original attributes preserved | |
| traces/tempo: | |
| receivers: [otlp] | |
| processors: [filter] | |
| exporters: [otlp/tempo] | |
| # Langfuse: transformed to gen_ai.* semconv | |
| traces/langfuse: | |
| receivers: [otlp] | |
| processors: [filter, transform/genai] | |
| exporters: [otlphttp/langfuse] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment