|
44 | 44 | ERROR_TYPE, |
45 | 45 | ) |
46 | 46 | from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import ( |
47 | | - GEN_AI_CLIENT_OPERATION_DURATION, |
48 | | - GEN_AI_CLIENT_TOKEN_USAGE, |
49 | 47 | GEN_AI_OPERATION_NAME, |
50 | 48 | GEN_AI_REQUEST_MAX_TOKENS, |
51 | 49 | GEN_AI_REQUEST_MODEL, |
|
54 | 52 | GEN_AI_REQUEST_TOP_P, |
55 | 53 | GEN_AI_RESPONSE_FINISH_REASONS, |
56 | 54 | GEN_AI_SYSTEM, |
| 55 | + GEN_AI_TOKEN_TYPE, |
57 | 56 | GEN_AI_USAGE_INPUT_TOKENS, |
58 | 57 | GEN_AI_USAGE_OUTPUT_TOKENS, |
59 | 58 | GenAiOperationNameValues, |
60 | 59 | 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, |
61 | 65 | ) |
62 | 66 | from opentelemetry.trace.span import Span |
63 | 67 | from opentelemetry.trace.status import Status, StatusCode |
@@ -139,6 +143,26 @@ def setup_metrics(self, meter: Meter, metrics: dict[str, Instrument]): |
139 | 143 | explicit_bucket_boundaries_advisory=_GEN_AI_CLIENT_TOKEN_USAGE_BUCKETS, |
140 | 144 | ) |
141 | 145 |
|
| 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 | + |
142 | 166 | def extract_attributes(self, attributes: _AttributeMapT): |
143 | 167 | if self._call_context.operation not in self._HANDLED_OPERATIONS: |
144 | 168 | return |
@@ -351,6 +375,28 @@ def _converse_on_success( |
351 | 375 | ) |
352 | 376 | ) |
353 | 377 |
|
| 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 | + |
354 | 400 | def _invoke_model_on_success( |
355 | 401 | self, |
356 | 402 | span: Span, |
@@ -496,6 +542,28 @@ def _handle_amazon_titan_response( |
496 | 542 | ) |
497 | 543 | event_logger.emit(choice.to_choice_event()) |
498 | 544 |
|
| 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 | + |
499 | 567 | # pylint: disable=no-self-use |
500 | 568 | def _handle_amazon_nova_response( |
501 | 569 | self, |
@@ -523,6 +591,28 @@ def _handle_amazon_nova_response( |
523 | 591 | choice = _Choice.from_converse(response_body, capture_content) |
524 | 592 | event_logger.emit(choice.to_choice_event()) |
525 | 593 |
|
| 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 | + |
526 | 616 | # pylint: disable=no-self-use |
527 | 617 | def _handle_anthropic_claude_response( |
528 | 618 | self, |
@@ -551,6 +641,28 @@ def _handle_anthropic_claude_response( |
551 | 641 | ) |
552 | 642 | event_logger.emit(choice.to_choice_event()) |
553 | 643 |
|
| 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 | + |
554 | 666 | def on_error( |
555 | 667 | self, |
556 | 668 | span: Span, |
|
0 commit comments