monocle-apptrace 0.4.2__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.

Files changed (88) hide show
  1. monocle_apptrace/__main__.py +1 -1
  2. monocle_apptrace/exporters/file_exporter.py +125 -37
  3. monocle_apptrace/instrumentation/common/__init__.py +16 -1
  4. monocle_apptrace/instrumentation/common/constants.py +14 -1
  5. monocle_apptrace/instrumentation/common/instrumentor.py +19 -152
  6. monocle_apptrace/instrumentation/common/method_wrappers.py +376 -0
  7. monocle_apptrace/instrumentation/common/span_handler.py +58 -32
  8. monocle_apptrace/instrumentation/common/utils.py +52 -15
  9. monocle_apptrace/instrumentation/common/wrapper.py +124 -18
  10. monocle_apptrace/instrumentation/common/wrapper_method.py +47 -1
  11. monocle_apptrace/instrumentation/metamodel/a2a/__init__.py +0 -0
  12. monocle_apptrace/instrumentation/metamodel/a2a/_helper.py +37 -0
  13. monocle_apptrace/instrumentation/metamodel/a2a/entities/__init__.py +0 -0
  14. monocle_apptrace/instrumentation/metamodel/a2a/entities/inference.py +112 -0
  15. monocle_apptrace/instrumentation/metamodel/a2a/methods.py +22 -0
  16. monocle_apptrace/instrumentation/metamodel/adk/__init__.py +0 -0
  17. monocle_apptrace/instrumentation/metamodel/adk/_helper.py +182 -0
  18. monocle_apptrace/instrumentation/metamodel/adk/entities/agent.py +50 -0
  19. monocle_apptrace/instrumentation/metamodel/adk/entities/tool.py +57 -0
  20. monocle_apptrace/instrumentation/metamodel/adk/methods.py +24 -0
  21. monocle_apptrace/instrumentation/metamodel/agents/__init__.py +0 -0
  22. monocle_apptrace/instrumentation/metamodel/agents/_helper.py +220 -0
  23. monocle_apptrace/instrumentation/metamodel/agents/agents_processor.py +152 -0
  24. monocle_apptrace/instrumentation/metamodel/agents/entities/__init__.py +0 -0
  25. monocle_apptrace/instrumentation/metamodel/agents/entities/inference.py +191 -0
  26. monocle_apptrace/instrumentation/metamodel/agents/methods.py +56 -0
  27. monocle_apptrace/instrumentation/metamodel/aiohttp/_helper.py +6 -11
  28. monocle_apptrace/instrumentation/metamodel/anthropic/_helper.py +112 -18
  29. monocle_apptrace/instrumentation/metamodel/anthropic/entities/inference.py +18 -10
  30. monocle_apptrace/instrumentation/metamodel/azfunc/_helper.py +13 -11
  31. monocle_apptrace/instrumentation/metamodel/azfunc/entities/http.py +5 -0
  32. monocle_apptrace/instrumentation/metamodel/azureaiinference/_helper.py +88 -8
  33. monocle_apptrace/instrumentation/metamodel/azureaiinference/entities/inference.py +22 -8
  34. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +92 -16
  35. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +13 -8
  36. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +1 -1
  37. monocle_apptrace/instrumentation/metamodel/fastapi/__init__.py +0 -0
  38. monocle_apptrace/instrumentation/metamodel/fastapi/_helper.py +82 -0
  39. monocle_apptrace/instrumentation/metamodel/fastapi/entities/__init__.py +0 -0
  40. monocle_apptrace/instrumentation/metamodel/fastapi/entities/http.py +44 -0
  41. monocle_apptrace/instrumentation/metamodel/fastapi/methods.py +23 -0
  42. monocle_apptrace/instrumentation/metamodel/finish_types.py +463 -0
  43. monocle_apptrace/instrumentation/metamodel/flask/_helper.py +6 -11
  44. monocle_apptrace/instrumentation/metamodel/gemini/_helper.py +51 -7
  45. monocle_apptrace/instrumentation/metamodel/gemini/entities/inference.py +22 -11
  46. monocle_apptrace/instrumentation/metamodel/gemini/entities/retrieval.py +43 -0
  47. monocle_apptrace/instrumentation/metamodel/gemini/methods.py +18 -1
  48. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +79 -8
  49. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +15 -10
  50. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +7 -0
  51. monocle_apptrace/instrumentation/metamodel/lambdafunc/_helper.py +78 -0
  52. monocle_apptrace/instrumentation/metamodel/lambdafunc/entities/http.py +51 -0
  53. monocle_apptrace/instrumentation/metamodel/lambdafunc/methods.py +23 -0
  54. monocle_apptrace/instrumentation/metamodel/lambdafunc/wrapper.py +23 -0
  55. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +145 -19
  56. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +19 -10
  57. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +67 -10
  58. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +127 -20
  59. monocle_apptrace/instrumentation/metamodel/langgraph/langgraph_processor.py +46 -0
  60. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +35 -9
  61. monocle_apptrace/instrumentation/metamodel/litellm/__init__.py +0 -0
  62. monocle_apptrace/instrumentation/metamodel/litellm/_helper.py +89 -0
  63. monocle_apptrace/instrumentation/metamodel/litellm/entities/__init__.py +0 -0
  64. monocle_apptrace/instrumentation/metamodel/litellm/entities/inference.py +108 -0
  65. monocle_apptrace/instrumentation/metamodel/litellm/methods.py +19 -0
  66. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +227 -16
  67. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +127 -10
  68. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +13 -8
  69. monocle_apptrace/instrumentation/metamodel/llamaindex/llamaindex_processor.py +62 -0
  70. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +68 -1
  71. monocle_apptrace/instrumentation/metamodel/mcp/__init__.py +0 -0
  72. monocle_apptrace/instrumentation/metamodel/mcp/_helper.py +118 -0
  73. monocle_apptrace/instrumentation/metamodel/mcp/entities/__init__.py +0 -0
  74. monocle_apptrace/instrumentation/metamodel/mcp/entities/inference.py +48 -0
  75. monocle_apptrace/instrumentation/metamodel/mcp/mcp_processor.py +8 -0
  76. monocle_apptrace/instrumentation/metamodel/mcp/methods.py +21 -0
  77. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +188 -16
  78. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +148 -92
  79. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +1 -1
  80. monocle_apptrace/instrumentation/metamodel/teamsai/_helper.py +53 -23
  81. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/actionplanner_output_processor.py +1 -1
  82. monocle_apptrace/instrumentation/metamodel/teamsai/entities/inference/teamsai_output_processor.py +15 -9
  83. monocle_apptrace/instrumentation/metamodel/teamsai/sample.json +0 -4
  84. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/METADATA +27 -11
  85. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/RECORD +88 -47
  86. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/WHEEL +0 -0
  87. {monocle_apptrace-0.4.2.dist-info → monocle_apptrace-0.5.0.dist-info}/licenses/LICENSE +0 -0
  88. {monocle_apptrace-0.4.2.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
+ ]
@@ -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
+ )