diff --git a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_bedrock_patches.py b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_bedrock_patches.py index a8d61f4d..a25e5533 100644 --- a/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_bedrock_patches.py +++ b/aws-opentelemetry-distro/src/amazon/opentelemetry/distro/patches/_bedrock_patches.py @@ -268,6 +268,8 @@ def extract_attributes(self, attributes: _AttributeMapT): if "amazon.titan" in model_id: self._extract_titan_attributes(attributes, request_body) + if "amazon.nova" in model_id: + self._extract_nova_attributes(attributes, request_body) elif "anthropic.claude" in model_id: self._extract_claude_attributes(attributes, request_body) elif "meta.llama" in model_id: @@ -288,6 +290,12 @@ def _extract_titan_attributes(self, attributes, request_body): self._set_if_not_none(attributes, GEN_AI_REQUEST_TOP_P, config.get("topP")) self._set_if_not_none(attributes, GEN_AI_REQUEST_MAX_TOKENS, config.get("maxTokenCount")) + def _extract_nova_attributes(self, attributes, request_body): + config = request_body.get("inferenceConfig", {}) + self._set_if_not_none(attributes, GEN_AI_REQUEST_TEMPERATURE, config.get("temperature")) + self._set_if_not_none(attributes, GEN_AI_REQUEST_TOP_P, config.get("top_p")) + self._set_if_not_none(attributes, GEN_AI_REQUEST_MAX_TOKENS, config.get("max_new_tokens")) + def _extract_claude_attributes(self, attributes, request_body): self._set_if_not_none(attributes, GEN_AI_REQUEST_MAX_TOKENS, request_body.get("max_tokens")) self._set_if_not_none(attributes, GEN_AI_REQUEST_TEMPERATURE, request_body.get("temperature")) @@ -324,6 +332,7 @@ def _set_if_not_none(attributes, key, value): if value is not None: attributes[key] = value + # pylint: disable=too-many-branches def on_success(self, span: Span, result: Dict[str, Any]): model_id = self._call_context.params.get(_MODEL_ID) @@ -342,6 +351,8 @@ def on_success(self, span: Span, result: Dict[str, Any]): response_body = json.loads(telemetry_content.decode("utf-8")) if "amazon.titan" in model_id: self._handle_amazon_titan_response(span, response_body) + if "amazon.nova" in model_id: + self._handle_amazon_nova_response(span, response_body) elif "anthropic.claude" in model_id: self._handle_anthropic_claude_response(span, response_body) elif "meta.llama" in model_id: @@ -375,6 +386,17 @@ def _handle_amazon_titan_response(self, span: Span, response_body: Dict[str, Any if "completionReason" in result: span.set_attribute(GEN_AI_RESPONSE_FINISH_REASONS, [result["completionReason"]]) + # pylint: disable=no-self-use + def _handle_amazon_nova_response(self, span: Span, response_body: Dict[str, Any]): + if "usage" in response_body: + usage = response_body["usage"] + if "inputTokens" in usage: + span.set_attribute(GEN_AI_USAGE_INPUT_TOKENS, usage["inputTokens"]) + if "outputTokens" in usage: + span.set_attribute(GEN_AI_USAGE_OUTPUT_TOKENS, usage["outputTokens"]) + if "stopReason" in response_body: + span.set_attribute(GEN_AI_RESPONSE_FINISH_REASONS, [response_body["stopReason"]]) + # pylint: disable=no-self-use def _handle_anthropic_claude_response(self, span: Span, response_body: Dict[str, Any]): if "usage" in response_body: diff --git a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py index 6fe16f15..209c5d1b 100644 --- a/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py +++ b/aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_instrumentation_patch.py @@ -215,209 +215,94 @@ def _test_patched_botocore_instrumentation(self): bedrock_agent_runtime_sucess_attributes: Dict[str, str] = _do_on_success_bedrock("bedrock-agent-runtime") self.assertEqual(len(bedrock_agent_runtime_sucess_attributes), 0) - # BedrockRuntime - Amazon Titan Models + # BedrockRuntime - Amazon Titan self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS) - request_body = { - "textGenerationConfig": { - "maxTokenCount": 512, - "temperature": 0.9, - "topP": 0.75, - } - } - bedrock_runtime_attributes: Dict[str, str] = _do_extract_attributes_bedrock( - "bedrock-runtime", model_id="amazon.titan", request_body=json.dumps(request_body) - ) - self.assertEqual(len(bedrock_runtime_attributes), 5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], "amazon.titan") - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.max_tokens"], 512) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.temperature"], 0.9) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.top_p"], 0.75) - response_body = { - "inputTextTokenCount": 123, - "results": [ - { - "tokenCount": 456, - "outputText": "testing", - "completionReason": "FINISH", - } - ], - } - json_bytes = json.dumps(response_body).encode("utf-8") - body_bytes = BytesIO(json_bytes) - streaming_body = StreamingBody(body_bytes, len(json_bytes)) - bedrock_runtime_success_attributes: Dict[str, str] = _do_on_success_bedrock( - "bedrock-runtime", model_id="amazon.titan", streaming_body=streaming_body - ) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.input_tokens"], 123) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.output_tokens"], 456) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.response.finish_reasons"], ["FINISH"]) - - # BedrockRuntime - Anthropic Claude Models - - self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS) - request_body = { - "anthropic_version": "bedrock-2023-05-31", - "max_tokens": 512, - "temperature": 0.5, - "top_p": 0.999, - } - bedrock_runtime_attributes: Dict[str, str] = _do_extract_attributes_bedrock( - "bedrock-runtime", model_id="anthropic.claude", request_body=json.dumps(request_body) - ) - self.assertEqual(len(bedrock_runtime_attributes), 5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], "anthropic.claude") - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.max_tokens"], 512) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.temperature"], 0.5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.top_p"], 0.999) - response_body = { - "stop_reason": "end_turn", - "stop_sequence": None, - "usage": {"input_tokens": 23, "output_tokens": 36}, - } - json_bytes = json.dumps(response_body).encode("utf-8") - body_bytes = BytesIO(json_bytes) - streaming_body = StreamingBody(body_bytes, len(json_bytes)) - bedrock_runtime_success_attributes: Dict[str, str] = _do_on_success_bedrock( - "bedrock-runtime", model_id="anthropic.claude", streaming_body=streaming_body + self._test_patched_bedrock_runtime_invoke_model( + model_id="amazon.titan-embed-text-v1", + max_tokens=512, + temperature=0.9, + top_p=0.75, + finish_reason="FINISH", + input_tokens=123, + output_tokens=456, ) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.input_tokens"], 23) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.output_tokens"], 36) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.response.finish_reasons"], ["end_turn"]) - # BedrockRuntime - Cohere Command Models - self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS) - request_body = { - "message": "Hello, world", - "max_tokens": 512, - "temperature": 0.5, - "p": 0.75, - } - - bedrock_runtime_attributes: Dict[str, str] = _do_extract_attributes_bedrock( - "bedrock-runtime", model_id="cohere.command", request_body=json.dumps(request_body) - ) - self.assertEqual(len(bedrock_runtime_attributes), 6) - self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], "cohere.command") - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.max_tokens"], 512) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.temperature"], 0.5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.top_p"], 0.75) - self.assertEqual( - bedrock_runtime_attributes["gen_ai.usage.input_tokens"], math.ceil(len(request_body["message"]) / 6) - ) - response_body = { - "text": "Goodbye, world", - "finish_reason": "COMPLETE", - } - json_bytes = json.dumps(response_body).encode("utf-8") - body_bytes = BytesIO(json_bytes) - streaming_body = StreamingBody(body_bytes, len(json_bytes)) - bedrock_runtime_success_attributes: Dict[str, str] = _do_on_success_bedrock( - "bedrock-runtime", model_id="cohere.command", streaming_body=streaming_body - ) - self.assertEqual( - bedrock_runtime_success_attributes["gen_ai.usage.output_tokens"], math.ceil(len(response_body["text"]) / 6) + self._test_patched_bedrock_runtime_invoke_model( + model_id="amazon.nova-pro-v1:0", + max_tokens=500, + temperature=0.9, + top_p=0.7, + finish_reason="FINISH", + input_tokens=123, + output_tokens=456, ) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.response.finish_reasons"], ["COMPLETE"]) - # BedrockRuntime - AI21 Jamba Models - self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS) - request_body = { - "max_tokens": 512, - "temperature": 0.5, - "top_p": 0.9, - } - - bedrock_runtime_attributes: Dict[str, str] = _do_extract_attributes_bedrock( - "bedrock-runtime", model_id="ai21.jamba", request_body=json.dumps(request_body) - ) - self.assertEqual(len(bedrock_runtime_attributes), 5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], "ai21.jamba") - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.max_tokens"], 512) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.temperature"], 0.5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.top_p"], 0.9) - response_body = { - "choices": [{"finish_reason": "stop"}], - "usage": {"prompt_tokens": 24, "completion_tokens": 31, "total_tokens": 55}, - } - json_bytes = json.dumps(response_body).encode("utf-8") - body_bytes = BytesIO(json_bytes) - streaming_body = StreamingBody(body_bytes, len(json_bytes)) - bedrock_runtime_success_attributes: Dict[str, str] = _do_on_success_bedrock( - "bedrock-runtime", model_id="ai21.jamba", streaming_body=streaming_body + # BedrockRuntime - Anthropic Claude + self._test_patched_bedrock_runtime_invoke_model( + model_id="anthropic.claude-v2:1", + max_tokens=512, + temperature=0.5, + top_p=0.999, + finish_reason="end_turn", + input_tokens=23, + output_tokens=36, ) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.input_tokens"], 24) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.output_tokens"], 31) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.response.finish_reasons"], ["stop"]) - - # BedrockRuntime - Meta LLama Models - self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS) - request_body = { - "max_gen_len": 512, - "temperature": 0.5, - "top_p": 0.9, - } - bedrock_runtime_attributes: Dict[str, str] = _do_extract_attributes_bedrock( - "bedrock-runtime", model_id="meta.llama", request_body=json.dumps(request_body) + # BedrockRuntime - Meta LLama + self._test_patched_bedrock_runtime_invoke_model( + model_id="meta.llama2-13b-chat-v1", + max_tokens=512, + temperature=0.5, + top_p=0.9, + finish_reason="stop", + input_tokens=31, + output_tokens=36, ) - self.assertEqual(len(bedrock_runtime_attributes), 5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], "meta.llama") - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.max_tokens"], 512) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.temperature"], 0.5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.top_p"], 0.9) - response_body = {"prompt_token_count": 31, "generation_token_count": 36, "stop_reason": "stop"} - json_bytes = json.dumps(response_body).encode("utf-8") - body_bytes = BytesIO(json_bytes) - streaming_body = StreamingBody(body_bytes, len(json_bytes)) - bedrock_runtime_success_attributes: Dict[str, str] = _do_on_success_bedrock( - "bedrock-runtime", model_id="meta.llama", streaming_body=streaming_body - ) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.input_tokens"], 31) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.usage.output_tokens"], 36) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.response.finish_reasons"], ["stop"]) - - # BedrockRuntime - Mistral Models - self.assertTrue("bedrock-runtime" in _KNOWN_EXTENSIONS) - msg = "Hello, World" - formatted_prompt = f"[INST] {msg} [/INST]" - request_body = { - "prompt": formatted_prompt, - "max_tokens": 512, - "temperature": 0.5, - "top_p": 0.9, - } - bedrock_runtime_attributes: Dict[str, str] = _do_extract_attributes_bedrock( - "bedrock-runtime", model_id="mistral", request_body=json.dumps(request_body) - ) - self.assertEqual(len(bedrock_runtime_attributes), 6) - self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], "mistral") - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.max_tokens"], 512) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.temperature"], 0.5) - self.assertEqual(bedrock_runtime_attributes["gen_ai.request.top_p"], 0.9) - self.assertEqual( - bedrock_runtime_attributes["gen_ai.usage.input_tokens"], math.ceil(len(request_body["prompt"]) / 6) + # BedrockRuntime - Cohere Command-r + cohere_input = "Hello, world" + cohere_output = "Goodbye, world" + + self._test_patched_bedrock_runtime_invoke_model( + model_id="cohere.command-r-v1:0", + max_tokens=512, + temperature=0.5, + top_p=0.75, + finish_reason="COMPLETE", + input_tokens=math.ceil(len(cohere_input) / 6), + output_tokens=math.ceil(len(cohere_output) / 6), + input_prompt=cohere_input, + output_prompt=cohere_output, ) - response_body = {"outputs": [{"text": "Goodbye, World", "stop_reason": "stop"}]} - json_bytes = json.dumps(response_body).encode("utf-8") - body_bytes = BytesIO(json_bytes) - streaming_body = StreamingBody(body_bytes, len(json_bytes)) - bedrock_runtime_success_attributes: Dict[str, str] = _do_on_success_bedrock( - "bedrock-runtime", model_id="mistral", streaming_body=streaming_body + + # BedrockRuntime - AI21 Jambda + self._test_patched_bedrock_runtime_invoke_model( + model_id="ai21.jamba-1-5-large-v1:0", + max_tokens=512, + temperature=0.5, + top_p=0.999, + finish_reason="end_turn", + input_tokens=23, + output_tokens=36, ) - self.assertEqual( - bedrock_runtime_success_attributes["gen_ai.usage.output_tokens"], - math.ceil(len(response_body["outputs"][0]["text"]) / 6), + # BedrockRuntime - Mistral + msg = "Hello World" + mistral_input = f"[INST] {msg} [/INST]" + mistral_output = "Goodbye, World" + + self._test_patched_bedrock_runtime_invoke_model( + model_id="mistral.mistral-7b-instruct-v0:2", + max_tokens=512, + temperature=0.5, + top_p=0.9, + finish_reason="stop", + input_tokens=math.ceil(len(mistral_input) / 6), + output_tokens=math.ceil(len(mistral_output) / 6), + input_prompt=mistral_input, + output_prompt=mistral_output, ) - self.assertEqual(bedrock_runtime_success_attributes["gen_ai.response.finish_reasons"], ["stop"]) # SecretsManager self.assertTrue("secretsmanager" in _KNOWN_EXTENSIONS) @@ -506,6 +391,146 @@ def _test_patched_bedrock_instrumentation(self): self.assertEqual(len(bedrock_sucess_attributes), 1) self.assertEqual(bedrock_sucess_attributes["aws.bedrock.guardrail.id"], _BEDROCK_GUARDRAIL_ID) + def _test_patched_bedrock_runtime_invoke_model(self, **args): + model_id = args.get("model_id", None) + max_tokens = args.get("max_tokens", None) + temperature = args.get("temperature", None) + top_p = args.get("top_p", None) + finish_reason = args.get("finish_reason", None) + input_tokens = args.get("input_tokens", None) + output_tokens = args.get("output_tokens", None) + input_prompt = args.get("input_prompt", None) + output_prompt = args.get("output_prompt", None) + + def get_model_response_request(): + request_body = {} + response_body = {} + + if "amazon.titan" in model_id: + request_body = { + "textGenerationConfig": { + "maxTokenCount": max_tokens, + "temperature": temperature, + "topP": top_p, + } + } + + response_body = { + "inputTextTokenCount": input_tokens, + "results": [ + { + "tokenCount": output_tokens, + "outputText": "testing", + "completionReason": finish_reason, + } + ], + } + + if "amazon.nova" in model_id: + request_body = { + "inferenceConfig": { + "max_new_tokens": max_tokens, + "temperature": temperature, + "top_p": top_p, + } + } + + response_body = { + "output": {"message": {"content": [{"text": ""}], "role": "assistant"}}, + "stopReason": finish_reason, + "usage": {"inputTokens": input_tokens, "outputTokens": output_tokens}, + } + + if "anthropic.claude" in model_id: + request_body = { + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": max_tokens, + "temperature": temperature, + "top_p": top_p, + } + + response_body = { + "stop_reason": finish_reason, + "stop_sequence": None, + "usage": {"input_tokens": input_tokens, "output_tokens": output_tokens}, + } + + if "ai21.jamba" in model_id: + request_body = { + "max_tokens": max_tokens, + "temperature": temperature, + "top_p": top_p, + } + + response_body = { + "choices": [{"finish_reason": finish_reason}], + "usage": { + "prompt_tokens": input_tokens, + "completion_tokens": output_tokens, + "total_tokens": (input_tokens + output_tokens), + }, + } + + if "meta.llama" in model_id: + request_body = { + "max_gen_len": max_tokens, + "temperature": temperature, + "top_p": top_p, + } + + response_body = { + "prompt_token_count": input_tokens, + "generation_token_count": output_tokens, + "stop_reason": finish_reason, + } + + if "cohere.command" in model_id: + request_body = { + "message": input_prompt, + "max_tokens": max_tokens, + "temperature": temperature, + "p": top_p, + } + + response_body = { + "text": output_prompt, + "finish_reason": finish_reason, + } + + if "mistral" in model_id: + request_body = { + "prompt": input_prompt, + "max_tokens": max_tokens, + "temperature": temperature, + "top_p": top_p, + } + + response_body = {"outputs": [{"text": output_prompt, "stop_reason": finish_reason}]} + + json_bytes = json.dumps(response_body).encode("utf-8") + + return json.dumps(request_body), StreamingBody(BytesIO(json_bytes), len(json_bytes)) + + request_body, response_body = get_model_response_request() + + bedrock_runtime_attributes: Dict[str, str] = _do_extract_attributes_bedrock( + "bedrock-runtime", model_id=model_id, request_body=request_body + ) + bedrock_runtime_success_attributes: Dict[str, str] = _do_on_success_bedrock( + "bedrock-runtime", model_id=model_id, streaming_body=response_body + ) + + bedrock_runtime_attributes.update(bedrock_runtime_success_attributes) + + self.assertEqual(bedrock_runtime_attributes["gen_ai.system"], _GEN_AI_SYSTEM) + self.assertEqual(bedrock_runtime_attributes["gen_ai.request.model"], model_id) + self.assertEqual(bedrock_runtime_attributes["gen_ai.request.max_tokens"], max_tokens) + self.assertEqual(bedrock_runtime_attributes["gen_ai.request.temperature"], temperature) + self.assertEqual(bedrock_runtime_attributes["gen_ai.request.top_p"], top_p) + self.assertEqual(bedrock_runtime_attributes["gen_ai.usage.input_tokens"], input_tokens) + self.assertEqual(bedrock_runtime_attributes["gen_ai.usage.output_tokens"], output_tokens) + self.assertEqual(bedrock_runtime_attributes["gen_ai.response.finish_reasons"], [finish_reason]) + def _test_patched_bedrock_agent_instrumentation(self): """For bedrock-agent service, both extract_attributes and on_success provides attributes, the attributes depend on the API being invoked.""" diff --git a/contract-tests/images/applications/botocore/botocore_server.py b/contract-tests/images/applications/botocore/botocore_server.py index 0575f4d8..6c315a4d 100644 --- a/contract-tests/images/applications/botocore/botocore_server.py +++ b/contract-tests/images/applications/botocore/botocore_server.py @@ -427,6 +427,24 @@ def get_model_request_response(path): ], } + if "amazon.nova" in path: + model_id = "amazon.nova-pro-v1:0" + + request_body = { + "messages": [{"role": "user", "content": [{"text": "A camping trip"}]}], + "inferenceConfig": { + "max_new_tokens": 800, + "temperature": 0.9, + "top_p": 0.7, + }, + } + + response_body = { + "output": {"message": {"content": [{"text": ""}], "role": "assistant"}}, + "stopReason": "max_tokens", + "usage": {"inputTokens": 432, "outputTokens": 681}, + } + if "anthropic.claude" in path: model_id = "anthropic.claude-v2:1" diff --git a/contract-tests/tests/test/amazon/botocore/botocore_test.py b/contract-tests/tests/test/amazon/botocore/botocore_test.py index d444b971..ed04c951 100644 --- a/contract-tests/tests/test/amazon/botocore/botocore_test.py +++ b/contract-tests/tests/test/amazon/botocore/botocore_test.py @@ -1,6 +1,7 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 import math +import re from logging import INFO, Logger, getLogger from typing import Dict, List @@ -442,6 +443,33 @@ def test_bedrock_runtime_invoke_model_amazon_titan(self): span_name="Bedrock Runtime.InvokeModel", ) + def test_bedrock_runtime_invoke_model_amazon_nova(self): + self.do_test_requests( + "bedrock/invokemodel/invoke-model/amazon.nova-pro-v1:0", + "GET", + 200, + 0, + 0, + rpc_service="Bedrock Runtime", + remote_service="AWS::BedrockRuntime", + remote_operation="InvokeModel", + remote_resource_type="AWS::Bedrock::Model", + remote_resource_identifier="amazon.nova-pro-v1:0", + cloudformation_primary_identifier="amazon.nova-pro-v1:0", + request_specific_attributes={ + _GEN_AI_REQUEST_MODEL: "amazon.nova-pro-v1:0", + _GEN_AI_REQUEST_MAX_TOKENS: 800, + _GEN_AI_REQUEST_TEMPERATURE: 0.9, + _GEN_AI_REQUEST_TOP_P: 0.7, + }, + response_specific_attributes={ + _GEN_AI_RESPONSE_FINISH_REASONS: ["max_tokens"], + _GEN_AI_USAGE_INPUT_TOKENS: 432, + _GEN_AI_USAGE_OUTPUT_TOKENS: 681, + }, + span_name="Bedrock Runtime.InvokeModel", + ) + def test_bedrock_runtime_invoke_model_anthropic_claude(self): self.do_test_requests( "bedrock/invokemodel/invoke-model/anthropic.claude-v2:1", @@ -982,10 +1010,7 @@ def _assert_semantic_conventions_attributes( def _assert_attribute(self, attributes_dict: Dict[str, AnyValue], key, value) -> None: if isinstance(value, str): - if self._is_valid_regex(value): - self._assert_match_attribute(attributes_dict, key, value) - else: - self._assert_str_attribute(attributes_dict, key, value) + self._assert_str_attribute(attributes_dict, key, value) elif isinstance(value, int): self._assert_int_attribute(attributes_dict, key, value) elif isinstance(value, float): @@ -1048,3 +1073,14 @@ def _assert_array_value_ddb_table_name(self, attributes_dict: Dict[str, AnyValue self.assertEqual(len(actual_values.values), len(expect_values)) for index in range(len(actual_values.values)): self.assertEqual(actual_values.values[index].string_value, expect_values[index]) + + @override + def _assert_str_attribute(self, attributes_dict: Dict[str, AnyValue], key: str, expected_value: str): + self.assertIn(key, attributes_dict) + actual_value: AnyValue = attributes_dict[key] + self.assertIsNotNone(actual_value) + pattern = re.compile(expected_value) + match = pattern.fullmatch(actual_value.string_value) + self.assertTrue( + match is not None, f"Actual: {actual_value.string_value} does not match Expected: {expected_value}" + )