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.

Files changed (70) hide show
  1. monocle_apptrace/exporters/file_exporter.py +2 -1
  2. monocle_apptrace/instrumentation/common/__init__.py +7 -5
  3. monocle_apptrace/instrumentation/common/constants.py +103 -12
  4. monocle_apptrace/instrumentation/common/instrumentor.py +1 -6
  5. monocle_apptrace/instrumentation/common/method_wrappers.py +10 -125
  6. monocle_apptrace/instrumentation/common/scope_wrapper.py +126 -0
  7. monocle_apptrace/instrumentation/common/span_handler.py +32 -8
  8. monocle_apptrace/instrumentation/common/utils.py +34 -3
  9. monocle_apptrace/instrumentation/common/wrapper.py +208 -41
  10. monocle_apptrace/instrumentation/common/wrapper_method.py +9 -1
  11. monocle_apptrace/instrumentation/metamodel/a2a/entities/inference.py +3 -1
  12. monocle_apptrace/instrumentation/metamodel/adk/__init__.py +0 -0
  13. monocle_apptrace/instrumentation/metamodel/adk/_helper.py +206 -0
  14. monocle_apptrace/instrumentation/metamodel/adk/entities/agent.py +111 -0
  15. monocle_apptrace/instrumentation/metamodel/adk/entities/tool.py +59 -0
  16. monocle_apptrace/instrumentation/metamodel/adk/methods.py +31 -0
  17. monocle_apptrace/instrumentation/metamodel/agents/__init__.py +0 -0
  18. monocle_apptrace/instrumentation/metamodel/agents/_helper.py +225 -0
  19. monocle_apptrace/instrumentation/metamodel/agents/agents_processor.py +174 -0
  20. monocle_apptrace/instrumentation/metamodel/agents/entities/__init__.py +0 -0
  21. monocle_apptrace/instrumentation/metamodel/agents/entities/inference.py +196 -0
  22. monocle_apptrace/instrumentation/metamodel/agents/methods.py +55 -0
  23. monocle_apptrace/instrumentation/metamodel/aiohttp/entities/http.py +2 -1
  24. monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +82 -5
  25. monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +6 -1
  26. monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +2 -1
  27. monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +2 -1
  28. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +2 -1
  29. monocle_apptrace/instrumentation/metamodel/fastapi/entities/http.py +2 -1
  30. monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +18 -18
  31. monocle_apptrace/instrumentation/metamodel/finish_types.py +79 -1
  32. monocle_apptrace/instrumentation/metamodel/flask/entities/http.py +2 -1
  33. monocle_apptrace/instrumentation/metamodel/gemini/entities/inference.py +7 -3
  34. monocle_apptrace/instrumentation/metamodel/gemini/entities/retrieval.py +2 -1
  35. monocle_apptrace/instrumentation/metamodel/gemini/methods.py +8 -1
  36. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +64 -0
  37. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +12 -1
  38. monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +2 -1
  39. monocle_apptrace/instrumentation/metamodel/lambdafunc/entities/http.py +2 -1
  40. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +18 -0
  41. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +6 -1
  42. monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +2 -1
  43. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +6 -0
  44. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +10 -5
  45. monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +11 -4
  46. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +27 -23
  47. monocle_apptrace/instrumentation/metamodel/litellm/__init__.py +0 -0
  48. monocle_apptrace/instrumentation/metamodel/litellm/_helper.py +89 -0
  49. monocle_apptrace/instrumentation/metamodel/litellm/entities/__init__.py +0 -0
  50. monocle_apptrace/instrumentation/metamodel/litellm/entities/inference.py +109 -0
  51. monocle_apptrace/instrumentation/metamodel/litellm/methods.py +19 -0
  52. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +9 -4
  53. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +2 -1
  54. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +2 -1
  55. monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +14 -3
  56. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +1 -1
  57. monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +2 -1
  58. monocle_apptrace/instrumentation/metamodel/mcp/entities/inference.py +3 -1
  59. monocle_apptrace/instrumentation/metamodel/mcp/mcp_processor.py +0 -5
  60. monocle_apptrace/instrumentation/metamodel/mcp/methods.py +1 -1
  61. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +110 -5
  62. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +59 -13
  63. monocle_apptrace/instrumentation/metamodel/requests/entities/http.py +2 -1
  64. monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +12 -1
  65. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +12 -1
  66. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/METADATA +15 -10
  67. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/RECORD +70 -53
  68. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/WHEEL +0 -0
  69. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1.dist-info}/licenses/LICENSE +0 -0
  70. {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
+ ]
@@ -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
+