Skip to content

Record tool payloads of every shape on gen_ai.tool.call.* - #569

Draft
sergical wants to merge 1 commit into
withastro:mainfrom
sergical:semconv-tool-content
Draft

sergical wants to merge 1 commit into
withastro:mainfrom
sergical:semconv-tool-content

Conversation

@sergical

@sergical sergical commented Aug 9, 2026

Copy link
Copy Markdown

Summary

  • Record tool arguments/results on gen_ai.tool.call.* for every payload shape; drop the flue.tool.call.* fallback keys (FLUE_TELEMETRY_EXTENSION_REVISION → 5).
  • Best-effort JSON-parse string payloads (the spec asks for this) so serialized objects record structured and double-encoded results un-nest.
  • Update @flue/opentelemetry README and the ecosystem docs page.

Why

Fixes #568. String tool results — every MCP tool, most local tools — landed on vendor keys no backend reads, so consumers of the semconv keys showed tool inputs with no outputs. The spec types these attributes any and sanctions JSON-string form on spans; the adapters already stringify objects, so the object-only fork changed nothing but the key.

Considered and skipped: dual-writing flue.tool.call.* for a deprecation window. Both keys draw from the same per-span content pool (sized against workerd's 64 KiB span-attribute cap), so duplicating the largest payloads in the system would make truncation kick in twice as early. Easy to switch if you'd rather keep a compat window.

Verification

  • pnpm exec turbo run build --filter=@flue/runtime --filter=@flue/opentelemetry — pass
  • pnpm -w exec tsc --noEmit -p packages/runtime / -p packages/opentelemetry — clean
  • biome lint on the four changed source files — clean (@flue/runtime has no vitest files to run)
  • Against the built dist: 'hello' records as-is on gen_ai.tool.call.result; '{"a":1}' and {a: 1} record structured
  • Live against Sentry: every successful execute_tool span (MCP tools included) carries gen_ai.tool.call.result and renders in the AI trace views

🤖 Generated with Claude Code

… keys

The adapters previously routed non-object tool arguments/results to vendor
flue.tool.call.* fallback keys, on a reading of the GenAI semconv as
object-shaped-only. At the pinned revision the spec types these attributes
`any`: instrumentations SHOULD best-effort deserialize serialized strings,
and on spans the value MAY be recorded as a JSON string when a structured
format is not supported — which is the case for every OTel span attribute
store. Since objects were already JSON-stringified at emission, the fork
changed nothing but the key, and consumers that read the semconv keys
(Sentry's AI views among them) never saw tool results: most tools —
including every MCP tool — return strings.

Both backends now always record under gen_ai.tool.call.*, with best-effort
JSON deserialization of string payloads per the spec guidance. The
flue.tool.call.* keys are removed (extension revision 5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool results are invisible to OTel backends: string payloads land on flue.tool.call.* instead of gen_ai.tool.call.result

1 participant