monocle-apptrace 0.5.0b1__py3-none-any.whl → 0.5.1b1__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 (72) 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/mcp_server.py +94 -0
  67. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1b1.dist-info}/METADATA +41 -11
  68. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1b1.dist-info}/RECORD +72 -53
  69. monocle_apptrace-0.5.1b1.dist-info/entry_points.txt +2 -0
  70. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1b1.dist-info}/WHEEL +0 -0
  71. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1b1.dist-info}/licenses/LICENSE +0 -0
  72. {monocle_apptrace-0.5.0b1.dist-info → monocle_apptrace-0.5.1b1.dist-info}/licenses/NOTICE +0 -0
@@ -1,7 +1,16 @@
1
1
  # pylint: disable=protected-access
2
+ from contextlib import contextmanager
3
+ import os
4
+ from typing import AsyncGenerator, Iterator, Optional
2
5
  import logging
3
6
  from opentelemetry.trace import Tracer
7
+ from opentelemetry.trace.propagation import _SPAN_KEY, set_span_in_context, get_current_span
8
+ from opentelemetry.trace import propagation
4
9
  from opentelemetry.context import set_value, attach, detach, get_value
10
+ from opentelemetry.context import create_key, get_value, set_value
11
+ from opentelemetry.context.context import Context
12
+ from opentelemetry.trace.span import INVALID_SPAN, Span
13
+ from opentelemetry.trace.status import StatusCode
5
14
 
6
15
  from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
7
16
  from monocle_apptrace.instrumentation.common.utils import (
@@ -9,10 +18,14 @@ from monocle_apptrace.instrumentation.common.utils import (
9
18
  with_tracer_wrapper,
10
19
  set_scope,
11
20
  remove_scope,
12
- get_parent_span
21
+ get_current_monocle_span,
22
+ set_monocle_span_in_context
13
23
  )
14
- from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY, ADD_NEW_WORKFLOW
24
+ from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY, ADD_NEW_WORKFLOW, AGENTIC_SPANS
25
+ from monocle_apptrace.instrumentation.common.scope_wrapper import monocle_trace_scope
26
+
15
27
  logger = logging.getLogger(__name__)
28
+ ISOLATE_MONOCLE_SPANS = os.getenv("MONOCLE_ISOLATE_SPANS", "true").lower() == "true"
16
29
 
17
30
  def get_auto_close_span(to_wrap, kwargs):
18
31
  try:
@@ -38,6 +51,8 @@ def pre_process_span(name, tracer, handler, add_workflow_span, to_wrap, wrapped,
38
51
  def post_process_span(handler, to_wrap, wrapped, instance, args, kwargs, return_value, span, parent_span, ex):
39
52
  if not (SpanHandler.is_root_span(span) or get_value(ADD_NEW_WORKFLOW) == True):
40
53
  try:
54
+ if parent_span == INVALID_SPAN:
55
+ parent_span = None
41
56
  handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span, parent_span, ex)
42
57
  except Exception as e:
43
58
  logger.info(f"Warning: Error occurred in hydrate_span: {e}")
@@ -60,33 +75,43 @@ def monocle_wrapper_span_processor(tracer: Tracer, handler: SpanHandler, to_wrap
60
75
  return_value = None
61
76
  span_status = None
62
77
  auto_close_span = get_auto_close_span(to_wrap, kwargs)
63
- parent_span = get_parent_span()
64
- with tracer.start_as_current_span(name, end_on_exit=auto_close_span) as span:
78
+ parent_span = get_current_monocle_span()
79
+ with start_as_monocle_span(tracer, name, auto_close_span) as span:
65
80
  pre_process_span(name, tracer, handler, add_workflow_span, to_wrap, wrapped, instance, args, kwargs, span, source_path)
66
81
 
67
82
  if SpanHandler.is_root_span(span) or add_workflow_span:
68
83
  # Recursive call for the actual span
69
84
  return_value, span_status = monocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, False, args, kwargs)
70
- span.set_status(span_status)
85
+ span.set_status(StatusCode.OK)
71
86
  if not auto_close_span:
72
87
  span.end()
73
88
  else:
74
89
  ex:Exception = None
75
- try:
76
- with SpanHandler.workflow_type(to_wrap, span):
77
- return_value = wrapped(*args, **kwargs)
78
- except Exception as e:
79
- ex = e
80
- raise
81
- finally:
82
- def post_process_span_internal(ret_val):
83
- post_process_span(handler, to_wrap, wrapped, instance, args, kwargs, ret_val, span, parent_span ,ex)
84
- if not auto_close_span:
85
- span.end()
86
- if ex is None and not auto_close_span and to_wrap.get("output_processor") and to_wrap.get("output_processor").get("response_processor"):
87
- to_wrap.get("output_processor").get("response_processor")(to_wrap, return_value, post_process_span_internal)
88
- else:
90
+ to_wrap = get_wrapper_with_next_processor(to_wrap, handler, instance, args, kwargs)
91
+ if has_more_processors(to_wrap):
92
+ try:
93
+ return_value, span_status = monocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, False, args, kwargs)
94
+ except Exception as e:
95
+ ex = e
96
+ raise
97
+ finally:
89
98
  post_process_span_internal(return_value)
