Skip to content

Commit 0ccb68d

Browse files
committed
feat(claude-messages)!: emit invoke_agent, chat and execute_tool spans
One flat span per call, named claude.messages, becomes the tree the TypeScript SDK emits: an invoke_agent root, one `chat {model}` child per model turn, one `execute_tool {name}` child per tool call. A five-turn run with tools was previously one span with one set of token counts, so per-turn cost and latency were not recoverable from a trace at all, and a tool call left no trace beyond its LD metric event. BREAKING CHANGE: the span this handler emits is renamed from `claude.messages` and `claude.messages.stream` to `invoke_agent`. Queries that select on the old names will not match. Prompt and completion content is no longer on spans unless the caller passes capture_content=True. Span construction moved to spans.py so the tool loop reads as a tool loop rather than as span bookkeeping with a provider call in the middle. Tool spans take the root's context, not the chat span's, so they are siblings of chat rather than nested inside it. Both parents are passed explicitly: these handlers open a plain span rather than an active one, so there is no ambient span for a child to inherit, and a host app with its own tracer provider would otherwise get a flat trace. The root keeps what only it can carry: the launchdarkly.* identity, the feature_flag event, and the run's token total. It is the span a config-scoped query finds, and summing the children requires having already found them. A test asserts children carry none of it. Cache tokens now reach the span. Anthropic reports cache reads and writes beside input_tokens rather than inside it, so a turn that read 19,971 tokens from cache and wrote 3,580 more reported 3. The chat span now reports 23,554 for that turn, and there is a test with those numbers in it. The handler's return value keeps the cache fields unfolded, in Anthropic's own names, because parse_usage folds exactly once; a pre-folded figure returned alongside the fields would count the cache twice downstream. RawRunUsage carries that shape and is named so it cannot be confused with the client's SpanUsage-based RunUsage, which is cache-inclusive. Finish reasons are mapped rather than passed through: end_turn becomes stop, tool_use becomes tool_calls. A consumer grouping by this attribute across handlers previously saw two names for one outcome. A failed run now reports what its completed turns cost, on the root, but only when a turn actually reported usage. All-zero attributes would assert the run cost nothing, which a run whose first call died mid-flight cannot claim. The streaming path gets a `finally`. A consumer that breaks out of the iteration makes the generator skip `except` entirely, because GeneratorExit inherits from BaseException, so the root span was never ended and never exported: the whole run vanished from AI Config Monitoring along with the feature_flag event it carries. Every span now ends through end_span_once, and an abandoned one is marked and left UNSET rather than ERROR, because LaunchDarkly's own metrics record neither a success nor an error for abandonment and ERROR would put two dashboards in disagreement about one run. The success tail sets status without ending, so the `finally` owns every end. Ending twice is ignored by the OTel SDK but recorded as a diagnostic error, and would hide a genuine leak. Tests: the telemetry classes are rewritten rather than extended, because they pinned the old flat span. The single shared MagicMock span is replaced with a recorder that keeps one object per span, since the old approach could not tell a parent from a child. 82 tests here, up from 64. The fake usage object now declares only the fields Anthropic sets, so a handler cannot read a cache field the provider never reported. Not changed: the tool catalog is still unfiltered, unlike the TypeScript SDK, which offers the model only tools that have a registered handler. That difference predates this work and changes what the model is offered rather than what the span reports.
1 parent 905f873 commit 0ccb68d

3 files changed

Lines changed: 1322 additions & 372 deletions

File tree

0 commit comments

Comments
 (0)