monocle-apptrace 0.5.0b1__py3-none-any.whl → 0.5.1__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/exporters/file_exporter.py +2 -1
- monocle_apptrace/instrumentation/common/__init__.py +7 -5
- monocle_apptrace/instrumentation/common/constants.py +103 -12
- monocle_apptrace/instrumentation/common/instrumentor.py +1 -6
- monocle_apptrace/instrumentation/common/method_wrappers.py +10 -125
- monocle_apptrace/instrumentation/common/scope_wrapper.py +126 -0
- monocle_apptrace/instrumentation/common/span_handler.py +32 -8
- monocle_apptrace/instrumentation/common/utils.py +34 -3
- monocle_apptrace/instrumentation/common/wrapper.py +208 -41
- monocle_apptrace/instrumentation/common/wrapper_method.py +9 -1
- monocle_apptrace/instrumentation/metamodel/a2a/entities/inference.py +3 -1
- monocle_apptrace/instrumentation/metamodel/adk/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/adk/_helper.py +206 -0
- monocle_apptrace/instrumentation/metamodel/adk/entities/agent.py +111 -0
- monocle_apptrace/instrumentation/metamodel/adk/entities/tool.py +59 -0
- monocle_apptrace/instrumentation/metamodel/adk/methods.py +31 -0
- monocle_apptrace/instrumentation/metamodel/agents/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/agents/_helper.py +225 -0
- monocle_apptrace/instrumentation/metamodel/agents/agents_processor.py +174 -0
- monocle_apptrace/instrumentation/metamodel/agents/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/agents/entities/inference.py +196 -0
- monocle_apptrace/instrumentation/metamodel/agents/methods.py +55 -0
- monocle_apptrace/instrumentation/metamodel/aiohttp/entities/http.py +2 -1
- monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +82 -5
- monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +6 -1
- monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +2 -1
- monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +2 -1
- monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +2 -1
- monocle_apptrace/instrumentation/metamodel/fastapi/entities/http.py +2 -1
- monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +18 -18
- monocle_apptrace/instrumentation/metamodel/finish_types.py +79 -1
- monocle_apptrace/instrumentation/metamodel/flask/entities/http.py +2 -1
- monocle_apptrace/instrumentation/metamodel/gemini/entities/inference.py +7 -3
- monocle_apptrace/instrumentation/metamodel/gemini/entities/retrieval.py +2 -1
- monocle_apptrace/instrumentation/metamodel/gemini/methods.py +8 -1
- monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +64 -0
- monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +12 -1
- monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +2 -1
- monocle_apptrace/instrumentation/metamodel/lambdafunc/entities/http.py +2 -1
- monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +18 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +6 -1
- monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +2 -1
- monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +6 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +10 -5
- monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +11 -4
- monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +27 -23
- 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 +109 -0
- monocle_apptrace/instrumentation/metamodel/litellm/methods.py +19 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +9 -4
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +2 -1
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +2 -1
- monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +14 -3
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +1 -1
- monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +2 -1
- monocle_apptrace/instrumentation/metamodel/mcp/entities/inference.py +3 -1
- monocle_apptrace/instrumentation/metamodel/mcp/mcp_processor.py +0 -5
- monocle_apptrace/instrumentation/metamodel/mcp/methods.py +1 -1
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +110 -5
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +59 -13
- monocle_apptrace/instrumentation/metamodel/requests/entities/http.py +2 -1
- monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +12 -1
- monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +12 -1
- {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/METADATA +15 -10
- {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/RECORD +70 -53
- {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/WHEEL +0 -0
- {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.common.constants import SPAN_SUBTYPES, SPAN_TYPES
|
|
2
|
+
from monocle_apptrace.instrumentation.metamodel.adk import _helper
|
|
3
|
+
AGENT = {
|
|
4
|
+
"type": SPAN_TYPES.AGENTIC_INVOCATION,
|
|
5
|
+
"subtype": SPAN_SUBTYPES.ROUTING,
|
|
6
|
+
"attributes": [
|
|
7
|
+
[
|
|
8
|
+
{
|
|
9
|
+
"_comment": "agent type",
|
|
10
|
+
"attribute": "type",
|
|
11
|
+
"accessor": lambda arguments:'agent.adk'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"_comment": "name of the agent",
|
|
15
|
+
"attribute": "name",
|
|
16
|
+
"accessor": lambda arguments: _helper.get_agent_name(arguments['instance'])
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"_comment": "agent description",
|
|
20
|
+
"attribute": "description",
|
|
21
|
+
"accessor": lambda arguments: _helper.get_agent_description(arguments['instance'])
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"_comment": "delegating agent name",
|
|
25
|
+
"attribute": "from_agent",
|
|
26
|
+
"accessor": lambda arguments: _helper.get_delegating_agent(arguments)
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
],
|
|
30
|
+
"events": [
|
|
31
|
+
{
|
|
32
|
+
"name":"data.input",
|
|
33
|
+
"attributes": [
|
|
34
|
+
{
|
|
35
|
+
"_comment": "this is Agent input",
|
|
36
|
+
"attribute": "query",
|
|
37
|
+
"accessor": lambda arguments: _helper.extract_agent_input(arguments)
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name":"data.output",
|
|
43
|
+
"attributes": [
|
|
44
|
+
{
|
|
45
|
+
"_comment": "this is response from LLM",
|
|
46
|
+
"attribute": "response",
|
|
47
|
+
"accessor": lambda arguments: _helper.extract_agent_response(arguments['result'])
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
REQUEST = {
|
|
55
|
+
"type": "agentic.request",
|
|
56
|
+
"attributes": [
|
|
57
|
+
[
|
|
58
|
+
{
|
|
59
|
+
"_comment": "agent type",
|
|
60
|
+
"attribute": "type",
|
|
61
|
+
"accessor": lambda arguments:'agent.adk'
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
],
|
|
65
|
+
"events": [
|
|
66
|
+
{
|
|
67
|
+
"name":"data.input",
|
|
68
|
+
"attributes": [
|
|
69
|
+
{
|
|
70
|
+
"_comment": "this is Agent input",
|
|
71
|
+
"attribute": "input",
|
|
72
|
+
"accessor": lambda arguments: _helper.extract_agent_request_input(arguments)
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name":"data.output",
|
|
78
|
+
"attributes": [
|
|
79
|
+
{
|
|
80
|
+
"_comment": "this is response from LLM",
|
|
81
|
+
"attribute": "response",
|
|
82
|
+
"accessor": lambda arguments: _helper.extract_agent_response(arguments['result'])
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
DELEGATION = {
|
|
90
|
+
"type": "agentic.delegation",
|
|
91
|
+
"should_skip": lambda arguments: _helper.should_skip_delegation(arguments),
|
|
92
|
+
"attributes": [
|
|
93
|
+
[
|
|
94
|
+
{
|
|
95
|
+
"_comment": "agent type",
|
|
96
|
+
"attribute": "type",
|
|
97
|
+
"accessor": lambda arguments:'agent.adk'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"_comment": "name of the agent",
|
|
101
|
+
"attribute": "from_agent",
|
|
102
|
+
"accessor": lambda arguments: _helper.get_delegating_agent(arguments)
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"_comment": "name of the agent called",
|
|
106
|
+
"attribute": "to_agent",
|
|
107
|
+
"accessor": lambda arguments: _helper.get_agent_name(arguments['instance'])
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
]
|
|
111
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.common.constants import SPAN_SUBTYPES, SPAN_TYPES
|
|
2
|
+
from monocle_apptrace.instrumentation.metamodel.adk import _helper
|
|
3
|
+
TOOL = {
|
|
4
|
+
"type": SPAN_TYPES.AGENTIC_TOOL_INVOCATION,
|
|
5
|
+
"subtype": SPAN_SUBTYPES.ROUTING,
|
|
6
|
+
"attributes": [
|
|
7
|
+
[
|
|
8
|
+
{
|
|
9
|
+
"_comment": "tool type",
|
|
10
|
+
"attribute": "type",
|
|
11
|
+
"accessor": lambda arguments:'tool.adk'
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"_comment": "name of the tool",
|
|
15
|
+
"attribute": "name",
|
|
16
|
+
"accessor": lambda arguments: _helper.get_tool_name(arguments['instance'])
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"_comment": "tool description",
|
|
20
|
+
"attribute": "description",
|
|
21
|
+
"accessor": lambda arguments: _helper.get_tool_description(arguments['instance'])
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
{
|
|
26
|
+
"_comment": "name of the agent",
|
|
27
|
+
"attribute": "name",
|
|
28
|
+
"accessor": lambda arguments: _helper.get_source_agent(arguments)
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"_comment": "agent type",
|
|
32
|
+
"attribute": "type",
|
|
33
|
+
"accessor": lambda arguments:'agent.adk'
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
],
|
|
37
|
+
"events": [
|
|
38
|
+
{
|
|
39
|
+
"name":"data.input",
|
|
40
|
+
"attributes": [
|
|
41
|
+
{
|
|
42
|
+
"_comment": "this is Tool input",
|
|
43
|
+
"attribute": "Inputs",
|
|
44
|
+
"accessor": lambda arguments: _helper.extract_tool_input(arguments)
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name":"data.output",
|
|
50
|
+
"attributes": [
|
|
51
|
+
{
|
|
52
|
+
"_comment": "this is response from Tool",
|
|
53
|
+
"attribute": "response",
|
|
54
|
+
"accessor": lambda arguments: _helper.extract_tool_response(arguments['result'])
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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, REQUEST, DELEGATION
|
|
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_list": [DELEGATION, 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
|
+
{
|
|
25
|
+
"package": "google.adk.runners",
|
|
26
|
+
"object": "Runner",
|
|
27
|
+
"method": "run_async",
|
|
28
|
+
"wrapper_method": atask_iter_wrapper,
|
|
29
|
+
"output_processor": REQUEST,
|
|
30
|
+
}
|
|
31
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,225 @@
|
|
|
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
|
+
def get_tool_type(span):
|
|
162
|
+
if (span.attributes.get("is_mcp", False)):
|
|
163
|
+
return "tool.mcp"
|
|
164
|
+
else:
|
|
165
|
+
return "tool.openai_agents"
|
|
166
|
+
|
|
167
|
+
def get_tool_name(instance) -> str:
|
|
168
|
+
"""Get the name of a tool."""
|
|
169
|
+
return get_name(instance)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def is_root_agent_name(instance) -> bool:
|
|
173
|
+
"""Check if this is the root agent."""
|
|
174
|
+
return get_name(instance) == ROOT_AGENT_NAME
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_source_agent() -> str:
|
|
178
|
+
"""Get the name of the agent that initiated the request."""
|
|
179
|
+
from_agent = get_value(AGENTS_AGENT_NAME_KEY)
|
|
180
|
+
return from_agent if from_agent is not None else ""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def get_description(instance) -> str:
|
|
184
|
+
"""Get the description of an instance."""
|
|
185
|
+
if hasattr(instance, "description"):
|
|
186
|
+
return str(instance.description)
|
|
187
|
+
elif hasattr(instance, "handoff_description"):
|
|
188
|
+
return str(instance.handoff_description)
|
|
189
|
+
elif hasattr(instance, "__doc__") and instance.__doc__:
|
|
190
|
+
return str(instance.__doc__)
|
|
191
|
+
return ""
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_tool_description(instance) -> str:
|
|
195
|
+
"""Get the description of a tool."""
|
|
196
|
+
return get_description(instance)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def extract_handoff_target(arguments):
|
|
200
|
+
"""Extract the target agent from a handoff operation."""
|
|
201
|
+
try:
|
|
202
|
+
# Check if this is a handoff by looking at the result
|
|
203
|
+
return arguments.get("result").name
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logger.warning("Warning: Error occurred in extract_handoff_target: %s", str(e))
|
|
206
|
+
return ""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def update_span_from_agent_response(response):
|
|
210
|
+
"""Update span with metadata from agent response."""
|
|
211
|
+
meta_dict = {}
|
|
212
|
+
try:
|
|
213
|
+
if response is not None and hasattr(response, "usage"):
|
|
214
|
+
usage = response.usage
|
|
215
|
+
if hasattr(usage, "completion_tokens"):
|
|
216
|
+
meta_dict.update({"completion_tokens": usage.completion_tokens})
|
|
217
|
+
if hasattr(usage, "prompt_tokens"):
|
|
218
|
+
meta_dict.update({"prompt_tokens": usage.prompt_tokens})
|
|
219
|
+
if hasattr(usage, "total_tokens"):
|
|
220
|
+
meta_dict.update({"total_tokens": usage.total_tokens})
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.warning(
|
|
223
|
+
"Warning: Error occurred in update_span_from_agent_response: %s", str(e)
|
|
224
|
+
)
|
|
225
|
+
return meta_dict
|
|
@@ -0,0 +1,174 @@
|
|
|
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
|
+
mcp_url = None
|
|
38
|
+
# kwargs.get("on_invoke_tool").args[0].params["url"]
|
|
39
|
+
if (
|
|
40
|
+
kwargs.get("on_invoke_tool")
|
|
41
|
+
and hasattr(kwargs.get("on_invoke_tool"), "args")
|
|
42
|
+
and len(kwargs.get("on_invoke_tool").args) > 0
|
|
43
|
+
and hasattr(kwargs.get("on_invoke_tool").args[0], "params")
|
|
44
|
+
):
|
|
45
|
+
mcp_url = kwargs.get("on_invoke_tool").args[0].params.get("url", None)
|
|
46
|
+
if mcp_url:
|
|
47
|
+
logger.debug(f"Using MCP URL: {mcp_url}")
|
|
48
|
+
tool_instance = SimpleNamespace(
|
|
49
|
+
name=kwargs.get("name", "unknown_tool"),
|
|
50
|
+
description=kwargs.get("description", "No description provided"),
|
|
51
|
+
)
|
|
52
|
+
if original_func and not getattr(original_func, "_monocle_wrapped", False):
|
|
53
|
+
# Now wrap the function with our instrumentation
|
|
54
|
+
|
|
55
|
+
async def wrapped_func(*func_args, **func_kwargs):
|
|
56
|
+
token = None
|
|
57
|
+
try:
|
|
58
|
+
if mcp_url:
|
|
59
|
+
token = attach(set_value("mcp.url", mcp_url))
|
|
60
|
+
# Use the handler to create spans when the decorated function is called
|
|
61
|
+
return await atask_wrapper(
|
|
62
|
+
tracer=tracer, handler=handler, to_wrap=to_wrap
|
|
63
|
+
)(
|
|
64
|
+
wrapped=original_func,
|
|
65
|
+
instance=tool_instance,
|
|
66
|
+
source_path=source_path,
|
|
67
|
+
args=func_args,
|
|
68
|
+
kwargs=func_kwargs,
|
|
69
|
+
)
|
|
70
|
+
finally:
|
|
71
|
+
if token:
|
|
72
|
+
detach(token)
|
|
73
|
+
|
|
74
|
+
kwargs["on_invoke_tool"] = wrapped_func
|
|
75
|
+
# Preserve function metadata
|
|
76
|
+
wrapped_func.__name__ = getattr(wrapped, "__name__", "unknown_tool")
|
|
77
|
+
wrapped_func.__doc__ = getattr(wrapped, "__doc__", "")
|
|
78
|
+
# mark function as wrapped
|
|
79
|
+
setattr(wrapped_func, "_monocle_wrapped", True)
|
|
80
|
+
|
|
81
|
+
result = wrapped(*args, **kwargs)
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@with_tracer_wrapper
|
|
86
|
+
def handoff_constructor_wrapper(
|
|
87
|
+
tracer: Tracer,
|
|
88
|
+
handler: BaseSpanHandler,
|
|
89
|
+
to_wrap,
|
|
90
|
+
wrapped,
|
|
91
|
+
instance,
|
|
92
|
+
source_path,
|
|
93
|
+
args,
|
|
94
|
+
kwargs,
|
|
95
|
+
):
|
|
96
|
+
|
|
97
|
+
original_func = kwargs.get("on_invoke_handoff", None)
|
|
98
|
+
result = None
|
|
99
|
+
tool_instance = SimpleNamespace(
|
|
100
|
+
name=kwargs.get("name", "unknown_handoff"),
|
|
101
|
+
description=kwargs.get("description", "No description provided"),
|
|
102
|
+
)
|
|
103
|
+
if original_func and not getattr(original_func, "_monocle_wrapped", False):
|
|
104
|
+
# Now wrap the function with our instrumentation
|
|
105
|
+
async def wrapped_func(*func_args, **func_kwargs):
|
|
106
|
+
# Use the handler to create spans when the decorated function is called
|
|
107
|
+
return await atask_wrapper(tracer=tracer, handler=handler, to_wrap=to_wrap)(
|
|
108
|
+
wrapped=original_func,
|
|
109
|
+
instance=tool_instance,
|
|
110
|
+
source_path=source_path,
|
|
111
|
+
args=func_args,
|
|
112
|
+
kwargs=func_kwargs,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
kwargs["on_invoke_handoff"] = wrapped_func
|
|
116
|
+
# Preserve function metadata
|
|
117
|
+
wrapped_func.__name__ = getattr(wrapped, "__name__", "unknown_handoff")
|
|
118
|
+
wrapped_func.__doc__ = getattr(wrapped, "__doc__", "")
|
|
119
|
+
# mark function as wrapped
|
|
120
|
+
setattr(wrapped_func, "_monocle_wrapped", True)
|
|
121
|
+
|
|
122
|
+
result = wrapped(*args, **kwargs)
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class AgentsSpanHandler(BaseSpanHandler):
|
|
127
|
+
"""Span handler for OpenAI Agents SDK."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, *args, **kwargs):
|
|
130
|
+
super().__init__(*args, **kwargs)
|
|
131
|
+
self.agent_context_token = None
|
|
132
|
+
|
|
133
|
+
def set_agent_context(self, to_wrap, wrapped, instance, args, kwargs):
|
|
134
|
+
"""Set the agent context for tracking across calls."""
|
|
135
|
+
try:
|
|
136
|
+
# For Runner.run, the agent is the first argument
|
|
137
|
+
if len(args) > 0:
|
|
138
|
+
agent = args[0]
|
|
139
|
+
agent_name = get_runner_agent_name(agent)
|
|
140
|
+
if agent_name:
|
|
141
|
+
self.agent_context_token = attach(
|
|
142
|
+
set_value(AGENTS_AGENT_NAME_KEY, agent_name)
|
|
143
|
+
)
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.warning("Warning: Error setting agent context: %s", str(e))
|
|
146
|
+
|
|
147
|
+
def clear_agent_context(self):
|
|
148
|
+
"""Clear the agent context."""
|
|
149
|
+
if self.agent_context_token:
|
|
150
|
+
detach(self.agent_context_token)
|
|
151
|
+
self.agent_context_token = None
|
|
152
|
+
|
|
153
|
+
def pre_task_processing(
|
|
154
|
+
self, to_wrap, wrapped, instance, args, kwargs, *args1, **kwargs1
|
|
155
|
+
):
|
|
156
|
+
"""Pre-processing for agent tasks."""
|
|
157
|
+
self.set_agent_context(to_wrap, wrapped, instance, args, kwargs)
|
|
158
|
+
context = set_value(AGENT_PREFIX_KEY, DELEGATION_NAME_PREFIX)
|
|
159
|
+
attach(context)
|
|
160
|
+
return super().pre_task_processing(
|
|
161
|
+
to_wrap, wrapped, instance, args, kwargs, *args1, **kwargs1
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def post_task_processing(
|
|
165
|
+
self, to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span
|
|
166
|
+
):
|
|
167
|
+
"""Post-processing for agent tasks."""
|
|
168
|
+
self.clear_agent_context()
|
|
169
|
+
context = set_value(AGENT_PREFIX_KEY, None)
|
|
170
|
+
attach(context)
|
|
171
|
+
return super().post_task_processing(
|
|
172
|
+
to_wrap, wrapped, instance, args, kwargs, result, ex, span, parent_span
|
|
173
|
+
)
|
|
174
|
+
|
|
File without changes
|