Skip to content

Python: fix(anthropic): report token usage as CompletionUsage - #14618

Merged
Eduard van Valkenburg (eavanvalkenburg) merged 1 commit into
microsoft:mainfrom
pcbeingused333:fix/anthropic-completion-usage
Oct 26, 2026
Merged

Eduard van Valkenburg (eavanvalkenburg) merged 1 commit into
microsoft:mainfrom
pcbeingused333:fix/anthropic-completion-usage

Conversation

@pcbeingused333

Copy link
Copy Markdown
Contributor

Motivation and Context

The model diagnostics record token usage only when metadata["usage"] is a CompletionUsage (utils/telemetry/model_diagnostics/decorators.py). The other chat connectors build one, but AnthropicChatCompletion stores the raw anthropic.types.Usage (non-streaming) or a plain dict like {"input_tokens": ..., "output_tokens": ...} (streaming). As a result, spans for Anthropic calls never get gen_ai.usage.input_tokens / gen_ai.usage.output_tokens.

Prompt caching also isn't counted: Anthropic reports cache_read_input_tokens and cache_creation_input_tokens separately from input_tokens, and the streaming path drops them.

Description

  • A new _get_completion_usage helper turns Anthropic usage into a CompletionUsage:
    • prompt_tokens = input_tokens + cache_read_input_tokens + cache_creation_input_tokens;
    • prompt_tokens_details.cached_tokens = cache_read_input_tokens (only when present);
    • completion_tokens = output_tokens.
  • Non-streaming: metadata["usage"] is now that CompletionUsage.
  • Streaming: message_start sets the prompt side. The final message_delta chunk carries the full usage: its own counts when it reports input_tokens, otherwise the message_start prompt counts plus the delta's output_tokens. Since the chunk metadata is merged with |, this is the usage of the combined message.
  • Fields are read with getattr because older anthropic releases (the requirement is ~= 0.32) lack some of them.

Behavior change: metadata["usage"] for Anthropic responses is now a CompletionUsage, the same type as the other connectors. Code that read .input_tokens / .output_tokens from it should use .prompt_tokens / .completion_tokens. The raw Anthropic usage is still available on inner_content.

Contribution Checklist

Tests, in tests/unit/connectors/ai/anthropic/services/test_anthropic_chat_completion.py, with model diagnostics enabled, 10 input + 100 cache-read + 20 cache-write + 7 output tokens:

  • test_usage_is_reported_as_completion_usage (non-streaming): metadata["usage"] is a CompletionUsage (130 prompt, 100 cached, 7 completion), and the span gets gen_ai.usage.input_tokens=130, gen_ai.usage.output_tokens=7;
  • test_streaming_usage_is_reported_as_completion_usage: the same checks on the combined streamed message.

Both fail on main. tests/unit/connectors/ai/anthropic + tests/unit/utils/model_diagnostics: 65 passed (test_connector_decorated.py was not run here because it imports the Mistral connector). ruff 0.15.5 check/format is clean. mypy on the changed module reports the same 3 pre-existing errors as main, all in other modules.

This change was drafted with Claude Code; the tests above were run before opening the PR.

🤖 Generated with Claude Code

AnthropicChatCompletion stored the raw anthropic Usage (non-streaming) or a
plain dict (streaming) in metadata["usage"], so the model diagnostics never
recorded gen_ai.usage.input_tokens / output_tokens for Anthropic calls. Build a
CompletionUsage like the other connectors, counting prompt-cache reads and
writes in prompt_tokens and cache reads in prompt_tokens_details.cached_tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 16, 2026 23:55
@semantic-kernel-automation semantic-kernel-automation Bot added the python Pull requests for the Python Semantic Kernel label Oct 16, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused conversion and streaming changes match the diagnostics contract, with regression coverage and no identified blocking issues.

No open issues 🎉

What changed in this PR

Normalizes Anthropic token usage to CompletionUsage so model diagnostics can report input and output token counts.

Changes:

  • Includes cache-read and cache-creation tokens in prompt totals.
  • Preserves prompt usage when merging streaming output counts.
  • Adds regression tests for response metadata and telemetry.
File Description
python/​tests/​unit/​connectors/​ai/​anthropic/​services/​test_anthropic_chat_completion.py Tests streaming and non-streaming usage reporting.
python/​semantic_kernel/​connectors/​ai/​anthropic/​services/​anthropic_chat_completion.py Converts Anthropic usage and preserves streaming prompt counts.

Review effort: Balanced

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I ran this end to end and the telemetry fix works. Setup: Python 3.13.3, anthropic SDK 1.15.0, opentelemetry-sdk with an in-memory exporter, SEMANTICKERNEL_EXPERIMENTAL_GENAI_ENABLE_OTEL_DIAGNOSTICS=true. The PR diff applied cleanly onto current main (fetched 2026-10-16) and I compared it with a clean main checkout.

Probe: a local HTTP server that speaks the Anthropic Messages API. The response (and the SSE message_start) carries input_tokens=10, cache_read_input_tokens=100, cache_creation_input_tokens=5. The non-streaming output_tokens is 2, and the streaming message_delta carries output_tokens=7. A real AsyncAnthropic(base_url=local) is passed to AnthropicChatCompletion.

main PR
non-stream metadata["usage"] raw Anthropic Usage(...) CompletionUsage(prompt_tokens=115, cached_tokens=100, completion_tokens=2)
stream metadata["usage"] dicts {'input_tokens': 10} then {'input_tokens': 10, 'output_tokens': 7} CompletionUsage with prompt 115, and completion 7 on the last chunk
SK chat claude-sonnet-4-5 span attributes (non-stream) none gen_ai.usage.input_tokens=115, gen_ai.usage.output_tokens=2
SK chat claude-sonnet-4-5 span attributes (stream) none input 115, output 7

So the missing token attributes are fixed, and the cached tokens are counted in the prompt side.

Edge cases on the streaming path:

  • message_delta with output_tokens only (what I used above): prompt 115 is kept from message_start. Good.
  • message_delta with input_tokens=10 and the two cache fields: prompt 115. Good.
  • message_delta with input_tokens=10 but no cache fields (I made this up to test the code path): the final usage becomes prompt_tokens=10, prompt_tokens_details=None, so the 105 cache tokens from message_start are dropped. The code says "use the delta counts when it reports input_tokens". I do not know whether the real API ever sends input_tokens without the cache fields in a delta, so this may never happen. If you want to be safe, fall back to the message_start cache values when the delta leaves them out.

Tests: tests/unit/connectors/ai/anthropic gives 37 passed on the PR. When I copy the PR's test file onto main, the two new tests fail (test_usage_is_reported_as_completion_usage, test_streaming_usage_is_reported_as_completion_usage) and 35 pass.

Two notes. The PR text already says this is a behavior change for code that read .input_tokens from the old usage object. I did not search outside this repo for such code. Also, PromptTokensDetails has a cache_write_tokens field and the PR leaves it None even though cache_creation_input_tokens is known. That is optional.

I ran only against a local server, not the real Anthropic API. I did not check how this combines with #14616, which edits nearby lines in the same file.

@pcbeingused333

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

Merged via the queue into microsoft:main with commit fdbc2cc Oct 26, 2026
37 checks passed

This branch was previously deployed

1 inactive deployment
github-app-auth — 643b7a48 Deployed Oct 16, 2026 by pcbeingused333 via team_check #638
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Pull requests for the Python Semantic Kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants