Repository navigation
[openai] Record cache-write and audio token usage - #681
Conversation
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Pull request dashboard statusMerged · refreshed 2026-10-15 22:27 UTC Status above doesn't look right?
|
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Add integer type assertions for exported usage attributes in the regression tests.
Pull request overview
Adds OpenAI Chat Completions cache-write and modality token usage recording for synchronous, asynchronous, and streaming calls.
Changes:
- Extracts detailed usage fields into telemetry.
- Adds local HTTP/SSE regression coverage.
- Adds a changelog entry.
File summaries
| File | Description |
|---|---|
instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_chat_token_usage.py |
Tests usage-detail variants across call modes. |
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/utils.py |
Extracts detailed usage fields. |
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/patch.py |
Applies extraction to non-streaming responses. |
instrumentation/opentelemetry-instrumentation-genai-openai/src/opentelemetry/instrumentation/genai/openai/chat_wrappers.py |
Applies extraction to streaming responses. |
instrumentation/opentelemetry-instrumentation-genai-openai/.changelog/603.added |
Documents the feature. |
Review details
Suppressed comments (1)
instrumentation/opentelemetry-instrumentation-genai-openai/tests/test_chat_token_usage.py:222
- This only checks equality, so a
boolorfloatusage value would still pass (True == 1and1.0 == 1). The new semconv usage attributes must be integers; assert the types of the exported values here so the regression test catches invalid attribute values.
assert actual == expected
- Files reviewed: 5/5 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
lmolkova
left a comment
There was a problem hiding this comment.
Please make sure to follow existing test practices in this repo
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
Signed-off-by: 1fanwang <1fannnw@gmail.com>
This comment has been minimized.
This comment has been minimized.
Signed-off-by: 1fanwang <1fannnw@gmail.com>
|
This PR has been automatically marked as stale because it has not had any activity for 14 days. It will be closed if no further activity occurs within 14 days of this comment. |
Assisted-by: GitHub Copilot CLI (Auto) Signed-off-by: 1fanwang <1fannnw@gmail.com>
@lmolkova the tests now use the instrumentation fixtures and SDK response types for normal and streamed calls, including async coverage. The body distinguishes the synthetic SDK probe from live provider evidence; no provider recording is claimed. |
This comment has been minimized.
This comment has been minimized.
Assisted-by: GitHub Copilot CLI (claude-sonnet-5.5) Signed-off-by: 1fanwang <1fannnw@gmail.com>
Description
Chat Completions telemetry omits cache-write and audio token counts, so users cannot observe those reported usage breakdowns. This records them for normal and streamed calls without changing aggregate input/output totals; streamed usage is applied during cleanup.
Text and image counts are not recorded: the SDK schema defines them, but the provider check below only confirms that Chat Completions returned audio and cache-write counts.
Fixes #603.
Type of change
How has this been tested?
Testing Done
The OpenAI 3.13.0 SDK parsed synthetic JSON/SSE through a mock HTTP transport on macOS with Python 3.14.7. These fixtures check extraction, not provider behavior.
A provider check ran against an earlier head of this PR with Python 3.12 and OpenAI SDK 3.13.0. It made two Chat Completions calls with retries disabled, a 30-second timeout, and completion limits of 128 audio tokens and 8 text tokens. API usage matched the exported span attributes. The probe prints the usage counts shown below, then a final status line. To repeat it, set OPENAI_API_KEY through a secret manager, save the source below as provider_probe.py, and run:
{"span_usage": {"gen_ai.usage.audio.input_tokens": 5, "gen_ai.usage.audio.output_tokens": 18, "gen_ai.usage.input_tokens": 19, "gen_ai.usage.output_tokens": 25}} {"api_usage": {"audio_input_tokens": 5, "audio_output_tokens": 18}, "case": "audio"} {"span_usage": {"gen_ai.usage.cache_write.input_tokens": 2404, "gen_ai.usage.input_tokens": 2407, "gen_ai.usage.output_tokens": 8, "gen_ai.usage.reasoning.output_tokens": 8}} {"api_usage": {"cache_write_tokens": 2404}, "case": "cache_write"}Save the source below as sdk_probe.py. From the repository root:
Both probe runs exited 0. Before:
{"path": "sync-response", "usage": {"gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}} {"path": "sync-stream", "usage": {"gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}} {"path": "async-response", "usage": {"gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}} {"path": "async-stream", "usage": {"gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}}After:
{"path": "sync-response", "usage": {"gen_ai.usage.audio.input_tokens": 10, "gen_ai.usage.audio.output_tokens": 2, "gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.cache_write.input_tokens": 10, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}} {"path": "sync-stream", "usage": {"gen_ai.usage.audio.input_tokens": 10, "gen_ai.usage.audio.output_tokens": 2, "gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.cache_write.input_tokens": 10, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}} {"path": "async-response", "usage": {"gen_ai.usage.audio.input_tokens": 10, "gen_ai.usage.audio.output_tokens": 2, "gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.cache_write.input_tokens": 10, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}} {"path": "async-stream", "usage": {"gen_ai.usage.audio.input_tokens": 10, "gen_ai.usage.audio.output_tokens": 2, "gen_ai.usage.cache_read.input_tokens": 5, "gen_ai.usage.cache_write.input_tokens": 10, "gen_ai.usage.input_tokens": 100, "gen_ai.usage.output_tokens": 20, "gen_ai.usage.reasoning.output_tokens": 3}}Reproducer source: sdk_probe.py
Reproducer source: provider_probe.py
Local conformance scenarios skipped because Weaver is unavailable.
Checklist
See CONTRIBUTING.md
for the style guide, changelog guidance, and more.