Skip to content

Commit 9a17f97

Browse files
committed
First stab an token usage metrics
1 parent bd04439 commit 9a17f97

File tree

1 file changed

+114
-2
lines changed
  • instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/extensions

1 file changed

+114
-2
lines changed

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

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@
4444
ERROR_TYPE,
4545
)
4646
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
47-
GEN_AI_CLIENT_OPERATION_DURATION,
48-
GEN_AI_CLIENT_TOKEN_USAGE,
4947
GEN_AI_OPERATION_NAME,
5048
GEN_AI_REQUEST_MAX_TOKENS,
5149
GEN_AI_REQUEST_MODEL,
@@ -54,10 +52,16 @@
5452
GEN_AI_REQUEST_TOP_P,
5553
GEN_AI_RESPONSE_FINISH_REASONS,
5654
GEN_AI_SYSTEM,
55+
GEN_AI_TOKEN_TYPE,
5756
GEN_AI_USAGE_INPUT_TOKENS,
5857
GEN_AI_USAGE_OUTPUT_TOKENS,
5958
GenAiOperationNameValues,
6059
GenAiSystemValues,
60+
GenAiTokenTypeValues,
61+
)
62+
from opentelemetry.semconv._incubating.metrics.gen_ai_metrics import (
63+
GEN_AI_CLIENT_OPERATION_DURATION,
64+
GEN_AI_CLIENT_TOKEN_USAGE,
6165
)
6266
from opentelemetry.trace.span import Span
6367
from opentelemetry.trace.status import Status, StatusCode
@@ -139,6 +143,26 @@ def setup_metrics(self, meter: Meter, metrics: dict[str, Instrument]):
139143
explicit_bucket_boundaries_advisory=_GEN_AI_CLIENT_TOKEN_USAGE_BUCKETS,
140144
)
141145

146+
def _extract_metrics_attributes(self) -> _AttributeMapT:
147+
attributes = {GEN_AI_SYSTEM: GenAiSystemValues.AWS_BEDROCK.value}
148+
149+
model_id = self._call_context.params.get(_MODEL_ID_KEY)
150+
if not model_id:
151+
return
152+
153+
attributes[GEN_AI_REQUEST_MODEL] = model_id
154+
155+
# titan in invoke model is a text completion one
156+
if "body" in self._call_context.params and "amazon.titan" in model_id:
157+
attributes[GEN_AI_OPERATION_NAME] = (
158+
GenAiOperationNameValues.TEXT_COMPLETION.value
159+
)
160+
else:
161+
attributes[GEN_AI_OPERATION_NAME] = (
162+
GenAiOperationNameValues.CHAT.value
163+
)
164+
return attributes
165+
142166
def extract_attributes(self, attributes: _AttributeMapT):
143167
if self._call_context.operation not in self._HANDLED_OPERATIONS:
144168
return
@@ -351,6 +375,28 @@ def _converse_on_success(
351375
)
352376
)
353377

378+
metrics = instrumentor_context.metrics
379+
if token_usage_histogram := metrics.get(GEN_AI_CLIENT_TOKEN_USAGE):
380+
if usage := result.get("usage"):
381+
metrics_attributes = self._extract_metrics_attributes()
382+
if input_tokens := usage.get("inputTokens"):
383+
input_attributes = {
384+
**metrics_attributes,
385+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.INPUT.value,
386+
}
387+
token_usage_histogram.record(
388+
input_tokens, input_attributes
389+
)
390+
391+
if output_tokens := usage.get("outputTokens"):
392+
output_attributes = {
393+
**metrics_attributes,
394+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.COMPLETION.value,
395+
}
396+
token_usage_histogram.record(
397+
output_tokens, output_attributes
398+
)
399+
354400
def _invoke_model_on_success(
355401
self,
356402
span: Span,
@@ -496,6 +542,28 @@ def _handle_amazon_titan_response(
496542
)
497543
event_logger.emit(choice.to_choice_event())
498544

545+
metrics = instrumentor_context.metrics
546+
if token_usage_histogram := metrics.get(GEN_AI_CLIENT_TOKEN_USAGE):
547+
metrics_attributes = self._extract_metrics_attributes()
548+
if input_tokens := response_body.get("inputTextTokenCount"):
549+
input_attributes = {
550+
**metrics_attributes,
551+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.INPUT.value,
552+
}
553+
token_usage_histogram.record(
554+
input_tokens, input_attributes
555+
)
556+
557+
if results := response_body.get("results"):
558+
if output_tokens := results[0].get("tokenCount"):
559+
output_attributes = {
560+
**metrics_attributes,
561+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.COMPLETION.value,
562+
}
563+
token_usage_histogram.record(
564+
output_tokens, output_attributes
565+
)
566+
499567
# pylint: disable=no-self-use
500568
def _handle_amazon_nova_response(
501569
self,
@@ -523,6 +591,28 @@ def _handle_amazon_nova_response(
523591
choice = _Choice.from_converse(response_body, capture_content)
524592
event_logger.emit(choice.to_choice_event())
525593

594+
metrics = instrumentor_context.metrics
595+
if token_usage_histogram := metrics.get(GEN_AI_CLIENT_TOKEN_USAGE):
596+
if usage := response_body.get("usage"):
597+
metrics_attributes = self._extract_metrics_attributes()
598+
if input_tokens := usage.get("inputTokens"):
599+
input_attributes = {
600+
**metrics_attributes,
601+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.INPUT.value,
602+
}
603+
token_usage_histogram.record(
604+
input_tokens, input_attributes
605+
)
606+
607+
if output_tokens := usage.get("outputTokens"):
608+
output_attributes = {
609+
**metrics_attributes,
610+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.COMPLETION.value,
611+
}
612+
token_usage_histogram.record(
613+
output_tokens, output_attributes
614+
)
615+
526616
# pylint: disable=no-self-use
527617
def _handle_anthropic_claude_response(
528618
self,
@@ -551,6 +641,28 @@ def _handle_anthropic_claude_response(
551641
)
552642
event_logger.emit(choice.to_choice_event())
553643

644+
metrics = instrumentor_context.metrics
645+
if token_usage_histogram := metrics.get(GEN_AI_CLIENT_TOKEN_USAGE):
646+
if usage := response_body.get("usage"):
647+
metrics_attributes = self._extract_metrics_attributes()
648+
if input_tokens := usage.get("input_tokens"):
649+
input_attributes = {
650+
**metrics_attributes,
651+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.INPUT.value,
652+
}
653+
token_usage_histogram.record(
654+
input_tokens, input_attributes
655+
)
656+
657+
if output_tokens := usage.get("output_tokens"):
658+
output_attributes = {
659+
**metrics_attributes,
660+
GEN_AI_TOKEN_TYPE: GenAiTokenTypeValues.COMPLETION.value,
661+
}
662+
token_usage_histogram.record(
663+
output_tokens, output_attributes
664+
)
665+
554666
def on_error(
555667
self,
556668
span: Span,

0 commit comments

Comments
 (0)