Skip to content

docs(telemetry): pin the span contract the TypeScript SDK already implements - #27

Draft
apucacao wants to merge 2 commits into
mainfrom
ag/py-telemetry-contract
Draft

docs(telemetry): pin the span contract the TypeScript SDK already implements#27
apucacao wants to merge 2 commits into
mainfrom
ag/py-telemetry-contract

Conversation

@apucacao

@apucacao apucacao commented Aug 11, 2026

Copy link
Copy Markdown

Writes down the span contract the TypeScript SDK already implements, so the six Python handler packages can be checked against one document instead of against six readings of another SDK.

No code. This is the specification the rest of the stack is built from.

  • Derived from js-ai-sdk at 5178db1. Where this file and that source disagree, the source wins and this file is the bug.
  • Every gen_ai.* and launchdarkly.* literal in the TypeScript source appears here, verified by diffing the two sets.
  • Two independent reviews, one against the TypeScript source and one against the Python source, found nine defects in an earlier draft. All nine are fixed here.

Three of those defects would have produced wrong spans across several handlers:

  1. gen_ai.response.model is the requested model on four of six handlers, not the answering model. Only openai-messages reads it off the response.
  2. The two OpenAI handlers use the Responses API, which has no finish_reason field. They derive the value and must not use the mapping table. An earlier draft told porters to build one table for all six.
  3. contextvars does cross asyncio.create_task. The claim that it does not was wrong, and a porter reasoning from it would draw bad conclusions elsewhere. Threads are the boundary that loses context.

Section 12 records the work an earlier draft left implicit, including the 348 existing test functions and how many assert on something this stack changes.

Where this sits

Bottom of the stack. Everything above it is checked against this file.


Note

Overview
Adds TELEMETRY-CONTRACT.md, the authoritative span/attribute contract the six Python handlers must match against the TypeScript SDK (js-ai-sdk @ 5178db1). No runtime code changes.

The doc pins the invoke_agent / chat {model} / execute_tool tree, per-handler gen_ai.* and launchdarkly.* attributes, finish-reason rules (including OpenAI Responses derivation), content-capture carriers, provider-specific token folding, and abandoned-stream teardown. It also calls out known gaps (e.g. retiring set_openllmetry_*, adding SpanUsage) and the ~121 existing tests that will need rewriting when the port lands.

Reviewed by Cursor Bugbot for commit 3f629c6. Bugbot is set up for automated code reviews on this repo. Configure here.

…lements

The TypeScript SDK reshaped its OpenTelemetry output in July: a three-level
span tree, a cache-aware token vocabulary, one finish-reason enum, and
conversation content behind an opt-in flag. Python never got any of it, and
still emits the shape TypeScript retired.

Six handler packages have to change to close that. Six agents each deriving
the vocabulary from the TypeScript source would produce six slightly
different vocabularies, which is how the TypeScript span code drifted apart
the first time. So the vocabulary is written down once, here, and the handler
work is checked against this file rather than against a reading of the other
SDK.

Derived from js-ai-sdk at 5178db1. Where this file and the TypeScript source
disagree, the source wins and this file is a bug.

Two mechanical checks stand behind it. Every `gen_ai.*` and `launchdarkly.*`
literal in the TypeScript source appears here, verified by diffing the two
sets; the only absentees are the two keys inside `setOpenLLMetryCompletion`,
which has no call sites left. And two independent reviews, one against the
TypeScript source and one against the Python source, found nine defects in
an earlier draft. All nine are fixed here, including three that would have
produced wrong spans across several handlers:

  - `gen_ai.response.model` is the requested model on four of six handlers,
    not the answering model. Only openai-messages reads it off the response.
  - The two OpenAI handlers use the Responses API, which has no
    finish_reason field. They derive the value and must not use the mapping
    table. An earlier draft told porters to build one table for all six.
  - contextvars does cross asyncio.create_task. The claim that it does not
    was wrong, and a porter reasoning from it would draw bad conclusions
    elsewhere. Threads are the boundary that loses context.

Section 12 records the work the first draft left implicit, including the
348 existing test functions and how many assert on something this changes.
@apucacao

Copy link
Copy Markdown
Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 49c8fe8. Configure here.

…y ran

The request-model row claimed the requested name for all six handlers.
claude-agents writes the model the inference actually used on its chat span, the
same value it gives gen_ai.response.model, which the TypeScript source does
deliberately: the CLI reports the model it really ran and there is no separate
per-turn requested name to report.

Stated as it was, the document made a correct handler look like a defect. A
review of #31 raised it against the handler rather than against this file, which
is the failure mode a specification has when it overgeneralises.

Its root span still writes the requested name, so only the chat span differs.
@apucacao

Copy link
Copy Markdown
Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

@apucacao

Copy link
Copy Markdown
Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3f629c6. Configure here.

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.

1 participant