@@ -768,6 +768,7 @@ async def wrap_chain_async_run(wrapped, instance, args, kwargs):
768768 run_args ["timestamp" ] = int (1000.0 * time .time ())
769769 completion_id = str (uuid .uuid4 ())
770770 add_nr_completion_id (run_args , completion_id )
771+ nr_callback_handler = _attach_nr_callback_handler (run_args )
771772 # Check to see if launched from agent or directly from chain.
772773 # The trace group will reflect from where it has started.
773774 # The AgentExecutor class has an attribute "agent" that does
@@ -788,6 +789,7 @@ async def wrap_chain_async_run(wrapped, instance, args, kwargs):
788789 completion_id = completion_id ,
789790 linking_metadata = linking_metadata ,
790791 duration = ft .duration * 1000 ,
792+ callback_handler = nr_callback_handler ,
791793 )
792794 raise
793795 ft .__exit__ (None , None , None )
@@ -803,6 +805,7 @@ async def wrap_chain_async_run(wrapped, instance, args, kwargs):
803805 response = response ,
804806 linking_metadata = linking_metadata ,
805807 duration = ft .duration * 1000 ,
808+ callback_handler = nr_callback_handler ,
806809 )
807810 return response
808811
@@ -824,6 +827,7 @@ def wrap_chain_sync_run(wrapped, instance, args, kwargs):
824827 run_args ["timestamp" ] = int (1000.0 * time .time ())
825828 completion_id = str (uuid .uuid4 ())
826829 add_nr_completion_id (run_args , completion_id )
830+ nr_callback_handler = _attach_nr_callback_handler (run_args )
827831 # Check to see if launched from agent or directly from chain.
828832 # The trace group will reflect from where it has started.
829833 # The AgentExecutor class has an attribute "agent" that does
@@ -844,6 +848,7 @@ def wrap_chain_sync_run(wrapped, instance, args, kwargs):
844848 completion_id = completion_id ,
845849 linking_metadata = linking_metadata ,
846850 duration = ft .duration * 1000 ,
851+ callback_handler = nr_callback_handler ,
847852 )
848853 raise
849854 ft .__exit__ (None , None , None )
@@ -859,6 +864,7 @@ def wrap_chain_sync_run(wrapped, instance, args, kwargs):
859864 response = response ,
860865 linking_metadata = linking_metadata ,
861866 duration = ft .duration * 1000 ,
867+ callback_handler = nr_callback_handler ,
862868 )
863869 return response
864870
@@ -880,6 +886,7 @@ def wrap_RunnableSequence_stream(wrapped, instance, args, kwargs):
880886 run_args ["timestamp" ] = int (1000.0 * time .time ())
881887 completion_id = str (uuid .uuid4 ())
882888 add_nr_completion_id (run_args , completion_id )
889+ nr_callback_handler = _attach_nr_callback_handler (run_args )
883890 # Check to see if launched from agent or directly from chain.
884891 # The trace group will reflect from where it has started.
885892 # The AgentExecutor class has an attribute "agent" that does
@@ -899,18 +906,25 @@ def wrap_RunnableSequence_stream(wrapped, instance, args, kwargs):
899906 completion_id = completion_id ,
900907 response = [],
901908 linking_metadata = linking_metadata ,
909+ callback_handler = nr_callback_handler ,
902910 ),
903911 on_error = _on_chain_error (
904912 ft = ft ,
905913 instance = instance ,
906914 run_args = run_args ,
907915 completion_id = completion_id ,
908916 linking_metadata = linking_metadata ,
917+ callback_handler = nr_callback_handler ,
909918 ),
910919 )
911920 except Exception :
912921 _on_chain_error (
913- ft = ft , instance = instance , run_args = run_args , completion_id = completion_id , linking_metadata = linking_metadata
922+ ft = ft ,
923+ instance = instance ,
924+ run_args = run_args ,
925+ completion_id = completion_id ,
926+ linking_metadata = linking_metadata ,
927+ callback_handler = nr_callback_handler ,
914928 )(transaction )
915929 raise
916930
@@ -934,6 +948,7 @@ def wrap_RunnableSequence_astream(wrapped, instance, args, kwargs):
934948 run_args ["timestamp" ] = int (1000.0 * time .time ())
935949 completion_id = str (uuid .uuid4 ())
936950 add_nr_completion_id (run_args , completion_id )
951+ nr_callback_handler = _attach_nr_callback_handler (run_args )
937952 # Check to see if launched from agent or directly from chain.
938953 # The trace group will reflect from where it has started.
939954 # The AgentExecutor class has an attribute "agent" that does
@@ -953,25 +968,34 @@ def wrap_RunnableSequence_astream(wrapped, instance, args, kwargs):
953968 completion_id = completion_id ,
954969 response = [],
955970 linking_metadata = linking_metadata ,
971+ callback_handler = nr_callback_handler ,
956972 ),
957973 on_error = _on_chain_error (
958974 ft = ft ,
959975 instance = instance ,
960976 run_args = run_args ,
961977 completion_id = completion_id ,
962978 linking_metadata = linking_metadata ,
979+ callback_handler = nr_callback_handler ,
963980 ),
964981 )
965982 except Exception :
966983 _on_chain_error (
967- ft = ft , instance = instance , run_args = run_args , completion_id = completion_id , linking_metadata = linking_metadata
984+ ft = ft ,
985+ instance = instance ,
986+ run_args = run_args ,
987+ completion_id = completion_id ,
988+ linking_metadata = linking_metadata ,
989+ callback_handler = nr_callback_handler ,
968990 )(transaction )
969991 raise
970992
971993 return return_val
972994
973995
974- def _on_chain_stop_iteration (* , ft , instance , run_args , completion_id , response , linking_metadata ):
996+ def _on_chain_stop_iteration (
997+ * , ft , instance , run_args , completion_id , response , linking_metadata , callback_handler = None
998+ ):
975999 def _on_stop_iteration (proxy , transaction ):
9761000 ft .__exit__ (None , None , None )
9771001 _create_successful_chain_run_events (
@@ -982,12 +1006,13 @@ def _on_stop_iteration(proxy, transaction):
9821006 response = response ,
9831007 linking_metadata = linking_metadata ,
9841008 duration = ft .duration * 1000 ,
1009+ callback_handler = callback_handler ,
9851010 )
9861011
9871012 return _on_stop_iteration
9881013
9891014
990- def _on_chain_error (* , ft , instance , run_args , completion_id , linking_metadata ):
1015+ def _on_chain_error (* , ft , instance , run_args , completion_id , linking_metadata , callback_handler = None ):
9911016 def _on_error (proxy , transaction ):
9921017 ft .notice_error (attributes = {"completion_id" : completion_id })
9931018 ft .__exit__ (* sys .exc_info ())
@@ -998,6 +1023,7 @@ def _on_error(proxy, transaction):
9981023 completion_id = completion_id ,
9991024 linking_metadata = linking_metadata ,
10001025 duration = ft .duration * 1000 ,
1026+ callback_handler = callback_handler ,
10011027 )
10021028
10031029 return _on_error
@@ -1015,13 +1041,109 @@ def add_nr_completion_id(run_args, completion_id):
10151041 run_args ["config" ]["metadata" ] = metadata
10161042
10171043
1018- def _create_error_chain_run_events (* , transaction , instance , run_args , completion_id , linking_metadata , duration ):
1044+ _NR_CALLBACK_HANDLER_CLS = None
1045+
1046+
1047+ def _get_nr_callback_handler_cls ():
1048+ global _NR_CALLBACK_HANDLER_CLS
1049+ if _NR_CALLBACK_HANDLER_CLS is not None :
1050+ return _NR_CALLBACK_HANDLER_CLS
1051+ try :
1052+ from langchain_core .callbacks import BaseCallbackHandler
1053+ except ImportError :
1054+ _NR_CALLBACK_HANDLER_CLS = False
1055+ return False
1056+
1057+ class NewRelicCallbackHandler (BaseCallbackHandler ):
1058+ raise_error = False
1059+
1060+ def __init__ (self ):
1061+ super ().__init__ ()
1062+ self .response_model = None
1063+
1064+ def on_llm_end (self , response , ** _kwargs ):
1065+ if self .response_model is not None :
1066+ return
1067+ try :
1068+ self .response_model = _extract_chain_response_model (response )
1069+ except Exception :
1070+ pass
1071+
1072+ _NR_CALLBACK_HANDLER_CLS = NewRelicCallbackHandler
1073+ return NewRelicCallbackHandler
1074+
1075+
1076+ def _attach_nr_callback_handler (run_args ):
1077+ cls = _get_nr_callback_handler_cls ()
1078+ if not cls :
1079+ return None
1080+ handler = cls ()
1081+ cfg = dict (run_args .get ("config" ) or {})
1082+ callbacks = cfg .get ("callbacks" )
1083+ if callbacks is None :
1084+ cfg ["callbacks" ] = [handler ]
1085+ elif isinstance (callbacks , list ):
1086+ cfg ["callbacks" ] = [* callbacks , handler ]
1087+ else :
1088+ try :
1089+ callbacks .add_handler (handler , inherit = True )
1090+ except Exception :
1091+ return None
1092+ run_args ["config" ] = cfg
1093+ return handler
1094+
1095+
1096+ def _extract_chain_response_model (response ):
1097+ llm_output = getattr (response , "llm_output" , None ) or {}
1098+ for key in ("model_name" , "model" , "model_id" ):
1099+ val = llm_output .get (key )
1100+ if isinstance (val , str ) and val :
1101+ return val
1102+ generations = getattr (response , "generations" , None ) or []
1103+ for gen_list in generations :
1104+ for gen in gen_list or []:
1105+ info = getattr (gen , "generation_info" , None ) or {}
1106+ for key in ("model_name" , "model" , "model_id" ):
1107+ val = info .get (key )
1108+ if isinstance (val , str ) and val :
1109+ return val
1110+ return None
1111+
1112+
1113+ def _get_chain_request_model (instance ):
1114+ # A best effort attempt to pull the request model from the chain or any of
1115+ # its steps for better observability. This is not guaranteed to work in all
1116+ # cases as it depends on how the chain and steps are implemented, and it can
1117+ # only pull the first model it finds. The request model is not a guaranteed
1118+ # attribute on chains or steps, but some implementations may have it.
1119+ try :
1120+ llm = getattr (instance , "llm" , None )
1121+ if llm is not None :
1122+ name = getattr (llm , "model_name" , None ) or getattr (llm , "model" , None )
1123+ if isinstance (name , str ) and name :
1124+ return name
1125+ steps = getattr (instance , "steps" , None )
1126+ if steps :
1127+ for step in steps :
1128+ name = getattr (step , "model_name" , None ) or getattr (step , "model" , None )
1129+ if isinstance (name , str ) and name :
1130+ return name
1131+ except Exception :
1132+ pass
1133+ return None
1134+
1135+
1136+ def _create_error_chain_run_events (
1137+ * , transaction , instance , run_args , completion_id , linking_metadata , duration , callback_handler = None
1138+ ):
10191139 _input = run_args .get ("input" )
10201140 llm_metadata_dict = _get_llm_metadata (transaction )
10211141 run_id , metadata , tags = _get_run_manager_info (transaction , run_args , instance , completion_id )
10221142 span_id = linking_metadata .get ("span.id" )
10231143 trace_id = linking_metadata .get ("trace.id" )
10241144 input_message_list = [_input ]
1145+ request_model = _get_chain_request_model (instance )
1146+ response_model = getattr (callback_handler , "response_model" , None ) if callback_handler else None
10251147
10261148 # Make sure the builtin attributes take precedence over metadata attributes.
10271149 full_chat_completion_summary_dict = {f"metadata.{ key } " : value for key , value in metadata .items ()}
@@ -1035,6 +1157,8 @@ def _create_error_chain_run_events(*, transaction, instance, run_args, completio
10351157 "virtual_llm" : True ,
10361158 "request_id" : run_id ,
10371159 "duration" : duration ,
1160+ "request.model" : request_model ,
1161+ "response.model" : response_model ,
10381162 "response.number_of_messages" : len (input_message_list ),
10391163 "tags" : tags ,
10401164 "error" : True ,
@@ -1053,6 +1177,7 @@ def _create_error_chain_run_events(*, transaction, instance, run_args, completio
10531177 llm_metadata_dict = llm_metadata_dict ,
10541178 output_message_list = [],
10551179 request_timestamp = run_args ["timestamp" ] or None ,
1180+ response_model = response_model ,
10561181 )
10571182
10581183
@@ -1069,7 +1194,7 @@ def _get_run_manager_info(transaction, run_args, instance, completion_id):
10691194
10701195
10711196def _create_successful_chain_run_events (
1072- * , transaction , instance , run_args , completion_id , response , linking_metadata , duration
1197+ * , transaction , instance , run_args , completion_id , response , linking_metadata , duration , callback_handler = None
10731198):
10741199 _input = run_args .get ("input" )
10751200 llm_metadata_dict = _get_llm_metadata (transaction )
@@ -1078,6 +1203,8 @@ def _create_successful_chain_run_events(
10781203 trace_id = linking_metadata .get ("trace.id" )
10791204 input_message_list = [_input ]
10801205 output_message_list = []
1206+ request_model = _get_chain_request_model (instance )
1207+ response_model = getattr (callback_handler , "response_model" , None ) if callback_handler else None
10811208 if isinstance (response , str ):
10821209 output_message_list = [response ]
10831210 else :
@@ -1104,6 +1231,8 @@ def _create_successful_chain_run_events(
11041231 "virtual_llm" : True ,
11051232 "request_id" : run_id ,
11061233 "duration" : duration ,
1234+ "request.model" : request_model ,
1235+ "response.model" : response_model ,
11071236 "response.number_of_messages" : len (input_message_list ) + len (output_message_list ),
11081237 "tags" : tags ,
11091238 "timestamp" : run_args .get ("timestamp" ) or None ,
@@ -1125,6 +1254,7 @@ def _create_successful_chain_run_events(
11251254 llm_metadata_dict = llm_metadata_dict ,
11261255 output_message_list = output_message_list ,
11271256 request_timestamp = run_args ["timestamp" ] or None ,
1257+ response_model = response_model ,
11281258 )
11291259
11301260
@@ -1139,6 +1269,7 @@ def create_chat_completion_message_event(
11391269 llm_metadata_dict ,
11401270 output_message_list ,
11411271 request_timestamp = None ,
1272+ response_model = None ,
11421273):
11431274 settings = transaction .settings if transaction .settings is not None else global_settings ()
11441275
@@ -1151,6 +1282,7 @@ def create_chat_completion_message_event(
11511282 "trace_id" : trace_id ,
11521283 "completion_id" : chat_completion_id ,
11531284 "sequence" : index ,
1285+ "response.model" : response_model ,
11541286 "vendor" : "langchain" ,
11551287 "ingest_source" : "Python" ,
11561288 "virtual_llm" : True ,
@@ -1176,6 +1308,7 @@ def create_chat_completion_message_event(
11761308 "trace_id" : trace_id ,
11771309 "completion_id" : chat_completion_id ,
11781310 "sequence" : index ,
1311+ "response.model" : response_model ,
11791312 "vendor" : "langchain" ,
11801313 "ingest_source" : "Python" ,
11811314 "is_response" : True ,
0 commit comments