12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
+
16
+ import logging
17
+ import sys
18
+ import uuid
19
+
15
20
from newrelic .api .function_trace import FunctionTrace
21
+ from newrelic .api .time_trace import get_trace_linking_metadata
16
22
from newrelic .api .transaction import current_transaction
17
23
from newrelic .common .object_names import callable_name
18
24
from newrelic .common .object_wrapper import wrap_function_wrapper
25
+ from newrelic .common .package_version_utils import get_package_version
19
26
from newrelic .common .signature import bind_args
27
+ from newrelic .core .config import global_settings
28
+
29
+ # Check for the presence of the autogen-core, autogen-agentchat, or autogen-ext package as they should all have the
30
+ # same version and one or multiple could be installed
31
+ AUTOGEN_VERSION = (
32
+ get_package_version ("autogen-core" )
33
+ or get_package_version ("autogen-agentchat" )
34
+ or get_package_version ("autogen-ext" )
35
+ )
36
+
37
+
38
+ RECORD_EVENTS_FAILURE_LOG_MESSAGE = "Exception occurred in Autogen instrumentation: Failed to record LLM events. Please report this issue to New Relic Support.\n %s"
39
+
40
+
41
+ _logger = logging .getLogger (__name__ )
20
42
21
43
22
44
async def wrap_from_server_params (wrapped , instance , args , kwargs ):
@@ -32,6 +54,123 @@ async def wrap_from_server_params(wrapped, instance, args, kwargs):
32
54
return await wrapped (* args , ** kwargs )
33
55
34
56
57
+ def wrap_on_messages_stream (wrapped , instance , args , kwargs ):
58
+ transaction = current_transaction ()
59
+ if not transaction :
60
+ return wrapped (* args , ** kwargs )
61
+
62
+ agent_name = getattr (instance , "name" , "agent" )
63
+ func_name = callable_name (wrapped )
64
+ function_trace_name = f"{ func_name } /{ agent_name } "
65
+ with FunctionTrace (name = function_trace_name , group = "Llm" , source = wrapped ):
66
+ return wrapped (* args , ** kwargs )
67
+
68
+
69
+ def _get_llm_metadata (transaction ):
70
+ # Grab LLM-related custom attributes off of the transaction to store as metadata on LLM events
71
+ custom_attrs_dict = transaction ._custom_params
72
+ llm_metadata_dict = {key : value for key , value in custom_attrs_dict .items () if key .startswith ("llm." )}
73
+ llm_context_attrs = getattr (transaction , "_llm_context_attrs" , None )
74
+ if llm_context_attrs :
75
+ llm_metadata_dict .update (llm_context_attrs )
76
+
77
+ return llm_metadata_dict
78
+
79
+
80
+ def _extract_tool_output (return_val , tool_name ):
81
+ try :
82
+ output = getattr (return_val [1 ], "content" , None )
83
+ return output
84
+ except Exception :
85
+ _logger .warning ("Unable to parse tool output value from %s. Omitting output from LlmTool event." , tool_name )
86
+ return None
87
+
88
+
89
+ def _construct_base_tool_event_dict (bound_args , tool_call_data , tool_id , transaction , settings ):
90
+ try :
91
+ _input = getattr (tool_call_data , "arguments" , None )
92
+ tool_input = str (_input ) if _input else None
93
+ run_id = getattr (tool_call_data , "id" , None )
94
+ tool_name = getattr (tool_call_data , "name" , "tool" )
95
+ agent_name = bound_args .get ("agent_name" )
96
+ linking_metadata = get_trace_linking_metadata ()
97
+
98
+ tool_event_dict = {
99
+ "id" : tool_id ,
100
+ "run_id" : run_id ,
101
+ "name" : tool_name ,
102
+ "span_id" : linking_metadata .get ("span.id" ),
103
+ "trace_id" : linking_metadata .get ("trace.id" ),
104
+ "agent_name" : agent_name ,
105
+ "vendor" : "autogen" ,
106
+ "ingest_source" : "Python" ,
107
+ }
108
+ if settings .ai_monitoring .record_content .enabled :
109
+ tool_event_dict .update ({"input" : tool_input })
110
+ tool_event_dict .update (_get_llm_metadata (transaction ))
111
+ except Exception :
112
+ tool_event_dict = {}
113
+ _logger .warning (RECORD_EVENTS_FAILURE_LOG_MESSAGE , exc_info = True )
114
+
115
+ return tool_event_dict
116
+
117
+
118
+ async def wrap__execute_tool_call (wrapped , instance , args , kwargs ):
119
+ transaction = current_transaction ()
120
+ if not transaction :
121
+ return await wrapped (* args , ** kwargs )
122
+
123
+ settings = transaction .settings or global_settings ()
124
+ if not settings .ai_monitoring .enabled :
125
+ return await wrapped (* args , ** kwargs )
126
+
127
+ # Framework metric also used for entity tagging in the UI
128
+ transaction .add_ml_model_info ("Autogen" , AUTOGEN_VERSION )
129
+ transaction ._add_agent_attribute ("llm" , True )
130
+
131
+ tool_id = str (uuid .uuid4 ())
132
+ bound_args = bind_args (wrapped , args , kwargs )
133
+ tool_call_data = bound_args .get ("tool_call" )
134
+ tool_event_dict = _construct_base_tool_event_dict (bound_args , tool_call_data , tool_id , transaction , settings )
135
+
136
+ tool_name = getattr (tool_call_data , "name" , "tool" )
137
+
138
+ func_name = callable_name (wrapped )
139
+ ft = FunctionTrace (name = f"{ func_name } /{ tool_name } " , group = "Llm/tool/Autogen" )
140
+ ft .__enter__ ()
141
+
142
+ try :
143
+ return_val = await wrapped (* args , ** kwargs )
144
+ except Exception :
145
+ ft .notice_error (attributes = {"tool_id" : tool_id })
146
+ ft .__exit__ (* sys .exc_info ())
147
+ # If we hit an exception, append the error attribute and duration from the exited function trace
148
+ tool_event_dict .update ({"duration" : ft .duration * 1000 , "error" : True })
149
+ transaction .record_custom_event ("LlmTool" , tool_event_dict )
150
+ raise
151
+
152
+ ft .__exit__ (None , None , None )
153
+
154
+ tool_event_dict .update ({"duration" : ft .duration * 1000 })
155
+
156
+ # If the tool was executed successfully, we can grab the tool output from the result
157
+ tool_output = _extract_tool_output (return_val , tool_name )
158
+ if settings .ai_monitoring .record_content .enabled :
159
+ tool_event_dict .update ({"output" : tool_output })
160
+
161
+ transaction .record_custom_event ("LlmTool" , tool_event_dict )
162
+
163
+ return return_val
164
+
165
+
166
+ def instrument_autogen_agentchat_agents__assistant_agent (module ):
167
+ if hasattr (module , "AssistantAgent" ):
168
+ if hasattr (module .AssistantAgent , "on_messages_stream" ):
169
+ wrap_function_wrapper (module , "AssistantAgent.on_messages_stream" , wrap_on_messages_stream )
170
+ if hasattr (module .AssistantAgent , "_execute_tool_call" ):
171
+ wrap_function_wrapper (module , "AssistantAgent._execute_tool_call" , wrap__execute_tool_call )
172
+
173
+
35
174
def instrument_autogen_ext_tools_mcp__base (module ):
36
175
if hasattr (module , "McpToolAdapter" ):
37
176
if hasattr (module .McpToolAdapter , "from_server_params" ):
0 commit comments