99
+ else:
100
+ try:
101
+ with SpanHandler.workflow_type(to_wrap, span):
102
+ return_value = wrapped(*args, **kwargs)
103
+ except Exception as e:
104
+ ex = e
105
+ raise
106
+ finally:
107
+ def post_process_span_internal(ret_val):
108
+ post_process_span(handler, to_wrap, wrapped, instance, args, kwargs, ret_val, span, parent_span ,ex)
109
+ if not auto_close_span:
110
+ span.end()
111
+ if ex is None and not auto_close_span and to_wrap.get("output_processor") and to_wrap.get("output_processor").get("response_processor"):
112
+ to_wrap.get("output_processor").get("response_processor")(to_wrap, return_value, post_process_span_internal)
113
+ else:
114
+ post_process_span_internal(return_value)
90
115
  span_status = span.status
91
116
  return return_value, span_status
92
117
 
@@ -105,7 +130,8 @@ def monocle_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, inst
105
130
  add_workflow_span = get_value(ADD_NEW_WORKFLOW) == True
106
131
  token = attach(set_value(ADD_NEW_WORKFLOW, False))
107
132
  try:
108
- return_value, span_status = monocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, add_workflow_span, args, kwargs)
133
+ with monocle_trace_scope(get_builtin_scope_names(to_wrap)):
134
+ return_value, span_status = monocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, add_workflow_span, args, kwargs)
109
135
  finally:
110
136
  detach(token)
111
137
  return return_value
@@ -115,41 +141,103 @@ def monocle_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, inst
115
141
  except Exception as e:
116
142
  logger.info(f"Warning: Error occurred in post_tracing: {e}")
117
143
 
118
- async def amonocle_wrapper_span_processor(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, add_workflow_span, args, kwargs):
144
+ async def amonocle_wrapper_span_processor(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, add_workflow_span,
145
+ args, kwargs):
119
146
  # Main span processing logic
120
147
  name = get_span_name(to_wrap, instance)
121
148
  return_value = None
122
149
  span_status = None
123
150
  auto_close_span = get_auto_close_span(to_wrap, kwargs)
124
- parent_span = get_parent_span()
125
- with tracer.start_as_current_span(name, end_on_exit=auto_close_span) as span:
151
+ parent_span = get_current_monocle_span()
152
+ with start_as_monocle_span(tracer, name, auto_close_span) as span:
126
153
  pre_process_span(name, tracer, handler, add_workflow_span, to_wrap, wrapped, instance, args, kwargs, span, source_path)
127
154
 
128
155
  if SpanHandler.is_root_span(span) or add_workflow_span:
129
156
  # Recursive call for the actual span
130
157
  return_value, span_status = await amonocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, False, args, kwargs)
131
- span.set_status(span_status)
158
+ span.set_status(StatusCode.OK)
132
159
  if not auto_close_span:
133
160
  span.end()
134
161
  else:
135
162
  ex:Exception = None
136
- try:
137
- with SpanHandler.workflow_type(to_wrap, span):
138
- return_value = await wrapped(*args, **kwargs)
139
- except Exception as e:
140
- ex = e
141
- raise
142
- finally:
143
- def post_process_span_internal(ret_val):
144
- post_process_span(handler, to_wrap, wrapped, instance, args, kwargs, ret_val, span, parent_span, ex)
145
- if not auto_close_span:
146
- span.end()
147
- if ex is None and not auto_close_span and to_wrap.get("output_processor") and to_wrap.get("output_processor").get("response_processor"):
148
- to_wrap.get("output_processor").get("response_processor")(to_wrap, return_value, post_process_span_internal)
149
- else:
163
+ to_wrap = get_wrapper_with_next_processor(to_wrap, handler, instance, args, kwargs)
164
+ if has_more_processors(to_wrap):
165
+ try:
166
+ return_value, span_status = await amonocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, False, args, kwargs)
167
+ except Exception as e:
168
+ ex = e
169
+ raise
170
+ finally:
150
171
  post_process_span_internal(return_value)
172
+ else:
173
+ try:
174
+ with SpanHandler.workflow_type(to_wrap, span):
175
+ return_value = await wrapped(*args, **kwargs)
176
+ except Exception as e:
177
+ ex = e
178
+ raise
179
+ finally:
180
+ def post_process_span_internal(ret_val):
181
+ post_process_span(handler, to_wrap, wrapped, instance, args, kwargs, ret_val, span, parent_span, ex)
182
+ if not auto_close_span:
183
+ span.end()
184
+ if ex is None and not auto_close_span and to_wrap.get("output_processor") and to_wrap.get("output_processor").get("response_processor"):
185
+ to_wrap.get("output_processor").get("response_processor")(to_wrap, return_value, post_process_span_internal)
186
+ else:
187
+ post_process_span_internal(return_value)
151
188
  span_status = span.status
152
- return return_value, span.status
189
+ return return_value, span_status
190
+
191
+ async def amonocle_iter_wrapper_span_processor(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, add_workflow_span,
192
+ args, kwargs) -> AsyncGenerator[any, None]:
193
+ # Main span processing logic
194
+ name = get_span_name(to_wrap, instance)
195
+ auto_close_span = get_auto_close_span(to_wrap, kwargs)
196
+ parent_span = get_current_monocle_span()
197
+ last_item = None
198
+
199
+ with start_as_monocle_span(tracer, name, auto_close_span) as span:
200
+ pre_process_span(name, tracer, handler, add_workflow_span, to_wrap, wrapped, instance, args, kwargs, span, source_path)
201
+
202
+ if SpanHandler.is_root_span(span) or add_workflow_span:
203
+ # Recursive call for the actual span
204
+ async for item in amonocle_iter_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, False, args, kwargs):
205
+ yield item
206
+ span.set_status(StatusCode.OK)
207
+ if not auto_close_span:
208
+ span.end()
209
+ else:
210
+ ex:Exception = None
211
+ to_wrap = get_wrapper_with_next_processor(to_wrap, handler, instance, args, kwargs)
212
+ if has_more_processors(to_wrap):
213
+ try:
214
+ async for item in amonocle_iter_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, False, args, kwargs):
215
+ last_item = item
216
+ yield item
217
+ except Exception as e:
218
+ ex = e
219
+ raise
220
+ finally:
221
+ post_process_span(handler, to_wrap, wrapped, instance, args, kwargs, last_item, span, parent_span, ex)
222
+ else:
223
+ try:
224
+ with SpanHandler.workflow_type(to_wrap, span):
225
+ async for item in wrapped(*args, **kwargs):
226
+ last_item = item
227
+ yield item
228
+ except Exception as e:
229
+ ex = e
230
+ raise
231
+ finally:
232
+ def post_process_span_internal(ret_val):
233
+ post_process_span(handler, to_wrap, wrapped, instance, args, kwargs, ret_val, span, parent_span, ex)
234
+ if not auto_close_span:
235
+ span.end()
236
+ if ex is None and not auto_close_span and to_wrap.get("output_processor") and to_wrap.get("output_processor").get("response_processor"):
237
+ to_wrap.get("output_processor").get("response_processor")(to_wrap, None, post_process_span_internal)
238
+ else:
239
+ post_process_span_internal(last_item)
240
+ return
153
241
 
154
242
  async def amonocle_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, args, kwargs):
155
243
  return_value = None
@@ -166,7 +254,9 @@ async def amonocle_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrappe
166
254
  add_workflow_span = get_value(ADD_NEW_WORKFLOW) == True
167
255
  token = attach(set_value(ADD_NEW_WORKFLOW, False))
168
256
  try:
169
- return_value, span_status = await amonocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, add_workflow_span, args, kwargs)
257
+ with monocle_trace_scope(get_builtin_scope_names(to_wrap)):
258
+ return_value, span_status = await amonocle_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path,
259
+ add_workflow_span, args, kwargs)
170
260
  finally:
171
261
  detach(token)
172
262
  return return_value
