monocle-apptrace 0.4.1__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of monocle-apptrace might be problematic. Click here for more details.
- monocle_apptrace/__main__.py +1 -1
- monocle_apptrace/exporters/file_exporter.py +125 -37
- monocle_apptrace/instrumentation/common/__init__.py +16 -1
- monocle_apptrace/instrumentation/common/constants.py +14 -1
- monocle_apptrace/instrumentation/common/instrumentor.py +19 -152
- monocle_apptrace/instrumentation/common/method_wrappers.py +376 -0
- monocle_apptrace/instrumentation/common/span_handler.py +58 -32
- monocle_apptrace/instrumentation/common/utils.py +52 -15
- monocle_apptrace/instrumentation/common/wrapper.py +124 -18
- monocle_apptrace/instrumentation/common/wrapper_method.py +48 -1
- monocle_apptrace/instrumentation/metamodel/a2a/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/a2a/_helper.py +37 -0
- monocle_apptrace/instrumentation/metamodel/a2a/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/a2a/entities/inference.py +112 -0
- monocle_apptrace/instrumentation/metamodel/a2a/methods.py +22 -0
- monocle_apptrace/instrumentation/metamodel/adk/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/adk/_helper.py +182 -0
- monocle_apptrace/instrumentation/metamodel/adk/entities/agent.py +50 -0
- monocle_apptrace/instrumentation/metamodel/adk/entities/tool.py +57 -0
- monocle_apptrace/instrumentation/metamodel/adk/methods.py +24 -0
- monocle_apptrace/instrumentation/metamodel/agents/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/agents/_helper.py +220 -0
- monocle_apptrace/instrumentation/metamodel/agents/agents_processor.py +152 -0
- monocle_apptrace/instrumentation/metamodel/agents/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/agents/entities/inference.py +191 -0
- monocle_apptrace/instrumentation/metamodel/agents/methods.py +56 -0
- monocle_apptrace/instrumentation/metamodel/aiohttp/_helper.py +6 -11
- monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +112 -18
- monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +18 -10
- monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +13 -11
- monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +5 -0
- monocle_apptrace/instrumentation/metamodel/azureaiinference/_helper.py +88 -8
- monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +22 -8
- monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +92 -16
- monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +13 -8
- monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +1 -1
- monocle_apptrace/instrumentation/metamodel/fastapi/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/_helper.py +82 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/entities/http.py +44 -0
- monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +23 -0
- monocle_apptrace/instrumentation/metamodel/finish_types.py +463 -0
- monocle_apptrace/instrumentation/metamodel/flask/_helper.py +6 -11
- monocle_apptrace/instrumentation/metamodel/gemini/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/gemini/_helper.py +120 -0
- monocle_apptrace/instrumentation/metamodel/gemini/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/gemini/entities/inference.py +86 -0
- monocle_apptrace/instrumentation/metamodel/gemini/entities/retrieval.py +43 -0
- monocle_apptrace/instrumentation/metamodel/gemini/methods.py +31 -0
- monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +79 -8
- monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +15 -10
- monocle_apptrace/instrumentation/metamodel/haystack/methods.py +7 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/_helper.py +78 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/entities/http.py +51 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/methods.py +23 -0
- monocle_apptrace/instrumentation/metamodel/lambdafunc/wrapper.py +23 -0
- monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +145 -19
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +19 -10
- monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +67 -10
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +127 -20
- monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +46 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +35 -9
- monocle_apptrace/instrumentation/metamodel/litellm/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/litellm/_helper.py +89 -0
- monocle_apptrace/instrumentation/metamodel/litellm/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/litellm/entities/inference.py +108 -0
- monocle_apptrace/instrumentation/metamodel/litellm/methods.py +19 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +227 -16
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +127 -10
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +13 -8
- monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +62 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +68 -1
- monocle_apptrace/instrumentation/metamodel/mcp/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +118 -0
- monocle_apptrace/instrumentation/metamodel/mcp/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/mcp/entities/inference.py +48 -0
- monocle_apptrace/instrumentation/metamodel/mcp/mcp_processor.py +8 -0
- monocle_apptrace/instrumentation/metamodel/mcp/methods.py +21 -0
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +188 -16
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +148 -92
- monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +1 -1
- monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +53 -23
- monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +1 -1
- monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +15 -9
- monocle_apptrace/instrumentation/metamodel/teamsai/sample.json +0 -4
- {monocle_apptrace-0.4.1.dist-info → monocle_apptrace-0.5.0.dist-info}/METADATA +27 -11
- monocle_apptrace-0.5.0.dist-info/RECORD +142 -0
- monocle_apptrace-0.4.1.dist-info/RECORD +0 -96
- {monocle_apptrace-0.4.1.dist-info → monocle_apptrace-0.5.0.dist-info}/WHEEL +0 -0
- {monocle_apptrace-0.4.1.dist-info → monocle_apptrace-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {monocle_apptrace-0.4.1.dist-info → monocle_apptrace-0.5.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for the ADK (Agent Development Kit) metamodel instrumentation.
|
|
3
|
+
This module provides utility functions to extract various attributes from agent and tool instances.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ast import arguments
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
from monocle_apptrace.instrumentation.metamodel.finish_types import map_adk_finish_reason_to_finish_type
|
|
9
|
+
|
|
10
|
+
def get_model_name(args):
|
|
11
|
+
return args[0].model if hasattr(args[0], 'model') else None
|
|
12
|
+
|
|
13
|
+
def get_inference_type(arguments):
|
|
14
|
+
""" Find inference type from argument """
|
|
15
|
+
return 'inference.gemini' ## TBD verify non-gemini inference types
|
|
16
|
+
|
|
17
|
+
def extract_inference_endpoint(instance):
|
|
18
|
+
""" Get inference service end point"""
|
|
19
|
+
if hasattr(instance,'api_client') and hasattr(instance.api_client, '_api_client'):
|
|
20
|
+
if hasattr(instance.api_client._api_client._http_options,'base_url'):
|
|
21
|
+
return instance.api_client._api_client._http_options.base_url
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
def extract_message(arguments):
|
|
25
|
+
return str(arguments['args'][0].contents)
|
|
26
|
+
|
|
27
|
+
def extract_assistant_message(arguments):
|
|
28
|
+
return str(arguments['result'].content.parts)
|
|
29
|
+
|
|
30
|
+
def update_span_from_llm_response(response, instance):
|
|
31
|
+
meta_dict = {}
|
|
32
|
+
if response is not None and hasattr(response, "usage_metadata") and response.usage_metadata is not None:
|
|
33
|
+
token_usage = response.usage_metadata
|
|
34
|
+
if token_usage is not None:
|
|
35
|
+
meta_dict.update({"completion_tokens": token_usage.candidates_token_count})
|
|
36
|
+
meta_dict.update({"prompt_tokens": token_usage.prompt_token_count })
|
|
37
|
+
meta_dict.update({"total_tokens": token_usage.total_token_count})
|
|
38
|
+
return meta_dict
|
|
39
|
+
|
|
40
|
+
def extract_finish_reason(arguments):
|
|
41
|
+
if arguments["exception"] is not None:
|
|
42
|
+
return None
|
|
43
|
+
if hasattr(arguments['result'], 'error_code'):
|
|
44
|
+
return arguments['result'].error_code
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
def map_finish_reason_to_finish_type(finish_reason:str):
|
|
48
|
+
return map_adk_finish_reason_to_finish_type(finish_reason)
|
|
49
|
+
|
|
50
|
+
def get_agent_name(instance: Any) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Extract the name of the agent from the given instance.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
instance: The agent instance to extract name from
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: The name of the agent, or a default value if not found
|
|
59
|
+
"""
|
|
60
|
+
return getattr(instance, 'name', 'unknown_agent')
|
|
61
|
+
|
|
62
|
+
def get_agent_description(instance: Any) -> str:
|
|
63
|
+
"""
|
|
64
|
+
Extract the description of the agent from the given instance.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
instance: The agent instance to extract description from
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
str: The description of the agent, or a default value if not found
|
|
71
|
+
"""
|
|
72
|
+
return getattr(instance, 'description', 'No description available')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def extract_agent_input(arguments: Dict[str, Any]) -> Any:
|
|
76
|
+
"""
|
|
77
|
+
Extract the input data from agent arguments.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
arguments: Dictionary containing agent call arguments
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Any: The extracted input data
|
|
84
|
+
"""
|
|
85
|
+
return arguments['args'][0].user_content.parts[0].text
|
|
86
|
+
|
|
87
|
+
def extract_agent_response(result: Any) -> Any:
|
|
88
|
+
"""
|
|
89
|
+
Extract the response data from agent result.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
result: The result returned by the agent
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Any: The extracted response data
|
|
96
|
+
"""
|
|
97
|
+
if result:
|
|
98
|
+
return result.content.parts[0].text
|
|
99
|
+
|
|
100
|
+
def get_tool_name(instance: Any) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Extract the name of the tool from the given instance.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
instance: The tool instance to extract name from
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
str: The name of the tool, or a default value if not found
|
|
109
|
+
"""
|
|
110
|
+
return getattr(instance, 'name', getattr(instance, '__name__', 'unknown_tool'))
|
|
111
|
+
|
|
112
|
+
def get_tool_description(instance: Any) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Extract the description of the tool from the given instance.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
instance: The tool instance to extract description from
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
str: The description of the tool, or a default value if not found
|
|
121
|
+
"""
|
|
122
|
+
return getattr(instance, 'description', getattr(instance, '__doc__', 'No description available'))
|
|
123
|
+
|
|
124
|
+
def get_source_agent(arguments) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Get the name of the source agent (the agent that is calling a tool or delegating to another agent).
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
str: The name of the source agent
|
|
130
|
+
"""
|
|
131
|
+
return arguments['kwargs']['tool_context'].agent_name
|
|
132
|
+
|
|
133
|
+
def get_delegating_agent(arguments) -> str:
|
|
134
|
+
"""
|
|
135
|
+
Get the name of the delegating agent (the agent that is delegating a task to another agent).
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
arguments: Dictionary containing agent call arguments
|
|
139
|
+
Returns:
|
|
140
|
+
str: The name of the delegating agent
|
|
141
|
+
"""
|
|
142
|
+
from_agent = arguments['args'][0].agent.name if hasattr(arguments['args'][0], 'agent') else None
|
|
143
|
+
if from_agent is not None:
|
|
144
|
+
if get_agent_name(arguments['instance']) == from_agent:
|
|
145
|
+
return None
|
|
146
|
+
return from_agent
|
|
147
|
+
|
|
148
|
+
def extract_tool_input(arguments: Dict[str, Any]) -> Any:
|
|
149
|
+
"""
|
|
150
|
+
Extract the input data from tool arguments.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
arguments: Dictionary containing tool call arguments
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Any: The extracted input data
|
|
157
|
+
"""
|
|
158
|
+
return str(arguments['kwargs'].get('args'))
|
|
159
|
+
|
|
160
|
+
def extract_tool_response(result: Any) -> Any:
|
|
161
|
+
"""
|
|
162
|
+
Extract the response data from tool result.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
result: The result returned by the tool
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Any: The extracted response data
|
|
169
|
+
"""
|
|
170
|
+
return str(result)
|
|
171
|
+
|
|
172
|
+
def get_target_agent(instance: Any) -> str:
|
|
173
|
+
"""
|
|
174
|
+
Extract the name of the target agent (the agent being called/delegated to).
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
instance: The target agent instance
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
str: The name of the target agent
|
|
181
|
+
"""
|
|
182
|
+
return getattr(instance, 'name', getattr(instance, '__name__', 'unknown_target_agent'))
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.metamodel.adk import _helper
|
|
2
|
+
AGENT = {
|
|
3
|
+
"type": "agentic.invocation",
|
|
4
|
+
"attributes": [
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
"_comment": "agent type",
|
|
8
|
+
"attribute": "type",
|
|
9
|
+
"accessor": lambda arguments:'agent.adk'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"_comment": "name of the agent",
|
|
13
|
+
"attribute": "name",
|
|
14
|
+
"accessor": lambda arguments: _helper.get_agent_name(arguments['instance'])
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"_comment": "agent description",
|
|
18
|
+
"attribute": "description",
|
|
19
|
+
"accessor": lambda arguments: _helper.get_agent_description(arguments['instance'])
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"_comment": "delegating agent name",
|
|
23
|
+
"attribute": "from_agent",
|
|
24
|
+
"accessor": lambda arguments: _helper.get_delegating_agent(arguments)
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
],
|
|
28
|
+
"events": [
|
|
29
|
+
{
|
|
30
|
+
"name":"data.input",
|
|
31
|
+
"attributes": [
|
|
32
|
+
{
|
|
33
|
+
"_comment": "this is Agent input",
|
|
34
|
+
"attribute": "query",
|
|
35
|
+
"accessor": lambda arguments: _helper.extract_agent_input(arguments)
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name":"data.output",
|
|
41
|
+
"attributes": [
|
|
42
|
+
{
|
|
43
|
+
"_comment": "this is response from LLM",
|
|
44
|
+
"attribute": "response",
|
|
45
|
+
"accessor": lambda arguments: _helper.extract_agent_response(arguments['result'])
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.metamodel.adk import _helper
|
|
2
|
+
TOOL = {
|
|
3
|
+
"type": "agentic.tool.invocation",
|
|
4
|
+
"attributes": [
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
"_comment": "tool type",
|
|
8
|
+
"attribute": "type",
|
|
9
|
+
"accessor": lambda arguments:'tool.adk'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"_comment": "name of the tool",
|
|
13
|
+
"attribute": "name",
|
|
14
|
+
"accessor": lambda arguments: _helper.get_tool_name(arguments['instance'])
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"_comment": "tool description",
|
|
18
|
+
"attribute": "description",
|
|
19
|
+
"accessor": lambda arguments: _helper.get_tool_description(arguments['instance'])
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
{
|
|
24
|
+
"_comment": "name of the agent",
|
|
25
|
+
"attribute": "name",
|
|
26
|
+
"accessor": lambda arguments: _helper.get_source_agent(arguments)
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"_comment": "agent type",
|
|
30
|
+
"attribute": "type",
|
|
31
|
+
"accessor": lambda arguments:'agent.adk'
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
],
|
|
35
|
+
"events": [
|
|
36
|
+
{
|
|
37
|
+
"name":"data.input",
|
|
38
|
+
"attributes": [
|
|
39
|
+
{
|
|
40
|
+
"_comment": "this is Tool input",
|
|
41
|
+
"attribute": "Inputs",
|
|
42
|
+
"accessor": lambda arguments: _helper.extract_tool_input(arguments)
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name":"data.output",
|
|
48
|
+
"attributes": [
|
|
49
|
+
{
|
|
50
|
+
"_comment": "this is response from Tool",
|
|
51
|
+
"attribute": "response",
|
|
52
|
+
"accessor": lambda arguments: _helper.extract_tool_response(arguments['result'])
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.common.wrapper import task_wrapper, atask_wrapper, atask_iter_wrapper
|
|
2
|
+
from monocle_apptrace.instrumentation.metamodel.adk.entities.agent import (
|
|
3
|
+
AGENT,
|
|
4
|
+
)
|
|
5
|
+
from monocle_apptrace.instrumentation.metamodel.adk.entities.tool import (
|
|
6
|
+
TOOL
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
ADK_METHODS = [
|
|
10
|
+
{
|
|
11
|
+
"package": "google.adk.agents.base_agent",
|
|
12
|
+
"object": "BaseAgent",
|
|
13
|
+
"method": "run_async",
|
|
14
|
+
"wrapper_method": atask_iter_wrapper,
|
|
15
|
+
"output_processor": AGENT,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"package": "google.adk.tools.function_tool",
|
|
19
|
+
"object": "FunctionTool",
|
|
20
|
+
"method": "run_async",
|
|
21
|
+
"wrapper_method": atask_wrapper,
|
|
22
|
+
"output_processor": TOOL,
|
|
23
|
+
}
|
|
24
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from opentelemetry.context import get_value
|
|
2
|
+
from monocle_apptrace.instrumentation.common.utils import (
|
|
3
|
+
resolve_from_alias,
|
|
4
|
+
get_json_dumps,
|
|
5
|
+
)
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
DELEGATION_NAME_PREFIX = "transfer_to_"
|
|
11
|
+
ROOT_AGENT_NAME = "AgentsSDK"
|
|
12
|
+
AGENTS_AGENT_NAME_KEY = "agent.openai_agents"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def extract_agent_response(response):
|
|
16
|
+
"""Extract the final output from an Agents SDK RunResult."""
|
|
17
|
+
try:
|
|
18
|
+
if response is not None and hasattr(response, "final_output"):
|
|
19
|
+
return str(response.final_output)
|
|
20
|
+
elif (
|
|
21
|
+
response is not None
|
|
22
|
+
and hasattr(response, "new_items")
|
|
23
|
+
and response.new_items
|
|
24
|
+
):
|
|
25
|
+
# Try to extract from new_items if final_output is not available
|
|
26
|
+
last_item = response.new_items[-1]
|
|
27
|
+
if hasattr(last_item, "content"):
|
|
28
|
+
return str(last_item.content)
|
|
29
|
+
elif hasattr(last_item, "text"):
|
|
30
|
+
return str(last_item.text)
|
|
31
|
+
elif (
|
|
32
|
+
response is not None
|
|
33
|
+
and hasattr(response, "next_step")
|
|
34
|
+
and response.next_step
|
|
35
|
+
and hasattr(response.next_step, "output")
|
|
36
|
+
and response.next_step.output
|
|
37
|
+
):
|
|
38
|
+
return str(response.next_step.output)
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.warning("Warning: Error occurred in extract_agent_response: %s", str(e))
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_agent_input(arguments):
|
|
45
|
+
"""Extract the input provided to the agent."""
|
|
46
|
+
try:
|
|
47
|
+
# For Runner.run and Runner.run_sync, the structure is:
|
|
48
|
+
# Runner.run(starting_agent, input, **kwargs)
|
|
49
|
+
# So args[0] = starting_agent, args[1] = input
|
|
50
|
+
if len(arguments["args"]) > 1:
|
|
51
|
+
input_data = arguments["args"][1]
|
|
52
|
+
if isinstance(input_data, str):
|
|
53
|
+
return input_data
|
|
54
|
+
elif isinstance(input_data, list):
|
|
55
|
+
# Handle list of input items
|
|
56
|
+
return get_json_dumps(input_data)
|
|
57
|
+
|
|
58
|
+
# Fallback to kwargs
|
|
59
|
+
if "original_input" in arguments["kwargs"]:
|
|
60
|
+
input_data = arguments["kwargs"]["original_input"]
|
|
61
|
+
if isinstance(input_data, str):
|
|
62
|
+
return input_data
|
|
63
|
+
elif isinstance(input_data, list):
|
|
64
|
+
return get_json_dumps(input_data)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.warning("Warning: Error occurred in extract_agent_input: %s", str(e))
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_agent_name(arguments) -> str:
|
|
71
|
+
"""Get the name of an agent."""
|
|
72
|
+
instance = None
|
|
73
|
+
# For Runner methods, the agent is passed as an argument, not instance
|
|
74
|
+
if arguments["args"] and len(arguments["args"]) > 0:
|
|
75
|
+
instance = arguments["args"][0]
|
|
76
|
+
else:
|
|
77
|
+
instance = arguments["kwargs"].get("agent")
|
|
78
|
+
if instance is None:
|
|
79
|
+
return "Unknown Agent"
|
|
80
|
+
return get_name(instance)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_agent_description(arguments) -> str:
|
|
84
|
+
"""Get the description of an agent."""
|
|
85
|
+
instance = None
|
|
86
|
+
# For Runner methods, the agent is passed as an argument, not instance
|
|
87
|
+
if arguments["args"] and len(arguments["args"]) > 0:
|
|
88
|
+
instance = arguments["args"][0]
|
|
89
|
+
else:
|
|
90
|
+
instance = arguments["kwargs"].get("agent")
|
|
91
|
+
if instance is None:
|
|
92
|
+
return ""
|
|
93
|
+
return get_description(instance)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_agent_instructions(arguments) -> str:
|
|
97
|
+
"""Get the instructions of an agent."""
|
|
98
|
+
instance = None
|
|
99
|
+
# For Runner methods, the agent is passed as an argument, not instance
|
|
100
|
+
if arguments["args"] and len(arguments["args"]) > 0:
|
|
101
|
+
instance = arguments["args"][0]
|
|
102
|
+
else:
|
|
103
|
+
instance = arguments["kwargs"].get("agent")
|
|
104
|
+
if instance is None:
|
|
105
|
+
return ""
|
|
106
|
+
if hasattr(instance, "instructions"):
|
|
107
|
+
instructions = instance.instructions
|
|
108
|
+
if isinstance(instructions, str):
|
|
109
|
+
return instructions
|
|
110
|
+
elif callable(instructions):
|
|
111
|
+
# For dynamic instructions, we can't easily extract them without context
|
|
112
|
+
return "Dynamic instructions (function)"
|
|
113
|
+
return ""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def extract_tool_response(result):
|
|
117
|
+
"""Extract response from a tool execution."""
|
|
118
|
+
if result is not None:
|
|
119
|
+
if hasattr(result, "output"):
|
|
120
|
+
return str(result.output)
|
|
121
|
+
elif hasattr(result, "content"):
|
|
122
|
+
return str(result.content)
|
|
123
|
+
else:
|
|
124
|
+
return str(result)
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def extract_tool_input(arguments):
|
|
129
|
+
"""Extract input arguments passed to a tool."""
|
|
130
|
+
try:
|
|
131
|
+
# For function tools, the input is typically in args
|
|
132
|
+
if len(arguments["args"]) > 1:
|
|
133
|
+
tool_input = arguments["args"][
|
|
134
|
+
1
|
|
135
|
+
] # Second argument is usually the JSON string with params
|
|
136
|
+
if isinstance(tool_input, str):
|
|
137
|
+
return [tool_input]
|
|
138
|
+
elif isinstance(tool_input, dict):
|
|
139
|
+
return [get_json_dumps(tool_input)]
|
|
140
|
+
|
|
141
|
+
# Fallback to all args
|
|
142
|
+
return [str(arg) for arg in arguments["args"]]
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.warning("Warning: Error occurred in extract_tool_input: %s", str(e))
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def get_name(instance):
|
|
149
|
+
"""Get the name of an agent or tool instance."""
|
|
150
|
+
if hasattr(instance, "name"):
|
|
151
|
+
return str(instance.name)
|
|
152
|
+
elif hasattr(instance, "__name__"):
|
|
153
|
+
return str(instance.__name__)
|
|
154
|
+
return ""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def get_runner_agent_name(instance) -> str:
|
|
158
|
+
"""Get the name of an agent."""
|
|
159
|
+
return get_name(instance)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_tool_name(instance) -> str:
|
|
163
|
+
"""Get the name of a tool."""
|
|
164
|
+
return get_name(instance)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def is_root_agent_name(instance) -> bool:
|
|
168
|
+
"""Check if this is the root agent."""
|
|
169
|
+
return get_name(instance) == ROOT_AGENT_NAME
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_source_agent() -> str:
|
|
173
|
+
"""Get the name of the agent that initiated the request."""
|
|
174
|
+
from_agent = get_value(AGENTS_AGENT_NAME_KEY)
|
|
175
|
+
return from_agent if from_agent is not None else ""
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_description(instance) -> str:
|
|
179
|
+
"""Get the description of an instance."""
|
|
180
|
+
if hasattr(instance, "description"):
|
|
181
|
+
return str(instance.description)
|
|
182
|
+
elif hasattr(instance, "handoff_description"):
|
|
183
|
+
return str(instance.handoff_description)
|
|
184
|
+
elif hasattr(instance, "__doc__") and instance.__doc__:
|
|
185
|
+
return str(instance.__doc__)
|
|
186
|
+
return ""
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_tool_description(instance) -> str:
|
|
190
|
+
"""Get the description of a tool."""
|
|
191
|
+
return get_description(instance)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def extract_handoff_target(arguments):
|
|
195
|
+
"""Extract the target agent from a handoff operation."""
|
|
196
|
+
try:
|
|
197
|
+
# Check if this is a handoff by looking at the result
|
|
198
|
+
return arguments.get("result").name
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.warning("Warning: Error occurred in extract_handoff_target: %s", str(e))
|
|
201
|
+
return ""
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def update_span_from_agent_response(response):
|
|
205
|
+
"""Update span with metadata from agent response."""
|
|
206
|
+
meta_dict = {}
|
|
207
|
+
try:
|
|
208
|
+
if response is not None and hasattr(response, "usage"):
|
|
209
|
+
usage = response.usage
|
|
210
|
+
if hasattr(usage, "completion_tokens"):
|
|
211
|
+
meta_dict.update({"completion_tokens": usage.completion_tokens})
|
|
212
|
+
if hasattr(usage, "prompt_tokens"):
|
|
213
|
+
meta_dict.update({"prompt_tokens": usage.prompt_tokens})
|
|
214
|
+
if hasattr(usage, "total_tokens"):
|
|
215
|
+
meta_dict.update({"total_tokens": usage.total_tokens})
|
|
216
|
+
except Exception as e:
|
|
217
|
+
logger.warning(
|
|
218
|
+
"Warning: Error occurred in update_span_from_agent_response: %s", str(e)
|
|
219
|
+
)
|
|
220
|
+
return meta_dict
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
from opentelemetry.context import attach, set_value, detach
|
|
4
|
+
from opentelemetry.trace import Tracer
|
|
5
|
+
from monocle_apptrace.instrumentation.common.constants import AGENT_PREFIX_KEY
|
|
6
|
+
from monocle_apptrace.instrumentation.common.span_handler import (
|
|
7
|
+
SpanHandler as BaseSpanHandler,
|
|
8
|
+
)
|
|
9
|
+
from monocle_apptrace.instrumentation.common.utils import with_tracer_wrapper
|
|
10
|
+
from monocle_apptrace.instrumentation.common.wrapper import atask_wrapper
|
|
11
|
+
from monocle_apptrace.instrumentation.metamodel.agents._helper import (
|
|
12
|
+
AGENTS_AGENT_NAME_KEY,
|
|
13
|
+
DELEGATION_NAME_PREFIX,
|
|
14
|
+
get_runner_agent_name,
|
|
15
|
+
)
|
|
16
|
+
from monocle_apptrace.instrumentation.metamodel.agents.entities.inference import (
|
|
17
|
+
AGENT_DELEGATION,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@with_tracer_wrapper
|
|
24
|
+
def constructor_wrapper(
|
|
25
|
+
tracer: Tracer,
|
|
26
|
+
handler: BaseSpanHandler,
|
|
27
|
+
to_wrap,
|
|
28
|
+
wrapped,
|
|
29
|
+
instance,
|
|
30
|
+
source_path,
|
|
31
|
+
args,
|
|
32
|
+
kwargs,
|
|
33
|
+
):
|
|
34
|
+
|
|
35
|
+
original_func = kwargs.get("on_invoke_tool", None)
|
|
36
|
+
result = None
|
|
37
|
+
tool_instance = SimpleNamespace(
|
|
38
|
+
name=kwargs.get("name", "unknown_tool"),
|
|
39
|
+
description=kwargs.get("description", "No description provided"),
|
|
40
|
+
)
|
|
41
|
+
if original_func and not getattr(original_func, "_monocle_wrapped", False):
|
|
42
|
+
# Now wrap the function with our instrumentation
|
|
43
|
+
async def wrapped_func(*func_args, **func_kwargs):
|
|
44
|
+
# Use the handler to create spans when the decorated function is called
|
|
45
|
+
return await atask_wrapper(tracer=tracer, handler=handler, to_wrap=to_wrap)(
|
|
46
|
+
wrapped=original_func,
|
|
47
|
+
instance=tool_instance,
|
|
48
|
+
source_path=source_path,
|
|
49
|
+
args=func_args,
|
|
50
|
+
kwargs=func_kwargs,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
kwargs["on_invoke_tool"] = wrapped_func
|
|
54
|
+
# Preserve function metadata
|
|
55
|
+
wrapped_func.__name__ = getattr(wrapped, "__name__", "unknown_tool")
|
|
56
|
+
wrapped_func.__doc__ = getattr(wrapped, "__doc__", "")
|
|
57
|
+
# mark function as wrapped
|
|
58
|
+
setattr(wrapped_func, "_monocle_wrapped", True)
|
|
59
|
+
|
|
60
|
+
result = wrapped(*args, **kwargs)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@with_tracer_wrapper
|
|
65
|
+
def handoff_constructor_wrapper(
|
|
66
|
+
tracer: Tracer,
|
|
67
|
+
handler: BaseSpanHandler,
|
|
68
|
+
to_wrap,
|
|
69
|
+
wrapped,
|
|
70
|
+
instance,
|
|
71
|
+
source_path,
|
|
72
|
+
args,
|
|
73
|
+
kwargs,
|
|
74
|
+
):
|
|
75
|
+
|
|
76
|
+
original_func = kwargs.get("on_invoke_handoff", None)
|
|
77
|
+
result = None
|
|
78
|
+
tool_instance = SimpleNamespace(
|
|
79
|
+
name=kwargs.get("name", "unknown_handoff"),
|
|
80
|
+
description=kwargs.get("description", "No description provided"),
|
|
81
|
+
)
|
|
82
|
+
if original_func and not getattr(original_func, "_monocle_wrapped", False):
|
|
83
|
+
# Now wrap the function with our instrumentation
|
|
84
|
+
async def wrapped_func(*func_args, **func_kwargs):
|
|
85
|
+
# Use the handler to create spans when the decorated function is called
|
|
86
|
+
return await atask_wrapper(tracer=tracer, handler=handler, to_wrap=to_wrap)(
|
|
87
|
+
wrapped=original_func,
|
|
88
|
+
instance=tool_instance,
|
|
89
|
+
source_path=source_path,
|
|
90
|
+
args=func_args,
|
|
91
|
+
kwargs=func_kwargs,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
kwargs["on_invoke_handoff"] = wrapped_func
|
|
95
|
+
# Preserve function metadata
|
|
96
|
+
wrapped_func.__name__ = getattr(wrapped, "__name__", "unknown_handoff")
|
|
97
|
+
wrapped_func.__doc__ = getattr(wrapped, "__doc__", "")
|
|
98
|
+
# mark function as wrapped
|
|
99
|
+
setattr(wrapped_func, "_monocle_wrapped", True)
|
|
100
|
+
|
|
101
|
+
result = wrapped(*args, **kwargs)
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AgentsSpanHandler(BaseSpanHandler):
|
|
106
|
+
"""Span handler for OpenAI Agents SDK."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, *args, **kwargs):
|
|
109
|
+
super().__init__(*args, **kwargs)
|
|
110
|
+
self.agent_context_token = None
|
|
111
|
+
|
|
112
|
+
def set_agent_context(self, to_wrap, wrapped, instance, args, kwargs):
|
|
113
|
+
"""Set the agent context for tracking across calls."""
|
|
114
|
+
try:
|
|
115
|
+
# For Runner.run, the agent is the first argument
|
|
116
|
+
if len(args) > 0:
|
|
117
|
+
agent = args[0]
|
|
118
|
+
agent_name = get_runner_agent_name(agent)
|
|
119
|
+
if agent_name:
|
|
120
|
+
self.agent_context_token = attach(
|
|
121
|
+
set_value(AGENTS_AGENT_NAME_KEY, agent_name)
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.warning("Warning: Error setting agent context: %s", str(e))
|
|
125
|
+
|
|
126
|
+
def clear_agent_context(self):
|
|
127
|
+
"""Clear the agent context."""
|
|
128
|
+
if self.agent_context_token:
|
|
129
|
+
detach(self.agent_context_token)
|
|
130
|
+
self.agent_context_token = None
|
|
131
|
+
|
|
132
|
+
def pre_task_processing(
|
|
133
|
+
self, to_wrap, wrapped, instance, args, kwargs, *args1, **kwargs1
|
|
134
|
+
):
|
|
135
|
+
"""Pre-processing for agent tasks."""
|
|
136
|
+
self.set_agent_context(to_wrap, wrapped, instance, args, kwargs)
|
|
137
|
+
context = set_value(AGENT_PREFIX_KEY, DELEGATION_NAME_PREFIX)
|
|
138
|
+
attach(context)
|
|
139
|
+
return super().pre_task_processing(
|
|
140
|
+
to_wrap, wrapped, instance, args, kwargs, *args1, **kwargs1
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def post_task_processing(
|
|
144
|
+
self, to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span
|
|
145
|
+
):
|
|
146
|
+
"""Post-processing for agent tasks."""
|
|
147
|
+
self.clear_agent_context()
|
|
148
|
+
context = set_value(AGENT_PREFIX_KEY, None)
|
|
149
|
+
attach(context)
|
|
150
|
+
return super().post_task_processing(
|
|
151
|
+
to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span
|
|
152
|
+
)
|
|
File without changes
|