Repository navigation
fix(agent-sessions): ingest detection and attribute decoding - #1127
Conversation
…pt scopes of known frameworks Symptom: sessions from OpenInference's LangChain and LlamaIndex instrumentors, the .NET Agent Framework, Strands TypeScript under a custom service.name and OpenInference's TypeScript OpenAI Agents instrumentor were listed as "Unidentified" (unknown:genai / unknown:openinference per span). Cause: scope_facts matched none of these scopes. OpenInference's langchain and llama_index scopes only set the generic foreign-OpenInference flag; `Experimental.Microsoft.Agents.AI` and `@arizeai/openinference-instrumentation-openai-agents` matched nothing; the Strands TS SDK names both its tracer and `gen_ai.provider.name` (or `gen_ai.system`) after the service, so a custom service.name hid its "strands-agents" marker. Fix: map the scopes to langchain, llamaindex, microsoft_agent_framework and openai_agents_sdk; detect Strands when the scope and the provider both equal service.name. langchain and llamaindex also read OpenInference's `session.id` and the `gen_ai.conversation.id` dual-write as session keys. Seen in: docs_langchain_a, docs_llamaindex_a, docs_microsoft-agent-framework_net, docs_strands_ts, docs_openai-agents_ts.
…and read runtimeContext session ids
Symptom: with the AI SDK v7 OpenTelemetry integration, chat, invoke_agent
and execute_tool spans fell to unknown:genai unless the app enabled the
`usage` supplemental attributes, and an id passed as
`runtimeContext: { sessionId }` did not group the session, so the guide had
to require `usage: true`, `runtimeContext: true` and an `enrichSpan` hook.
Cause: detect_vercel_ai_sdk required an `ai.*` key even inside the SDK's own
scope; v7 writes those keys only for opted-in supplemental attributes. The
session keys did not include `ai.settings.context.*`, where the SDK writes
runtime context.
Fix: inside the `ai`/`gen_ai` scopes a `gen_ai.operation.name` is enough.
`ai.settings.context.sessionId` and `ai.settings.context.conversationId` are
read after `gen_ai.conversation.id`.
Seen in: docs_vercel-ai-sdk_a / _b (ai 7.0.54, @ai-sdk/otel tracer "gen_ai").
Symptom: Spring AI chat calls through a non-OpenAI starter (Anthropic, Ollama, Bedrock, ...) fell to unknown:genai whenever the call had no tools, so the session lost its framework. Cause: detect_spring_ai only accepted a bare chat span in the org.springframework.boot scope when `gen_ai.system=openai`. A model starter's chat observation carries only `gen_ai.*`, with its own provider as `gen_ai.system`; `spring.ai.*` keys appear only when tools are attached. Fix: any `gen_ai.operation.name` in the Boot scope is Spring AI. The scope's other spans (HTTP server/client) carry no operation name. Seen in: docs_spring-ai_a / _b / _agent (chat spans without tools carry no spring.ai.* key).
…I SDK evidence Symptom: any span with `gen_ai.operation.name=agent_step` was stamped vercel_ai_sdk, whatever emitted it. The LangChain and LlamaIndex guides tried `agent_step` for their step spans and had to switch to `invoke_workflow` because the spans turned into Vercel AI SDK spans. Cause: detect_vercel_ai_sdk accepted `agent_step` on its own (ai_session.rs:1210). The op is the AI SDK's name for its step span, not a value only the SDK can write. Fix: drop the clause. The SDK's own step spans are still detected through its `gen_ai` scope (previous commit), and a step op from any other emitter takes the generic path. Seen in: docs_langchain_a / docs_llamaindex_a guide processors (step spans).
…e valid UTF-8 Symptom: LangChain traced through LangSmith's OTel export showed hex blobs as its transcript: `gen_ai.prompt` / `gen_ai.completion` arrived as hex strings on every span, so the transcript was unreadable, turns had no label and tool results read "not captured". Cause: any_value_string (apps/ingest/src/telemetry.rs:4019) hex-encoded every OTLP bytesValue. LangSmith 0.14 sends those two keys as bytes holding UTF-8 JSON. Fix: a bytes value that is valid UTF-8 is stored as that text; anything else keeps the hex form. The local-mode encoder in apps/cli (a port of the Rust encoder) does the same. Seen in: docs_langchain_ls (50 spans, both keys bytesValue).
… of escaped strings Symptom: a structured attribute value, such as `gen_ai.input.messages` sent as an OTLP array of maps (the form the GenAI semconv describes), was stored as an array of escaped JSON strings, so no reader could decode the messages. Guides had to tell emitters to send JSON strings. Cause: any_value_string (apps/ingest/src/telemetry.rs:4020-4027) turned every array element and map value into a string first, so each nesting level was re-encoded as a string inside the outer JSON. Fix: arrays and maps render through any_value_json, which keeps nested arrays and maps as JSON. Scalars keep their string form, so every flat array or map (the only shapes in the captures: `gen_ai.response.finish_reasons`, `agno.tools`, ...) is stored byte for byte as before. The local-mode encoder in apps/cli does the same. Seen in: none of the 113 captures sends a nested value on a span (checked every array, map and bytes attribute); the fix is for SDKs that emit the structured form.
Symptom: every "Test Connection" click in OpenRouter's Broadcast settings created a junk agent session (trace:e6d594d2..., vendor openrouter, 3 spans, 0 LLM calls). Cause: detect_openrouter matched on the scope alone, and the scope forces predicate evaluation, so the dashboard's attribute-less `openrouter-connection-test` span was stamped like a generation. Fix: an OpenRouter span needs a `gen_ai.operation.name`. Every generation, provider attempt and moderation span in the capture carries one; the connection-test span carries no attributes at all. Seen in: capture `openrouter` (3 connection-test spans on trace id 0...01).
Maple reviewConfidence 4/5 · likely safe to merge Extends AI-session detection (OpenInference/.NET/TS scopes, Strands under a custom service name, Vercel v7 and Spring AI from an operation name, bare OpenRouter spans) and makes bytes and nested structured attributes decode as text/JSON instead of hex and escaped strings. The Rust and TS behaviour is tested for both, and the change is otherwise safe to merge.
FindingsNote · F1 ·
|
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe CLI and ingest OTLP converters now decode qualifying UTF-8 bytes and preserve nested arrays and maps as JSON. AI span detection now recognizes additional instrumentation scopes, applies updated provider classification rules, and checks additional session ID attributes. ChangesAnyValue Formatting
AI Span Detection and Sessions
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The changed telemetry formatting and AI span attribution have no identified issue that blocks merging after normal checks. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Newly recognized agent spans may lose framework-specific detail decoding if the corresponding reader support is not deployed first. Ingestion remains tied to the authenticated organization, and this review did not establish a cross-tenant access path. Retained concerns
Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| "openinference.instrumentation.openai_agents" | ||
| | "@arizeai/openinference-instrumentation-openai-agents" => facts.openai_agents = true, |
There was a problem hiding this comment.
🔴 OpenAI Agents session details disappear
When openai_agents_sdk stamps TypeScript OpenInference spans, resolveAiIntegration falls back to GenAI. OpenInference-only messages and operation kinds disappear from session details.
Learn more
The gateway stamps a vendor on each span, and the session reader uses that vendor to choose attribute decoders. The new scope marks TypeScript OpenAI Agents spans as openai_agents_sdk. The integration registry only registers the OpenInference decoder for unknown:openinference and openinference-openai, so resolveAiIntegration chooses the generic decoder instead. OpenInference-only fields then do not become GenAI fields even though they remain in storage.
Example: A TypeScript Agents span has openinference.span.kind=CHAIN and input.value containing a prompt. It previously carried unknown:openinference, which decoded that prompt; now it carries openai_agents_sdk, whose default integration does not read input.value.
Recommended fix: Register the OpenInference integration for the newly classified OpenInference vendors in AI_VENDOR_INTEGRATIONS, including openai_agents_sdk, langchain, and llamaindex where their spans use that dialect. Update projection/decoder tests for these stamps and ship this with the classification change.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Known and gated rather than fixed here: #1121 registers the OpenInference integration under openai_agents_sdk, langchain and llamaindex, and this PR is gated to merge after it (merge-order note at the top of the PR body). The registry change lives in #1121 to avoid two branches editing AI_VENDOR_INTEGRATIONS.
| || (c.scope.matches_service_name | ||
| && (c.ev.gen_ai_provider_name == c.resource.service_name | ||
| || c.ev.gen_ai_system == c.resource.service_name)) |
There was a problem hiding this comment.
🟡 Generic AI spans mislabeled as Strands
When a service-named scope reports its service as provider, detect_strands labels its spans Strands without SDK evidence. Generic AI spans then appear under the wrong vendor.
Learn more
A scope identifies the component that creates spans, while service.name identifies the process. The new branch accepts equality between those names and a GenAI provider or system value as sufficient evidence for Strands. Neither value identifies the SDK: an application may name its own tracer and GenAI provider after the service. The ordered vendor predicates then classify that span as Strands before the generic tier can handle it.
Example: A custom app emits a chat span under scope my-agent, sets resource service.name=my-agent, and sets gen_ai.provider.name=my-agent. The span gets vendor strands, though no Strands SDK is present; unknown:genai was the generic classification.
Recommended fix: Require an additional Strands-specific marker for the custom-service fallback, or establish a scope/attribute signature that distinguishes Strands TS from other service-named tracers; test the same three matching strings from a non-Strands emitter.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in 5f8fa10: the custom-service rule now also requires gen_ai.event.start_time, a non-semconv key the Strands TS tracer writes on every span. A test covers an app that names its tracer and provider after its service without that key (stays unknown:genai).
… not unknown Review fix for #18: effect-lint rejects a function returning `unknown`. anyValueJson now returns the named AttrJson type (string, array or map of the same).
Maple reviewConfidence 5/5 · safe to merge The local-mode OTLP encoder now decodes
FindingsNote · F2 ·
|
…ey are valid UTF-8 Review fix for #16. Binary that happens to be valid UTF-8 was stored as control-character text: all-zero bytes became NULs instead of "", and [0x01, 0x02, 0x7f] became "\u0001\u0002\u007f" instead of "01027f". The text path now also requires no control characters other than tab, LF and CR, on both the Rust encoder and its apps/cli port. The port's TextDecoder also keeps a leading BOM (`ignoreBOM`), as Rust's from_utf8 does.
Maple reviewConfidence 4/5 · likely safe to merge Adds
Still open from earlier reviews
Fixed since the last review
What was checked
|
…m-service Strands TS rule Review fix for #1. The custom-service fallback accepted any span whose scope and gen_ai provider (or system) both equal service.name, so an app that names its own tracer and provider after its service would be read as Strands. The rule now also requires `gen_ai.event.start_time`, a non-semconv key the Strands TS tracer writes on every span (docs_strands_ts: invoke_agent, chat, execute_tool).
Maple reviewConfidence 4/5 · likely safe to merge The head commit narrows the custom-service Strands rule in
Still open from earlier reviews
What was checked
|
…blind runs Blind runs of 11 frameworks (a fresh agent applying only the skill to an open-source example) surfaced stale Maple limitations and missing setup guidance. - Remove statements fixed by #1120, #1121, #1122 and #1127 (2x token totals, Unidentified vendors, dropped plain-text tool payloads, OpenInference transcripts, check-headline caveats) from the skills and docs pages. - Add to every skill: wrong-region 401 hint, load .env before the exporter, fail fast on a missing key, verification without Maple access, a driver for apps without a scriptable entry point, and a non-crashing TS shutdown. - Apply the verified framework-specific fixes for vercel-ai-sdk, cloudflare-agents, mastra, langchain, openai-agents, google-adk, claude-agent-sdk and pydantic-ai.
…1115) * docs(agent-tracing): per-framework agent tracing guides and skills (WIP) * docs(agent-tracing): apply verifier fixes from end-to-end runs of every guide * docs(agent-tracing): editorial pass, cross-links from instrumentation and onboarding * docs(agent-tracing): align guides with what Agent Sessions shows for each framework * docs(agent-tracing): cut the human guides to a 5-minute setup, move detail into the skills * docs(agent-tracing): trim the Vercel AI SDK setup to three packages, keep the span processor variant for serverless * docs(agent-tracing): drop package trivia from the Vercel AI SDK install step * docs(agent-tracing): say when to use the OpenTelemetry guide instead of listing languages * docs(agent-tracing): cut claims a reader setting up tracing doesn't need * docs(agent-tracing): one quick-setup wording across guides, with the EU region hint * feat(docs): render install commands as npm/pnpm/bun and pip/uv tabs * docs(agent-tracing): TypeScript for LangChain.js, OpenAI Agents, ADK, Cloudflare Agents and Genkit; filter guides by language * docs(agent-tracing): list guides per language on the overview without a selector * docs(agent-tracing): list the any-language guide once, under other languages and frameworks * docs(agent-tracing): say what each Cloudflare Agents package is for * skills(agent-tracing): tell agents how to send redacted feedback on a skill * skills(agent-tracing): send feedback through the MCP only * skills(agent-tracing): drop the feedback section for now * skills(agent-tracing): drop fixed Maple gaps, add setup gotchas from blind runs Blind runs of 11 frameworks (a fresh agent applying only the skill to an open-source example) surfaced stale Maple limitations and missing setup guidance. - Remove statements fixed by #1120, #1121, #1122 and #1127 (2x token totals, Unidentified vendors, dropped plain-text tool payloads, OpenInference transcripts, check-headline caveats) from the skills and docs pages. - Add to every skill: wrong-region 401 hint, load .env before the exporter, fail fast on a missing key, verification without Maple access, a driver for apps without a scriptable entry point, and a non-crashing TS shutdown. - Apply the verified framework-specific fixes for vercel-ai-sdk, cloudflare-agents, mastra, langchain, openai-agents, google-adk, claude-agent-sdk and pydantic-ai. * docs(agent-tracing): drop stale payload rules, build the Claude SDK env per call - opentelemetry: tool results may be plain strings; Maple no longer drops plain-text tool payloads. - claude-agent-sdk: build the telemetry env per query() and fail fast on a missing key, matching the skill. * docs(agent-tracing): drop setup steps Maple no longer needs - GenAI semconv flag is recommended, not required, for LangChain (Python), LlamaIndex and smolagents; openai-agents keeps it for agent lanes and finish reasons - smolagents: stop zeroing run-span token usage - Vercel AI SDK / Cloudflare Agents: runtimeContext groups sessions, drop enrichSpan - Genkit: pass string tool results through unwrapped - LangChain.js: correct the GenAiSpans rationale - Strands TS: note zero tokens with api: "chat" behind OpenAI-compatible gateways * skills(agent-tracing): pass LangChain.js tool results through as plain text * docs(agent-tracing): drop workarounds and caveats the agent-session fixes made stale - Session keys: every vendor now falls back to gen_ai.conversation.id; drop the "Maple ignores gen_ai.conversation.id" lines (agno, crewai, dspy, smolagents, spring-ai, strands) and the OpenInference Haystack session.id caveat. - Tokens: usage counts only on the model-call span, so drop Strands' gen_ai_use_latest_invocation_tokens, the per-request TS agent rationale and the 1.54 floor, pydantic-ai's aggregated-usage warning and the Anthropic cache double-count caveats. Hand-written spans send semconv totals (input includes cache, output includes reasoning); the Anthropic/Gemini mappings and the ADK TS processor follow that. - Cost: LiteLLM's litellm.cost.total and Pydantic AI's operation.cost are read; drop the LiteLLM turn-cost recipe. - Detection: LangChain.js, Genkit and .NET Semantic Kernel get their framework label; OpenAI Agents TS gets agent lanes; LangChain.js groups by session.id without GenAiSpans' conversation-id copy. - Classification: drop DSPy's adapter marker, Spring AI's advisor rename, LangChain's ChatPromptTemplate step, the MAF workflow.build instruction, Mastra scorer and LangChain turn-label caveats, and MapleSpanFixes' tool argument fix (the tool-errors view decodes arguments like the session page). * skills(agent-tracing): leave ADK TS output tokens as reported; Maple adds thinking * skills(agent-tracing): trim to what an implementing agent needs (#1174) - cut human-guide links, backend background, tested-version notes, restated code - drop the Go reference; other languages follow the generic steps - Do-not lists keep only silent, non-obvious mistakes not stated in the steps - inline the GenkitForMaple processor instead of pointing at the guide - OTLP header: quoted literal space everywhere (every targeted SDK accepts it) - add Cloudflare Agents and Genkit to the OpenTelemetry skill's framework list - smolagents: enable_genai_semconv is required * docs(agent-tracing): ADK header uses a quoted literal space, not %20 * docs(agent-tracing): keep tool error text out of Haystack spans with content off, note Spring AI's * docs(agent-tracing): export ADK env vars, name MAPLE_INGEST_KEY, drop the removed Go reference * skills(agent-tracing): tolerate malformed tool arguments, route provider SDKs through the router, raw skill URL * docs(agent-tracing): tighten the OpenTelemetry guide's intro, content and check wording * docs(agent-tracing): use the private ingest key from the environment, never inline Agent tracing runs server-side, so guides and skills now use the private key (maple_sk_) as MAPLE_INGEST_KEY in the repo's secret/env convention. The user sets it themselves instead of pasting it into the prompt; skills create a gitignored .env and .env.example when the repo has none. * docs(onboard): servers use the private ingest key from MAPLE_INGEST_KEY, browsers the public key maple-onboard and the language style skills now read the private key (maple_sk_) from MAPLE_INGEST_KEY on servers, with the agent-tracing secret rules: repo secret/env convention, gitignored .env plus .env.example when there is none, fail fast when unset, never in source or asked for in chat. Browser and mobile code keep the inline public key. Landing docs and the agent-tracing overview prompt follow. * docs(agent-tracing): warn and disable export when MAPLE_INGEST_KEY is unset Instrumentation must never crash or block the app. Replace every throw, exit, panic and ${VAR:?} on a missing key with one warning plus a skipped Maple exporter, and never send an empty bearer. * skills(agent-tracing): read the Spring key from Boot's environment so .env works * Revert "skills(agent-tracing): read the Spring key from Boot's environment so .env works" This reverts commit dc45d34. * Revert "docs(agent-tracing): warn and disable export when MAPLE_INGEST_KEY is unset" This reverts commit 307aa7a. * Revert "docs(onboard): servers use the private ingest key from MAPLE_INGEST_KEY, browsers the public key" This reverts commit 814e177. * Revert "docs(agent-tracing): use the private ingest key from the environment, never inline" This reverts commit 821d778. * docs(agent-tracing): warn and disable export when the ingest key is unset or setup fails Instrumentation must never crash or block the app. Code that reads MAPLE_INGEST_KEY logs one warning and skips the Maple exporter instead of throwing, exiting or panicking, and Go/Rust setup errors are logged, not fatal.
The ingest gateway's AI detection (
apps/ingest/src/ai_session.rs) missed several frameworks, and the OTLP value encoder (apps/ingest/src/telemetry.rs) made some attribute values unreadable. Every fix below is checked against the real OTLP captures wherever one exercises it. Replaying all 113 captures throughstamp_trace_request, before and after, changes vendor stamps only in the captures listed here.(docs_llamaindex_a/b already read
llamaindexbecause the guide stamps a marker attribute; without the marker they take the same path as LangChain.)#1 Unfingerprinted scopes (
21cf6adcb, review fix5f8fa1043)service.nameand OpenInference's TS OpenAI Agents instrumentor all showed as "Unidentified" (table above).scope_facts(ai_session.rs:319) did not knowopeninference.instrumentation.langchain/.llama_index(they only set the foreign-OpenInference flag, :347),Experimental.Microsoft.Agents.AIor@arizeai/openinference-instrumentation-openai-agents. The Strands TS SDK names its tracer andgen_ai.provider.name(orgen_ai.system) afterservice.name(@strands-agents/sdktelemetry/tracer.js_getCommonAttributes), so a custom service name hides thestrands-agentsmarkerdetect_strands(:1183) keys on.langchain,llamaindex,microsoft_agent_frameworkandopenai_agents_sdk.service.nameand the span carries the Strands-onlygen_ai.event.start_time.langchainandllamaindexalso read OpenInference'ssession.idand thegen_ai.conversation.iddual-write as session keys. The session ids in the captures are unchanged.opentelemetry.instrumentation.genai.*): kept generic (unknown:genai).invoke_agentspan carries the session, and that span decides the session's vendor, so a vendor id on the chat spans would not name the session anyway.instrumentor_scopes_name_their_framework,strands_typescript_is_detected_under_a_custom_service_name.unknown:openinferencetoday. fix(agent-sessions): transcript and tool payload decoding #1121 registers it underlangchain,llamaindexandopenai_agents_sdk. Until fix(agent-sessions): transcript and tool payload decoding #1121 lands, the reclassified spans decode through the default GenAI integration. The Python LangChain/LlamaIndex spans dual-writegen_ai.*, so they are unaffected. The OpenAI Agents TS spans (OpenInference keys only) would lose their detail-page decoding, hence the merge-order gate at the top.@arizeai/openinference-instrumentation-langchain) are not fingerprinted either. No capture exercises them, so they are left for a follow-up.#2 Vercel AI SDK v7 spans without
ai.*keys, andruntimeContextsession ids (26ede77a5)eve_slack, 30 of 122 AI SDK spans (chat / invoke_agent / execute_tool of the v7 tracer) landed inunknown:genai.vercel_ai_sdk_usersentai.settings.context.sessionId, yet none of its 28 spans got a session (now 17 do). The guide therefore requiredusage: true,runtimeContext: trueand anenrichSpanhook.detect_vercel_ai_sdk(ai_session.rs:1208) required anai.*key even inside the SDK's own scope.@ai-sdk/otelwrites those keys only for opted-in supplemental attributes. The session keys (:956) did not includeai.settings.context.*, where the SDK writes runtime context (@ai-sdk/otelgetRuntimeContextAttributes).ai/gen_aiscopes, agen_ai.operation.nameis enough.ai.settings.context.sessionIdandai.settings.context.conversationIdare read, ranked aftergen_ai.conversation.id.vercel_v7_spans_without_ai_keys_and_runtime_context_sessions.#4 Spring AI chat spans of non-OpenAI starters (
4ef77a3bb)unknown:genaiunlessgen_ai.system=openai.detect_spring_ai(ai_session.rs:1205) accepted a bare chat span in theorg.springframework.bootscope only for OpenAI. A model starter's chat observation carriesgen_ai.*only, with its own provider asgen_ai.system.spring.ai.*keys appear only with tools (docs_spring-ai_a: the chat span without tools has none).gen_ai.operation.namein the Boot scope is Spring AI. The scope's HTTP spans have no operation name.spring_ai_chat_spans_of_any_model_starter.#6
agent_stepread as Vercel AI SDK evidence (9c37e53e1)gen_ai.operation.name=agent_stepbecamevercel_ai_sdk. The LangChain and LlamaIndex guide processors had to useinvoke_workflowfor their step spans instead.detect_vercel_ai_sdkaccepted the op on its own (ai_session.rs:1210).agent_step_outside_the_vercel_scope_is_not_vercel.#16 OTLP bytes attributes stored as hex (
d09f70b25, review fixf3e1be3d7)gen_ai.prompt/gen_ai.completionstored as hex, so the content was unreadable.any_value_string(telemetry.rs:4019) hex-encoded everybytesValue. LangSmith 0.14 sends those keys as UTF-8 JSON bytes."".apps/cli/src/server/otlp/encode.ts, a port of the Rust one) does the same. Like Rust, it keeps a leading BOM.bytes_attributes_decode_as_text_when_valid_utf8(Rust), "decodes bytes attributes as text…" (encode.test.ts). Both cover the JSON text, invalid UTF-8, control-byte binary, all-zero and BOM cases.{"messages": …}/{"generations": …}).#18 Structured attribute values (
8995b9352)gen_ai.response.finish_reasons,agno.tools, ...), and only ADK log bodies are maps.gen_ai.input.messagesin structured form. Such a value was stored as an array of escaped JSON strings, which no reader decodes.any_value_string(telemetry.rs:4020-4027) turned every element and map value into a string first, so each nesting level was re-encoded.any_value_json, which keeps nested arrays and maps as JSON. Scalars keep their string form, so every flat array or map is stored exactly as before. The CLI port matches, up to map key order: Rust sorts keys, TS keeps insertion order. That difference already existed for flat maps.structured_attributes_stay_json_when_nested(Rust, including the unchanged flat cases), "keeps nested arrays and maps as JSON" (encode.test.ts).#22 OpenRouter connection-test span stamped as AI (
d08eaf7da)trace:e6d594d2…, vendor openrouter, 3 spans, 0 LLM calls).detect_openrouter(ai_session.rs:1100) matched on the scope alone.gen_ai.operation.name. All 3,965 generation, attempt and moderation spans in the capture carry one; the connection-test span carries no attributes.openrouter_connection_test_span_is_not_ai.Notes
ai-vendors.tsare untouched.cargo test --lib ai_session,cargo test --lib telemetry,bun test src/server/otlp/encode.test.ts(apps/cli).2d4ae4202types the CLI port'sanyValueJsonas a named JSON type instead ofunknown(effect-lint).stamp_trace_request. This PR does not touch that function.Summary by CodeRabbit