@@ -176,6 +266,33 @@ async def amonocle_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrappe
176
266
  except Exception as e:
177
267
  logger.info(f"Warning: Error occurred in post_tracing: {e}")
178
268
 
269
+ async def amonocle_iter_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, args, kwargs) -> AsyncGenerator[any, None]:
270
+ token = None
271
+ pre_trace_token = None
272
+ try:
273
+ try:
274
+ pre_trace_token = handler.pre_tracing(to_wrap, wrapped, instance, args, kwargs)
275
+ except Exception as e:
276
+ logger.info(f"Warning: Error occurred in pre_tracing: {e}")
277
+ if to_wrap.get('skip_span', False) or handler.skip_span(to_wrap, wrapped, instance, args, kwargs):
278
+ async for item in wrapped(*args, **kwargs):
279
+ yield item
280
+ else:
281
+ add_workflow_span = get_value(ADD_NEW_WORKFLOW) == True
282
+ token = attach(set_value(ADD_NEW_WORKFLOW, False))
283
+ try:
284
+ with monocle_trace_scope(get_builtin_scope_names(to_wrap)):
285
+ async for item in amonocle_iter_wrapper_span_processor(tracer, handler, to_wrap, wrapped, instance, source_path, add_workflow_span, args, kwargs):
286
+ yield item
287
+ finally:
288
+ detach(token)
289
+ return
290
+ finally:
291
+ try:
292
+ handler.post_tracing(to_wrap, wrapped, instance, args, kwargs, None, pre_trace_token)
293
+ except Exception as e:
294
+ logger.info(f"Warning: Error occurred in post_tracing: {e}")
295
+
179
296
  @with_tracer_wrapper
180
297
  def task_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, args, kwargs):
181
298
  return monocle_wrapper(tracer, handler, to_wrap, wrapped, instance, source_path, args, kwargs)
@@ -184,6 +301,12 @@ def task_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instanc
184
301
  async def atask_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, args, kwargs):
185
302
  return await amonocle_wrapper(tracer, handler, to_wrap, wrapped, instance, source_path, args, kwargs)
186
303
 
304
+ @with_tracer_wrapper
305
+ async def atask_iter_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, args, kwargs) -> AsyncGenerator[any, None]:
306
+ async for item in amonocle_iter_wrapper(tracer, handler, to_wrap, wrapped, instance, source_path, args, kwargs):
307
+ yield item
308
+ return
309
+
187
310
  @with_tracer_wrapper
188
311
  def scope_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, source_path, args, kwargs):
189
312
  scope_name = to_wrap.get('scope_name', None)
@@ -245,4 +368,48 @@ def evaluate_scope_values(args, kwargs, to_wrap, scope_values):
245
368
  scope_values = None
246
369
  if isinstance(scope_values, dict):
247
370
  return scope_values
