1212# See the License for the specific language governing permissions and
1313# limitations under the License.
1414
15+
16+ import logging
17+ import sys
18+ import uuid
19+
1520from newrelic .api .function_trace import FunctionTrace
21+ from newrelic .api .time_trace import get_trace_linking_metadata
1622from newrelic .api .transaction import current_transaction
1723from newrelic .common .object_names import callable_name
1824from newrelic .common .object_wrapper import wrap_function_wrapper
25+ from newrelic .common .package_version_utils import get_package_version
1926from 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__ )
2042
2143
2244async def wrap_from_server_params (wrapped , instance , args , kwargs ):
@@ -32,6 +54,123 @@ async def wrap_from_server_params(wrapped, instance, args, kwargs):
3254 return await wrapped (* args , ** kwargs )
3355
3456
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+
35174def instrument_autogen_ext_tools_mcp__base (module ):
36175 if hasattr (module , "McpToolAdapter" ):
37176 if hasattr (module .McpToolAdapter , "from_server_params" ):
0 commit comments