1+
12# Copyright 2010 New Relic, Inc.
23#
34# Licensed under the Apache License, Version 2.0 (the "License");
2021from newrelic .api .external_trace import ExternalTrace
2122from newrelic .common .object_wrapper import wrap_function_wrapper
2223from newrelic .hooks .external_botocore import (
24+ AsyncEventStreamWrapper ,
2325 handle_bedrock_exception ,
2426 run_bedrock_response_extractor ,
2527 run_bedrock_request_extractor ,
28+ EMBEDDING_STREAMING_UNSUPPORTED_LOG_MESSAGE ,
2629 RESPONSE_PROCESSING_FAILURE_LOG_MESSAGE ,
2730)
2831
@@ -75,21 +78,36 @@ async def wrap_client__make_api_call(wrapped, instance, args, kwargs):
7578 if not hasattr (instance , "_nr_is_bedrock" ):
7679 return await wrapped (* args , ** kwargs )
7780
78- transaction = instance . _nr_txn
81+ transaction = getattr ( instance , " _nr_txn" , None )
7982 if not transaction :
8083 return await wrapped (* args , ** kwargs )
8184
85+ settings = getattr (instance , "_nr_settings" , None )
86+
87+ # Early exit if we can't access the shared settings object from invoke_model instrumentation
88+ # This settings object helps us determine if AIM was enabled as well as streaming
89+ if not (settings and settings .ai_monitoring .enabled ):
90+ return await wrapped (* args , ** kwargs )
91+
8292 # Grab all context data from botocore invoke_model instrumentation off the shared instance
8393 trace_id = getattr (instance , "_nr_trace_id" , "" )
8494 span_id = getattr (instance , "_nr_span_id" , "" )
8595
8696 request_extractor = getattr (instance , "_nr_request_extractor" , None )
8797 response_extractor = getattr (instance , "_nr_response_extractor" , None )
98+ stream_extractor = getattr (instance , "_nr_stream_extractor" , None )
99+ response_streaming = getattr (instance , "_nr_response_streaming" , False )
100+
88101 ft = getattr (instance , "_nr_ft" , None )
89102
90- model = args [1 ].get ("modelId" )
91- is_embedding = "embed" in model
92- request_body = args [1 ].get ("body" )
103+ if len (args ) >= 2 :
104+ model = args [1 ].get ("modelId" )
105+ request_body = args [1 ].get ("body" )
106+ is_embedding = "embed" in model
107+ else :
108+ model = ""
109+ request_body = None
110+ is_embedding = False
93111
94112 try :
95113 response = await wrapped (* args , ** kwargs )
@@ -98,7 +116,18 @@ async def wrap_client__make_api_call(wrapped, instance, args, kwargs):
98116 exc , is_embedding , model , span_id , trace_id , request_extractor , request_body , ft , transaction
99117 )
100118
101- if not response :
119+ if not response or response_streaming and not settings .ai_monitoring .streaming .enabled :
120+ if ft :
121+ ft .__exit__ (None , None , None )
122+ return response
123+
124+ if response_streaming and is_embedding :
125+ # This combination is not supported at time of writing, but may become
126+ # a supported feature in the future. Instrumentation will need to be written
127+ # if this becomes available.
128+ _logger .warning (EMBEDDING_STREAMING_UNSUPPORTED_LOG_MESSAGE )
129+ if ft :
130+ ft .__exit__ (None , None , None )
102131 return response
103132
104133 response_headers = response .get ("ResponseMetadata" , {}).get ("HTTPHeaders" ) or {}
@@ -112,13 +141,22 @@ async def wrap_client__make_api_call(wrapped, instance, args, kwargs):
112141 run_bedrock_request_extractor (request_extractor , request_body , bedrock_attrs )
113142
114143 try :
144+ if response_streaming :
145+ # Wrap EventStream object here to intercept __iter__ method instead of instrumenting class.
146+ # This class is used in numerous other services in botocore, and would cause conflicts.
147+ response ["body" ] = body = AsyncEventStreamWrapper (response ["body" ])
148+ body ._nr_ft = ft or None
149+ body ._nr_bedrock_attrs = bedrock_attrs or {}
150+ body ._nr_model_extractor = stream_extractor or None
151+ return response
152+
115153 # Read and replace response streaming bodies
116154 response_body = await response ["body" ].read ()
155+
117156 if ft :
118157 ft .__exit__ (None , None , None )
119158 bedrock_attrs ["duration" ] = ft .duration * 1000
120159 response ["body" ] = StreamingBody (AsyncBytesIO (response_body ), len (response_body ))
121-
122160 run_bedrock_response_extractor (response_extractor , response_body , bedrock_attrs , is_embedding , transaction )
123161
124162 except Exception :
0 commit comments