1313# limitations under the License.
1414
1515import json
16+ import inspect
1617import logging
1718import re
1819import sys
@@ -546,12 +547,77 @@ def extract_bedrock_cohere_model_streaming_response(response_body, bedrock_attrs
546547]
547548
548549
550+ def handle_bedrock_exception (exc , is_embedding , model , span_id , trace_id , request_extractor , request_body , ft , transaction ):
551+ try :
552+ bedrock_attrs = {
553+ "model" : model ,
554+ "span_id" : span_id ,
555+ "trace_id" : trace_id ,
556+ }
557+ try :
558+ request_extractor (request_body , bedrock_attrs )
559+ except json .decoder .JSONDecodeError :
560+ pass
561+ except Exception :
562+ _logger .warning (REQUEST_EXTACTOR_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
563+
564+ error_attributes = bedrock_error_attributes (exc , bedrock_attrs )
565+ notice_error_attributes = {
566+ "http.statusCode" : error_attributes .get ("http.statusCode" ),
567+ "error.message" : error_attributes .get ("error.message" ),
568+ "error.code" : error_attributes .get ("error.code" ),
569+ }
570+
571+ if is_embedding :
572+ notice_error_attributes .update ({"embedding_id" : str (uuid .uuid4 ())})
573+ else :
574+ notice_error_attributes .update ({"completion_id" : str (uuid .uuid4 ())})
575+
576+ if ft :
577+ ft .notice_error (
578+ attributes = notice_error_attributes ,
579+ )
580+
581+ ft .__exit__ (* sys .exc_info ())
582+ error_attributes ["duration" ] = ft .duration * 1000
583+
584+ if is_embedding :
585+ handle_embedding_event (transaction , error_attributes )
586+ else :
587+ handle_chat_completion_event (transaction , error_attributes )
588+ except Exception :
589+ _logger .warning (EXCEPTION_HANDLING_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
590+
591+ raise
592+
593+
594+ def run_bedrock_response_extractor (response_extractor , response_body , bedrock_attrs , is_embedding , transaction ):
595+ # Run response extractor for non-streaming responses
596+ try :
597+ response_extractor (response_body , bedrock_attrs )
598+ except Exception :
599+ _logger .warning (RESPONSE_EXTRACTOR_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
600+
601+ if is_embedding :
602+ handle_embedding_event (transaction , bedrock_attrs )
603+ else :
604+ handle_chat_completion_event (transaction , bedrock_attrs )
605+
606+
607+ def run_bedrock_request_extractor (request_extractor , request_body , bedrock_attrs ):
608+ try :
609+ request_extractor (request_body , bedrock_attrs )
610+ except json .decoder .JSONDecodeError :
611+ pass
612+ except Exception :
613+ _logger .warning (REQUEST_EXTACTOR_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
614+
615+
549616def wrap_bedrock_runtime_invoke_model (response_streaming = False ):
550617 @function_wrapper
551618 def _wrap_bedrock_runtime_invoke_model (wrapped , instance , args , kwargs ):
552619 # Wrapped function only takes keyword arguments, no need for binding
553620 transaction = current_transaction ()
554-
555621 if not transaction :
556622 return wrapped (* args , ** kwargs )
557623
@@ -604,54 +670,32 @@ def _wrap_bedrock_runtime_invoke_model(wrapped, instance, args, kwargs):
604670 span_id = available_metadata .get ("span.id" )
605671 trace_id = available_metadata .get ("trace.id" )
606672
673+ # Store data on instance to pass context to async instrumentation
674+ instance ._nr_trace_id = trace_id
675+ instance ._nr_span_id = span_id
676+ instance ._nr_request_extractor = request_extractor
677+ instance ._nr_response_extractor = response_extractor
678+ instance ._nr_stream_extractor = stream_extractor
679+ instance ._nr_txn = transaction
680+ instance ._nr_ft = ft
681+
682+ # Add a bedrock flag to instance so we can determine when make_api_call instrumentation is hit from non-Bedrock paths and bypass it if so
683+ instance ._nr_is_bedrock = True
684+
607685 try :
686+ # For aioboto3 clients, this will call make_api_call instrumentation in external_aiobotocore
608687 response = wrapped (* args , ** kwargs )
609688 except Exception as exc :
610- try :
611- bedrock_attrs = {
612- "model" : model ,
613- "span_id" : span_id ,
614- "trace_id" : trace_id ,
615- }
616- try :
617- request_extractor (request_body , bedrock_attrs )
618- except json .decoder .JSONDecodeError :
619- pass
620- except Exception :
621- _logger .warning (REQUEST_EXTACTOR_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
622-
623- error_attributes = bedrock_error_attributes (exc , bedrock_attrs )
624- notice_error_attributes = {
625- "http.statusCode" : error_attributes .get ("http.statusCode" ),
626- "error.message" : error_attributes .get ("error.message" ),
627- "error.code" : error_attributes .get ("error.code" ),
628- }
629-
630- if is_embedding :
631- notice_error_attributes .update ({"embedding_id" : str (uuid .uuid4 ())})
632- else :
633- notice_error_attributes .update ({"completion_id" : str (uuid .uuid4 ())})
634-
635- ft .notice_error (
636- attributes = notice_error_attributes ,
637- )
638-
639- ft .__exit__ (* sys .exc_info ())
640- error_attributes ["duration" ] = ft .duration * 1000
641-
642- if operation == "embedding" :
643- handle_embedding_event (transaction , error_attributes )
644- else :
645- handle_chat_completion_event (transaction , error_attributes )
646- except Exception :
647- _logger .warning (EXCEPTION_HANDLING_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
648-
649- raise
689+ handle_bedrock_exception (exc , is_embedding , model , span_id , trace_id , request_extractor , request_body , ft , transaction )
650690
651691 if not response or response_streaming and not settings .ai_monitoring .streaming .enabled :
652692 ft .__exit__ (None , None , None )
653693 return response
654694
695+ # Let the instrumentation of make_api_call in the aioboto3 client handle it if we have an async case
696+ if inspect .iscoroutine (response ):
697+ return response
698+
655699 if response_streaming and operation == "embedding" :
656700 # This combination is not supported at time of writing, but may become
657701 # a supported feature in the future. Instrumentation will need to be written
@@ -668,12 +712,7 @@ def _wrap_bedrock_runtime_invoke_model(wrapped, instance, args, kwargs):
668712 "trace_id" : trace_id ,
669713 }
670714
671- try :
672- request_extractor (request_body , bedrock_attrs )
673- except json .decoder .JSONDecodeError :
674- pass
675- except Exception :
676- _logger .warning (REQUEST_EXTACTOR_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
715+ run_bedrock_request_extractor (request_extractor , request_body , bedrock_attrs )
677716
678717 try :
679718 if response_streaming :
@@ -691,16 +730,7 @@ def _wrap_bedrock_runtime_invoke_model(wrapped, instance, args, kwargs):
691730 bedrock_attrs ["duration" ] = ft .duration * 1000
692731 response ["body" ] = StreamingBody (BytesIO (response_body ), len (response_body ))
693732
694- # Run response extractor for non-streaming responses
695- try :
696- response_extractor (response_body , bedrock_attrs )
697- except Exception :
698- _logger .warning (RESPONSE_EXTRACTOR_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
699-
700- if operation == "embedding" :
701- handle_embedding_event (transaction , bedrock_attrs )
702- else :
703- handle_chat_completion_event (transaction , bedrock_attrs )
733+ run_bedrock_response_extractor (response_extractor , response_body , bedrock_attrs , is_embedding , transaction )
704734
705735 except Exception :
706736 _logger .warning (RESPONSE_PROCESSING_FAILURE_LOG_MESSAGE % traceback .format_exception (* sys .exc_info ()))
@@ -864,7 +894,6 @@ def handle_chat_completion_event(transaction, bedrock_attrs):
864894 llm_context_attrs = getattr (transaction , "_llm_context_attrs" , None )
865895 if llm_context_attrs :
866896 llm_metadata_dict .update (llm_context_attrs )
867-
868897 span_id = bedrock_attrs .get ("span_id" , None )
869898 trace_id = bedrock_attrs .get ("trace_id" , None )
870899 request_id = bedrock_attrs .get ("request_id" , None )
@@ -1009,9 +1038,9 @@ def _nr_dynamodb_datastore_trace_wrapper_(wrapped, instance, args, kwargs):
10091038 partition = "aws-us-gov"
10101039
10111040 if partition and region and account_id and _target :
1012- agent_attrs ["cloud.resource_id" ] = (
1013- f"arn: { partition } :dynamodb: { region } : { account_id :012d } :table/ { _target } "
1014- )
1041+ agent_attrs [
1042+ "cloud.resource_id "
1043+ ] = f"arn: { partition } :dynamodb: { region } : { account_id :012d } :table/ { _target } "
10151044 agent_attrs ["db.system" ] = "DynamoDB"
10161045
10171046 except Exception as e :
0 commit comments