Skip to content

Commit 55d8c17

Browse files
opentelemetry-instrumentation-botocore: capture Bedrock prompt cache token usage
1 parent 98913a9 commit 55d8c17

4 files changed

Lines changed: 50 additions & 1 deletion

File tree

.changelog/0000.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`opentelemetry-instrumentation-botocore`: capture Bedrock prompt cache token usage

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/bedrock.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
GEN_AI_RESPONSE_FINISH_REASONS,
4848
GEN_AI_SYSTEM,
4949
GEN_AI_TOKEN_TYPE,
50+
GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
51+
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
5052
GEN_AI_USAGE_INPUT_TOKENS,
5153
GEN_AI_USAGE_OUTPUT_TOKENS,
5254
GenAiOperationNameValues,
@@ -462,7 +464,7 @@ def before_service_call(
462464
# this is used to calculate the operation duration metric, duration may be skewed by request_hook
463465
self._operation_start = default_timer()
464466

465-
# pylint: disable=no-self-use,too-many-locals
467+
# pylint: disable=no-self-use,too-many-locals,too-many-branches
466468
def _converse_on_success(
467469
self,
468470
span: Span,
@@ -482,6 +484,16 @@ def _converse_on_success(
482484
GEN_AI_USAGE_OUTPUT_TOKENS,
483485
output_tokens,
484486
)
487+
if cache_read := usage.get("cacheReadInputTokens"):
488+
span.set_attribute(
489+
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
490+
cache_read,
491+
)
492+
if cache_write := usage.get("cacheWriteInputTokens"):
493+
span.set_attribute(
494+
GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
495+
cache_write,
496+
)
485497

486498
if stop_reason := result.get("stopReason"):
487499
span.set_attribute(

instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions/bedrock_utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ def _process_event(self, event):
132132

133133
if output_tokens := usage.get("outputTokens"):
134134
self._response["usage"]["outputTokens"] = output_tokens
135+
136+
if cache_read := usage.get("cacheReadInputTokens"):
137+
self._response["usage"]["cacheReadInputTokens"] = cache_read
138+
139+
if cache_write := usage.get("cacheWriteInputTokens"):
140+
self._response["usage"]["cacheWriteInputTokens"] = cache_write
135141
self._complete_stream(self._response)
136142

137143
return

instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_bedrock.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from botocore.response import StreamingBody
1616

1717
from opentelemetry.instrumentation.botocore.extensions.bedrock_utils import (
18+
ConverseStreamWrapper,
1819
InvokeModelWithResponseStreamWrapper,
1920
_Choice,
2021
)
@@ -3077,6 +3078,35 @@ def test_converse_stream_with_missing_output_in_response():
30773078
assert choice.index == 0
30783079

30793080

3081+
def test_converse_stream_accumulates_cache_tokens():
3082+
# The ConverseStream metadata event carries prompt cache token usage;
3083+
# the wrapper should accumulate it alongside input/output tokens.
3084+
wrapper = ConverseStreamWrapper(
3085+
stream=mock.MagicMock(),
3086+
stream_done_callback=lambda *args, **kwargs: None,
3087+
stream_error_callback=lambda *args, **kwargs: None,
3088+
)
3089+
3090+
wrapper._process_event(
3091+
{
3092+
"metadata": {
3093+
"usage": {
3094+
"inputTokens": 8,
3095+
"outputTokens": 10,
3096+
"cacheReadInputTokens": 1500,
3097+
"cacheWriteInputTokens": 25,
3098+
}
3099+
}
3100+
}
3101+
)
3102+
3103+
usage = wrapper._response["usage"]
3104+
assert usage["inputTokens"] == 8
3105+
assert usage["outputTokens"] == 10
3106+
assert usage["cacheReadInputTokens"] == 1500
3107+
assert usage["cacheWriteInputTokens"] == 25
3108+
3109+
30803110
def amazon_nova_messages():
30813111
return [
30823112
{"role": "user", "content": [{"text": "Say this is a test"}]},

0 commit comments

Comments
 (0)