248
- return None
371
+ return None
372
+
373
+ @contextmanager
374
+ def start_as_monocle_span(tracer: Tracer, name: str, auto_close_span: bool) -> Iterator["Span"]:
375
+ """ Wrapper to OTEL start_as_current_span to isolate monocle and non monocle spans.
376
+ This essentiall links monocle and non-monocle spans separately which is default behavior.
377
+ It can be optionally overridden by setting the environment variable MONOCLE_ISOLATE_SPANS to false.
378
+ """
379
+ if not ISOLATE_MONOCLE_SPANS:
380
+ # If not isolating, use the default start_as_current_span
381
+ yield tracer.start_as_current_span(name, end_on_exit=auto_close_span)
382
+ return
383
+ original_span = get_current_span()
384
+ monocle_span_token = attach(set_span_in_context(get_current_monocle_span()))
385
+ with tracer.start_as_current_span(name, end_on_exit=auto_close_span) as span:
386
+ new_monocle_token = attach(set_monocle_span_in_context(span))
387
+ original_span_token = attach(set_span_in_context(original_span))
388
+ yield span
389
+ detach(original_span_token)
390
+ detach(new_monocle_token)
391
+ detach(monocle_span_token)
392
+
393
+ def get_builtin_scope_names(to_wrap) -> str:
394
+ output_processor = to_wrap.get("output_processor", None)
395
+ span_type = output_processor.get("type", None) if output_processor else None
396
+ if span_type and span_type in AGENTIC_SPANS:
397
+ return span_type
398
+ return None
399
+
400
+ def get_wrapper_with_next_processor(to_wrap, handler, instance, args, kwargs):
401
+ if has_more_processors(to_wrap):
402
+ next_output_processor_list = to_wrap.get('output_processor_list',[]).copy()
403
+ while len(next_output_processor_list) > 0:
404
+ next_output_processor = next_output_processor_list.pop(0)
405
+ if handler.should_skip(next_output_processor, instance, args, kwargs):
406
+ next_output_processor = None
407
+ else:
408
+ break
409
+ to_wrap = to_wrap.copy()
410
+ to_wrap['output_processor_list'] = next_output_processor_list
411
+ to_wrap['output_processor'] = next_output_processor
412
+ return to_wrap
413
+
414
+ def has_more_processors(to_wrap) -> bool:
415
+ return len(to_wrap.get('output_processor_list', [])) > 0
@@ -15,6 +15,8 @@ from monocle_apptrace.instrumentation.metamodel.openai.methods import (OPENAI_ME
15
15
  from monocle_apptrace.instrumentation.metamodel.openai._helper import OpenAISpanHandler
16
16
  from monocle_apptrace.instrumentation.metamodel.langgraph.methods import LANGGRAPH_METHODS
17
17
  from monocle_apptrace.instrumentation.metamodel.langgraph.langgraph_processor import LanggraphAgentHandler, LanggraphToolHandler
18
+ from monocle_apptrace.instrumentation.metamodel.agents.methods import AGENTS_METHODS
19
+ from monocle_apptrace.instrumentation.metamodel.agents.agents_processor import AgentsSpanHandler
18
20
  from monocle_apptrace.instrumentation.metamodel.flask.methods import (FLASK_METHODS, )
19
21
  from monocle_apptrace.instrumentation.metamodel.flask._helper import FlaskSpanHandler, FlaskResponseSpanHandler
20
22
  from monocle_apptrace.instrumentation.metamodel.requests.methods import (REQUESTS_METHODS, )
@@ -33,6 +35,8 @@ from monocle_apptrace.instrumentation.metamodel.lambdafunc.methods import LAMBDA
33
35
  from monocle_apptrace.instrumentation.metamodel.mcp.methods import MCP_METHODS
34
36
  from monocle_apptrace.instrumentation.metamodel.mcp.mcp_processor import MCPAgentHandler
35
37
  from monocle_apptrace.instrumentation.metamodel.a2a.methods import A2A_CLIENT_METHODS
38
+ from monocle_apptrace.instrumentation.metamodel.litellm.methods import LITELLM_METHODS
39
+ from monocle_apptrace.instrumentation.metamodel.adk.methods import ADK_METHODS
36
40
 
37
41
  class WrapperMethod:
38
42
  def __init__(
@@ -90,6 +94,7 @@ DEFAULT_METHODS_LIST = (
90
94
  FLASK_METHODS +
91
95
  REQUESTS_METHODS +
92
96
  LANGGRAPH_METHODS +
97
+ AGENTS_METHODS +
93
98
  OPENAI_METHODS +
94
99
  TEAMAI_METHODS +
95
100
  ANTHROPIC_METHODS +
@@ -100,7 +105,9 @@ DEFAULT_METHODS_LIST = (
100
105
  FASTAPI_METHODS +
101
106
  LAMBDA_HTTP_METHODS +
102
107
  MCP_METHODS +
103
- A2A_CLIENT_METHODS
108
+ A2A_CLIENT_METHODS +
109
+ LITELLM_METHODS +
110
+ ADK_METHODS
104
111
  )
105
112
 
106
113
  MONOCLE_SPAN_HANDLERS: Dict[str, SpanHandler] = {
@@ -118,6 +125,7 @@ MONOCLE_SPAN_HANDLERS: Dict[str, SpanHandler] = {
118
125
  "fastapi_response_handler": FastAPIResponseSpanHandler(),
119
126
  "langgraph_agent_handler": LanggraphAgentHandler(),
120
127
  "langgraph_tool_handler": LanggraphToolHandler(),
128
+ "agents_agent_handler": AgentsSpanHandler(),
121
129
  "llamaindex_tool_handler": LlamaIndexToolHandler(),
122
130
  "llamaindex_agent_handler": LlamaIndexAgentHandler(),
123
131
  "llamaindex_single_agent_tool_handler": LlamaIndexSingleAgenttToolHandlerWrapper(),
@@ -1,7 +1,9 @@
1
+ from monocle_apptrace.instrumentation.common.constants import SPAN_SUBTYPES, SPAN_TYPES
1
2
  from monocle_apptrace.instrumentation.metamodel.a2a import _helper
2
3
 
3
4
  A2A_CLIENT = {
4
- "type": "agentic.invocation",
5
+ "type": SPAN_TYPES.AGENTIC_INVOCATION,
6
+ "subtype": SPAN_SUBTYPES.ROUTING,
5
7
  "attributes": [
6
8
  [
7
9
  {
@@ -0,0 +1,206 @@
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_request_input(arguments: Dict[str, Any]) -> Any:
88
+ """
89
+ Extract the input data from agent request.
90
+
91
+ Args:
92
+ arguments: Dictionary containing agent call arguments
93
+
94
+ Returns:
95
+ Any: The extracted input data
96
+ """
97
+ return arguments['kwargs']['new_message'].parts[0].text if 'new_message' in arguments['kwargs'] else None
98
+
99
+ def extract_agent_response(result: Any) -> Any:
100
+ """
101
+ Extract the response data from agent result.
102
+
103
+ Args:
104
+ result: The result returned by the agent
105
+
106
+ Returns:
107
+ Any: The extracted response data
108
+ """
109
+ if result:
110
+ return result.content.parts[0].text
111
+
112
+ def get_tool_name(instance: Any) -> str:
113
+ """
114
+ Extract the name of the tool from the given instance.
115
+
116
+ Args:
117
+ instance: The tool instance to extract name from
118
+
119
+ Returns:
120
+ str: The name of the tool, or a default value if not found
121
+ """
122
+ return getattr(instance, 'name', getattr(instance, '__name__', 'unknown_tool'))
123
+
124
+ def get_tool_description(instance: Any) -> str:
125
+ """
126
+ Extract the description of the tool from the given instance.
127
+
128
+ Args:
129
+ instance: The tool instance to extract description from
130
+
131
+ Returns:
132
+ str: The description of the tool, or a default value if not found
133
+ """
134
+ return getattr(instance, 'description', getattr(instance, '__doc__', 'No description available'))
135
+
136
+ def get_source_agent(arguments) -> str:
137
+ """
138
+ Get the name of the source agent (the agent that is calling a tool or delegating to another agent).
139
+
140
+ Returns:
141
+ str: The name of the source agent
142
+ """
143
+ return arguments['kwargs']['tool_context'].agent_name
144
+
145
+ def get_delegating_agent(arguments) -> str:
146
+ """
147
+ Get the name of the delegating agent (the agent that is delegating a task to another agent).
148
+
149
+ Args:
150
+ arguments: Dictionary containing agent call arguments
151
+ Returns:
152
+ str: The name of the delegating agent
153
+ """
154
+ from_agent = arguments['args'][0].agent.name if hasattr(arguments['args'][0], 'agent') else None
155
+ if from_agent is not None:
156
+ if get_agent_name(arguments['instance']) == from_agent:
157
+ return None
158
+ return from_agent
159
+
160
+ def should_skip_delegation(arguments):
161
+ """
162
+ Determine whether to skip the delegation based on the arguments.
163
+
164
+ Args:
165
+ arguments: Dictionary containing agent call arguments
166
+
167
+ Returns:
168
+ bool: True if delegation should be skipped, False otherwise
169
+ """
170
+ return get_delegating_agent(arguments) is None
171
+
172
+ def extract_tool_input(arguments: Dict[str, Any]) -> Any:
173
+ """
174
+ Extract the input data from tool arguments.
175
+
176
+ Args:
177
+ arguments: Dictionary containing tool call arguments
178
+
179
+ Returns:
180
+ Any: The extracted input data
181
+ """
182
+ return str(arguments['kwargs'].get('args'))
183
+
184
+ def extract_tool_response(result: Any) -> Any:
185
+ """
186
+ Extract the response data from tool result.
187
+
188
+ Args:
189
+ result: The result returned by the tool
190
+
191
+ Returns:
192
+ Any: The extracted response data
193
+ """
194
+ return str(result)
195
+
196
+ def get_target_agent(instance: Any) -> str:
197
+ """
198
+ Extract the name of the target agent (the agent being called/delegated to).
199
+
200
+ Args:
201
+ instance: The target agent instance
202
+
203
+ Returns:
204
+ str: The name of the target agent
205
+ """
206
+ return getattr(instance, 'name', getattr(instance, '__name__', 'unknown_target_agent'))