monocle-apptrace 0.1.1__py3-none-any.whl → 0.3.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/__init__.py +1 -0
  2. monocle_apptrace/__main__.py +19 -0
  3. monocle_apptrace/exporters/aws/s3_exporter.py +181 -0
  4. monocle_apptrace/exporters/aws/s3_exporter_opendal.py +137 -0
  5. monocle_apptrace/exporters/azure/blob_exporter.py +146 -0
  6. monocle_apptrace/exporters/azure/blob_exporter_opendal.py +162 -0
  7. monocle_apptrace/exporters/base_exporter.py +48 -0
  8. monocle_apptrace/exporters/exporter_processor.py +144 -0
  9. monocle_apptrace/exporters/file_exporter.py +16 -0
  10. monocle_apptrace/exporters/monocle_exporters.py +55 -0
  11. monocle_apptrace/exporters/okahu/okahu_exporter.py +117 -0
  12. monocle_apptrace/instrumentation/__init__.py +1 -0
  13. monocle_apptrace/instrumentation/common/__init__.py +2 -0
  14. monocle_apptrace/instrumentation/common/constants.py +70 -0
  15. monocle_apptrace/instrumentation/common/instrumentor.py +362 -0
  16. monocle_apptrace/instrumentation/common/span_handler.py +220 -0
  17. monocle_apptrace/instrumentation/common/utils.py +356 -0
  18. monocle_apptrace/instrumentation/common/wrapper.py +92 -0
  19. monocle_apptrace/instrumentation/common/wrapper_method.py +72 -0
  20. monocle_apptrace/instrumentation/metamodel/__init__.py +0 -0
  21. monocle_apptrace/instrumentation/metamodel/botocore/__init__.py +0 -0
  22. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +95 -0
  23. monocle_apptrace/instrumentation/metamodel/botocore/entities/__init__.py +0 -0
  24. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +65 -0
  25. monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +26 -0
  26. monocle_apptrace/instrumentation/metamodel/botocore/methods.py +16 -0
  27. monocle_apptrace/instrumentation/metamodel/flask/__init__.py +0 -0
  28. monocle_apptrace/instrumentation/metamodel/flask/_helper.py +29 -0
  29. monocle_apptrace/instrumentation/metamodel/flask/methods.py +13 -0
  30. monocle_apptrace/instrumentation/metamodel/haystack/__init__.py +0 -0
  31. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +127 -0
  32. monocle_apptrace/instrumentation/metamodel/haystack/entities/__init__.py +0 -0
  33. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +76 -0
  34. monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +61 -0
  35. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +43 -0
  36. monocle_apptrace/instrumentation/metamodel/langchain/__init__.py +0 -0
  37. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +127 -0
  38. monocle_apptrace/instrumentation/metamodel/langchain/entities/__init__.py +0 -0
  39. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +72 -0
  40. monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +58 -0
  41. monocle_apptrace/{metamodel/maps/lang_chain_methods.json → instrumentation/metamodel/langchain/methods.py} +48 -43
  42. monocle_apptrace/instrumentation/metamodel/langgraph/__init__.py +0 -0
  43. monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +48 -0
  44. monocle_apptrace/instrumentation/metamodel/langgraph/entities/__init__.py +0 -0
  45. monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +56 -0
  46. monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +14 -0
  47. monocle_apptrace/instrumentation/metamodel/llamaindex/__init__.py +0 -0
  48. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +172 -0
  49. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/__init__.py +0 -0
  50. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +47 -0
  51. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +73 -0
  52. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +57 -0
  53. monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +101 -0
  54. monocle_apptrace/instrumentation/metamodel/openai/__init__.py +0 -0
  55. monocle_apptrace/instrumentation/metamodel/openai/_helper.py +112 -0
  56. monocle_apptrace/instrumentation/metamodel/openai/entities/__init__.py +0 -0
  57. monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +71 -0
  58. monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +43 -0
  59. monocle_apptrace/instrumentation/metamodel/openai/methods.py +45 -0
  60. monocle_apptrace/instrumentation/metamodel/requests/__init__.py +4 -0
  61. monocle_apptrace/instrumentation/metamodel/requests/_helper.py +31 -0
  62. monocle_apptrace/instrumentation/metamodel/requests/methods.py +12 -0
  63. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/METADATA +23 -2
  64. monocle_apptrace-0.3.0.dist-info/RECORD +68 -0
  65. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/WHEEL +1 -1
  66. monocle_apptrace/constants.py +0 -22
  67. monocle_apptrace/haystack/__init__.py +0 -9
  68. monocle_apptrace/haystack/wrap_node.py +0 -27
  69. monocle_apptrace/haystack/wrap_openai.py +0 -44
  70. monocle_apptrace/haystack/wrap_pipeline.py +0 -62
  71. monocle_apptrace/instrumentor.py +0 -124
  72. monocle_apptrace/langchain/__init__.py +0 -6
  73. monocle_apptrace/llamaindex/__init__.py +0 -15
  74. monocle_apptrace/metamodel/README.md +0 -47
  75. monocle_apptrace/metamodel/entities/README.md +0 -54
  76. monocle_apptrace/metamodel/entities/entity_types.json +0 -157
  77. monocle_apptrace/metamodel/entities/entity_types.py +0 -51
  78. monocle_apptrace/metamodel/maps/haystack_methods.json +0 -25
  79. monocle_apptrace/metamodel/maps/llama_index_methods.json +0 -70
  80. monocle_apptrace/metamodel/spans/README.md +0 -121
  81. monocle_apptrace/metamodel/spans/span_example.json +0 -140
  82. monocle_apptrace/metamodel/spans/span_format.json +0 -55
  83. monocle_apptrace/utils.py +0 -93
  84. monocle_apptrace/wrap_common.py +0 -311
  85. monocle_apptrace/wrapper.py +0 -24
  86. monocle_apptrace-0.1.1.dist-info/RECORD +0 -29
  87. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/licenses/LICENSE +0 -0
  88. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,356 @@
1
+ import logging, json
2
+ import os
3
+ from typing import Callable, Generic, Optional, TypeVar, Mapping
4
+ import threading, asyncio
5
+
6
+ from opentelemetry.context import attach, detach, get_current, get_value, set_value, Context
7
+ from opentelemetry.trace import NonRecordingSpan, Span, get_tracer
8
+ from opentelemetry.trace.propagation import _SPAN_KEY
9
+ from opentelemetry.sdk.trace import id_generator, TracerProvider
10
+ from opentelemetry.propagate import inject, extract
11
+ from opentelemetry import baggage
12
+ from monocle_apptrace.instrumentation.common.constants import MONOCLE_SCOPE_NAME_PREFIX, SCOPE_METHOD_FILE, SCOPE_CONFIG_PATH, llm_type_map
13
+
14
+ T = TypeVar('T')
15
+ U = TypeVar('U')
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ monocle_tracer_provider: TracerProvider = None
20
+ embedding_model_context = {}
21
+ scope_id_generator = id_generator.RandomIdGenerator()
22
+ http_scopes:dict[str:str] = {}
23
+
24
+ class MonocleSpanException(Exception):
25
+ def __init__(self, err_message:str):
26
+ """
27
+ Monocle exeption to indicate error in span processing.
28
+ Parameters:
29
+ - err_message (str): Error message.
30
+ - status (str): Status code
31
+ """
32
+ super().__init__(err_message)
33
+ self.message = err_message
34
+
35
+ def __str__(self):
36
+ """String representation of the exception."""
37
+ return f"[Monocle Span Error: {self.message} {self.status}"
38
+
39
+ def set_tracer_provider(tracer_provider: TracerProvider):
40
+ global monocle_tracer_provider
41
+ monocle_tracer_provider = tracer_provider
42
+
43
+ def get_tracer_provider() -> TracerProvider:
44
+ global monocle_tracer_provider
45
+ return monocle_tracer_provider
46
+
47
+ def set_span_attribute(span, name, value):
48
+ if value is not None:
49
+ if value != "":
50
+ span.set_attribute(name, value)
51
+
52
+ def dont_throw(func):
53
+ """
54
+ A decorator that wraps the passed in function and logs exceptions instead of throwing them.
55
+
56
+ @param func: The function to wrap
57
+ @return: The wrapper function
58
+ """
59
+ # Obtain a logger specific to the function's module
60
+ logger = logging.getLogger(func.__module__)
61
+
62
+ # pylint: disable=inconsistent-return-statements
63
+ def wrapper(*args, **kwargs):
64
+ try:
65
+ return func(*args, **kwargs)
66
+ except Exception as ex:
67
+ logger.warning("Failed to execute %s, error: %s", func.__name__, str(ex))
68
+
69
+ return wrapper
70
+
71
+ def with_tracer_wrapper(func):
72
+ """Helper for providing tracer for wrapper functions."""
73
+
74
+ def _with_tracer(tracer, handler, to_wrap):
75
+ def wrapper(wrapped, instance, args, kwargs):
76
+ try:
77
+ # get and log the parent span context if injected by the application
78
+ # This is useful for debugging and tracing of Azure functions
79
+ _parent_span_context = get_current()
80
+ if _parent_span_context is not None and _parent_span_context.get(_SPAN_KEY, None):
81
+ parent_span: Span = _parent_span_context.get(_SPAN_KEY, None)
82
+ is_span = isinstance(parent_span, NonRecordingSpan)
83
+ if is_span:
84
+ logger.debug(
85
+ f"Parent span is found with trace id {hex(parent_span.get_span_context().trace_id)}")
86
+ except Exception as e:
87
+ logger.error("Exception in attaching parent context: %s", e)
88
+
89
+ val = func(tracer, handler, to_wrap, wrapped, instance, args, kwargs)
90
+ return val
91
+
92
+ return wrapper
93
+
94
+ return _with_tracer
95
+
96
+ def resolve_from_alias(my_map, alias):
97
+ """Find a alias that is not none from list of aliases"""
98
+
99
+ for i in alias and my_map[i] is not None:
100
+ if i in my_map.keys():
101
+ return my_map[i]
102
+ return None
103
+
104
+ def set_embedding_model(model_name: str):
105
+ """
106
+ Sets the embedding model in the global context.
107
+
108
+ @param model_name: The name of the embedding model to set
109
+ """
110
+ embedding_model_context['embedding_model'] = model_name
111
+
112
+ def get_embedding_model() -> str:
113
+ """
114
+ Retrieves the embedding model from the global context.
115
+
116
+ @return: The name of the embedding model, or 'unknown' if not set
117
+ """
118
+ return embedding_model_context.get('embedding_model', 'unknown')
119
+
120
+ def set_attribute(key: str, value: str):
121
+ """
122
+ Set a value in the global context for a given key.
123
+
124
+ Args:
125
+ key: The key for the context value to set.
126
+ value: The value to set for the given key.
127
+ """
128
+ attach(set_value(key, value))
129
+
130
+ def get_attribute(key: str) -> str:
131
+ """
132
+ Retrieve a value from the global context for a given key.
133
+
134
+ Args:
135
+ key: The key for the context value to retrieve.
136
+
137
+ Returns:
138
+ The value associated with the given key.
139
+ """
140
+ return get_value(key)
141
+
142
+ def flatten_dict(d, parent_key='', sep='_'):
143
+ items = []
144
+ for k, v in d.items():
145
+ new_key = f"{parent_key}{sep}{k}" if parent_key else k
146
+ if isinstance(v, dict):
147
+ items.extend(flatten_dict(v, new_key, sep=sep).items())
148
+ else:
149
+ items.append((new_key, v))
150
+ return dict(items)
151
+
152
+ def get_fully_qualified_class_name(instance):
153
+ if instance is None:
154
+ return None
155
+ module_name = instance.__class__.__module__
156
+ qualname = instance.__class__.__qualname__
157
+ return f"{module_name}.{qualname}"
158
+
159
+ # returns json path like key probe in a dictionary
160
+ def get_nested_value(data, keys):
161
+ for key in keys:
162
+ if isinstance(data, dict) and key in data:
163
+ data = data[key]
164
+ elif hasattr(data, key):
165
+ data = getattr(data, key)
166
+ else:
167
+ return None
168
+ return data
169
+
170
+
171
+ def get_keys_as_tuple(dictionary, *keys):
172
+ return tuple(next((value for key, value in dictionary.items() if key.endswith(k) and value is not None), None) for k in keys)
173
+
174
+ def load_scopes() -> dict:
175
+ methods_data = []
176
+ scope_methods = []
177
+ try:
178
+ scope_config_file_path = os.environ.get(SCOPE_CONFIG_PATH,
179
+ os.path.join(os.getcwd(), SCOPE_METHOD_FILE))
180
+ with open(scope_config_file_path) as f:
181
+ methods_data = json.load(f)
182
+ for method in methods_data:
183
+ if method.get('http_header'):
184
+ http_scopes[method.get('http_header')] = method.get('scope_name')
185
+ else:
186
+ scope_methods.append(method)
187
+ except Exception as e:
188
+ logger.debug(f"Error loading scope methods from file: {e}")
189
+ return scope_methods
190
+
191
+ def __generate_scope_id() -> str:
192
+ global scope_id_generator
193
+ return f"{hex(scope_id_generator.generate_trace_id())}"
194
+
195
+ def set_scope(scope_name: str, scope_value:str = None) -> object:
196
+ return set_scopes({scope_name: scope_value})
197
+
198
+ def set_scopes(scopes:dict[str, object], baggage_context:Context = None) -> object:
199
+ if baggage_context is None:
200
+ baggage_context:Context = get_current()
201
+ for scope_name, scope_value in scopes.items():
202
+ if scope_value is None:
203
+ scope_value = __generate_scope_id()
204
+ baggage_context = baggage.set_baggage(f"{MONOCLE_SCOPE_NAME_PREFIX}{scope_name}", scope_value, baggage_context)
205
+
206
+ token:object = attach(baggage_context)
207
+ return token
208
+
209
+ def remove_scope(token:object) -> None:
210
+ remove_scopes(token)
211
+
212
+ def remove_scopes(token:object) -> None:
213
+ if token is not None:
214
+ detach(token)
215
+
216
+ def get_scopes() -> dict[str, object]:
217
+ monocle_scopes:dict[str, object] = {}
218
+ for key, val in baggage.get_all().items():
219
+ if key.startswith(MONOCLE_SCOPE_NAME_PREFIX):
220
+ monocle_scopes[key[len(MONOCLE_SCOPE_NAME_PREFIX):]] = val
221
+ return monocle_scopes
222
+
223
+ def get_baggage_for_scopes():
224
+ baggage_context:Context = None
225
+ for scope_key, scope_value in get_scopes():
226
+ monocle_scope_name = f"{MONOCLE_SCOPE_NAME_PREFIX}{scope_key}"
227
+ baggage_context = baggage.set_baggage(monocle_scope_name, scope_value, context=baggage_context)
228
+ return baggage_context
229
+
230
+ def set_scopes_from_baggage(baggage_context:Context):
231
+ for scope_key, scope_value in baggage.get_all(baggage_context):
232
+ if scope_key.startswith(MONOCLE_SCOPE_NAME_PREFIX):
233
+ scope_name = scope_key[len(MONOCLE_SCOPE_NAME_PREFIX):]
234
+ set_scope(scope_name, scope_value)
235
+
236
+ def extract_http_headers(headers) -> object:
237
+ global http_scopes
238
+ trace_context:Context = extract(headers, context=get_current())
239
+ imported_scope:dict[str, object] = {}
240
+ for http_header, http_scope in http_scopes.items():
241
+ if http_header in headers:
242
+ imported_scope[http_scope] = f"{http_header}: {headers[http_header]}"
243
+ token = set_scopes(imported_scope, trace_context)
244
+ return token
245
+
246
+ def clear_http_scopes(token:object) -> None:
247
+ global http_scopes
248
+ remove_scopes(token)
249
+
250
+ def http_route_handler(func, *args, **kwargs):
251
+ if 'req' in kwargs and hasattr(kwargs['req'], 'headers'):
252
+ headers = kwargs['req'].headers
253
+ else:
254
+ headers = None
255
+ token = None
256
+ if headers is not None:
257
+ token = extract_http_headers(headers)
258
+ try:
259
+ result = func(*args, **kwargs)
260
+ return result
261
+ finally:
262
+ if token is not None:
263
+ clear_http_scopes(token)
264
+
265
+ async def http_async_route_handler(func, *args, **kwargs):
266
+ if 'req' in kwargs and hasattr(kwargs['req'], 'headers'):
267
+ headers = kwargs['req'].headers
268
+ else:
269
+ headers = None
270
+ return async_wrapper(func, None, None, headers, *args, **kwargs)
271
+
272
+ def run_async_with_scope(method, current_context, exceptions, *args, **kwargs):
273
+ token = None
274
+ try:
275
+ if current_context:
276
+ token = attach(current_context)
277
+ return asyncio.run(method(*args, **kwargs))
278
+ except Exception as e:
279
+ exceptions['exception'] = e
280
+ raise e
281
+ finally:
282
+ if token:
283
+ detach(token)
284
+
285
+ def async_wrapper(method, scope_name=None, scope_value=None, headers=None, *args, **kwargs):
286
+ try:
287
+ run_loop = asyncio.get_running_loop()
288
+ except RuntimeError:
289
+ run_loop = None
290
+
291
+ token = None
292
+ exceptions = {}
293
+ if scope_name:
294
+ token = set_scope(scope_name, scope_value)
295
+ elif headers:
296
+ token = extract_http_headers(headers)
297
+ current_context = get_current()
298
+ try:
299
+ if run_loop and run_loop.is_running():
300
+ results = []
301
+ thread = threading.Thread(target=lambda: results.append(run_async_with_scope(method, current_context, exceptions, *args, **kwargs)))
302
+ thread.start()
303
+ thread.join()
304
+ if 'exception' in exceptions:
305
+ raise exceptions['exception']
306
+ return_value = results[0] if len(results) > 0 else None
307
+ return return_value
308
+ else:
309
+ return run_async_with_scope(method, None, exceptions, *args, **kwargs)
310
+ finally:
311
+ if token:
312
+ remove_scope(token)
313
+
314
+ class Option(Generic[T]):
315
+ def __init__(self, value: Optional[T]):
316
+ self.value = value
317
+
318
+ def is_some(self) -> bool:
319
+ return self.value is not None
320
+
321
+ def is_none(self) -> bool:
322
+ return self.value is None
323
+
324
+ def unwrap_or(self, default: T) -> T:
325
+ return self.value if self.is_some() else default
326
+
327
+ def map(self, func: Callable[[T], U]) -> 'Option[U]':
328
+ if self.is_some():
329
+ return Option(func(self.value))
330
+ return Option(None)
331
+
332
+ def and_then(self, func: Callable[[T], 'Option[U]']) -> 'Option[U]':
333
+ if self.is_some():
334
+ return func(self.value)
335
+ return Option(None)
336
+
337
+ # Example usage
338
+ def try_option(func: Callable[..., T], *args, **kwargs) -> Option[T]:
339
+ try:
340
+ return Option(func(*args, **kwargs))
341
+ except Exception:
342
+ return Option(None)
343
+
344
+ def get_llm_type(instance):
345
+ try:
346
+ llm_type = llm_type_map.get(type(instance).__name__.lower())
347
+ return llm_type
348
+ except:
349
+ pass
350
+
351
+ def resolve_from_alias(my_map, alias):
352
+ """Find a alias that is not none from list of aliases"""
353
+ for i in alias:
354
+ if i in my_map.keys():
355
+ return my_map[i]
356
+ return None
@@ -0,0 +1,92 @@
1
+ # pylint: disable=protected-access
2
+ import logging
3
+ from opentelemetry.trace import Tracer
4
+ from opentelemetry.context import set_value, attach, detach, get_value
5
+
6
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
7
+ from monocle_apptrace.instrumentation.common.utils import (
8
+ get_fully_qualified_class_name,
9
+ with_tracer_wrapper,
10
+ set_scope,
11
+ remove_scope,
12
+ async_wrapper
13
+ )
14
+ from monocle_apptrace.instrumentation.common.constants import WORKFLOW_TYPE_KEY
15
+ logger = logging.getLogger(__name__)
16
+
17
+ def wrapper_processor(async_task: bool, tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
18
+ # Some Langchain objects are wrapped elsewhere, so we ignore them here
19
+ if instance.__class__.__name__ in ("AgentExecutor"):
20
+ return wrapped(*args, **kwargs)
21
+
22
+ if hasattr(instance, "name") and instance.name:
23
+ name = f"{to_wrap.get('span_name')}.{instance.name.lower()}"
24
+ elif to_wrap.get("span_name"):
25
+ name = to_wrap.get("span_name")
26
+ else:
27
+ name = get_fully_qualified_class_name(instance)
28
+
29
+ return_value = None
30
+ token = None
31
+ try:
32
+ handler.pre_tracing(to_wrap, wrapped, instance, args, kwargs)
33
+ skip_scan:bool = to_wrap.get('skip_span') or handler.skip_span(to_wrap, wrapped, instance, args, kwargs)
34
+ if not to_wrap.get('skip_span'):
35
+ token = SpanHandler.attach_workflow_type(to_wrap=to_wrap)
36
+ if skip_scan:
37
+ if async_task:
38
+ return_value = async_wrapper(wrapped, None, None, None, *args, **kwargs)
39
+ else:
40
+ return_value = wrapped(*args, **kwargs)
41
+ else:
42
+ return_value = span_processor(name, async_task, tracer, handler, to_wrap, wrapped, instance, args, kwargs)
43
+ return return_value
44
+ finally:
45
+ handler.detach_workflow_type(token)
46
+ handler.post_tracing(to_wrap, wrapped, instance, args, kwargs, return_value)
47
+
48
+ def span_processor(name: str, async_task: bool, tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
49
+ # For singleton spans, eg OpenAI inference generate a workflow span to format the workflow specific attributes
50
+ return_value = None
51
+ with tracer.start_as_current_span(name) as span:
52
+ # Since Spanhandler can be overridden, ensure we set default monocle attributes.
53
+ SpanHandler.set_default_monocle_attributes(span)
54
+ if SpanHandler.is_root_span(span):
55
+ SpanHandler.set_workflow_properties(span, to_wrap)
56
+ if handler.is_non_workflow_root_span(span, to_wrap):
57
+ # This is a direct API call of a non-framework type, call the span_processor recursively for the actual span
58
+ return_value = span_processor(name, async_task, tracer, handler, to_wrap, wrapped, instance, args, kwargs)
59
+ else:
60
+ handler.pre_task_processing(to_wrap, wrapped, instance, args, kwargs, span)
61
+ if async_task:
62
+ return_value = async_wrapper(wrapped, None, None, None, *args, **kwargs)
63
+ else:
64
+ return_value = wrapped(*args, **kwargs)
65
+ handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
66
+ handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
67
+ return return_value
68
+
69
+ @with_tracer_wrapper
70
+ def task_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
71
+ return wrapper_processor(False, tracer, handler, to_wrap, wrapped, instance, args, kwargs)
72
+
73
+ @with_tracer_wrapper
74
+ async def atask_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
75
+ return wrapper_processor(True, tracer, handler, to_wrap, wrapped, instance, args, kwargs)
76
+
77
+ @with_tracer_wrapper
78
+ def scope_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
79
+ scope_name = to_wrap.get('scope_name', None)
80
+ if scope_name:
81
+ token = set_scope(scope_name)
82
+ return_value = wrapped(*args, **kwargs)
83
+ if scope_name:
84
+ remove_scope(token)
85
+ return return_value
86
+
87
+ @with_tracer_wrapper
88
+ async def ascope_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
89
+ scope_name = to_wrap.get('scope_name', None)
90
+ scope_value = to_wrap.get('scope_value', None)
91
+ return_value = async_wrapper(wrapped, scope_name, scope_value, None, *args, **kwargs)
92
+ return return_value
@@ -0,0 +1,72 @@
1
+ # pylint: disable=too-few-public-methods
2
+ from typing import Any, Dict
3
+ from monocle_apptrace.instrumentation.common.wrapper import task_wrapper, scope_wrapper
4
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler, NonFrameworkSpanHandler
5
+ from monocle_apptrace.instrumentation.metamodel.botocore.methods import BOTOCORE_METHODS
6
+ from monocle_apptrace.instrumentation.metamodel.botocore.handlers.botocore_span_handler import BotoCoreSpanHandler
7
+ from monocle_apptrace.instrumentation.metamodel.langchain.methods import (
8
+ LANGCHAIN_METHODS,
9
+ )
10
+ from monocle_apptrace.instrumentation.metamodel.llamaindex.methods import (LLAMAINDEX_METHODS, )
11
+ from monocle_apptrace.instrumentation.metamodel.haystack.methods import (HAYSTACK_METHODS, )
12
+ from monocle_apptrace.instrumentation.metamodel.openai.methods import (OPENAI_METHODS,)
13
+ from monocle_apptrace.instrumentation.metamodel.langgraph.methods import LANGGRAPH_METHODS
14
+ from monocle_apptrace.instrumentation.metamodel.flask.methods import (FLASK_METHODS, )
15
+ from monocle_apptrace.instrumentation.metamodel.flask._helper import FlaskSpanHandler
16
+ from monocle_apptrace.instrumentation.metamodel.requests.methods import (REQUESTS_METHODS, )
17
+ from monocle_apptrace.instrumentation.metamodel.requests._helper import RequestSpanHandler
18
+
19
+ class WrapperMethod:
20
+ def __init__(
21
+ self,
22
+ package: str,
23
+ object_name: str,
24
+ method: str,
25
+ span_name: str = None,
26
+ output_processor : str = None,
27
+ wrapper_method = task_wrapper,
28
+ span_handler = 'default',
29
+ scope_name: str = None,
30
+ span_type: str = None
31
+ ):
32
+ self.package = package
33
+ self.object = object_name
34
+ self.method = method
35
+ self.span_name = span_name
36
+ self.output_processor=output_processor
37
+ self.span_type = span_type
38
+
39
+ self.span_handler:SpanHandler.__class__ = span_handler
40
+ self.scope_name = scope_name
41
+ if scope_name:
42
+ self.wrapper_method = scope_wrapper
43
+ else:
44
+ self.wrapper_method = wrapper_method
45
+
46
+ def to_dict(self) -> dict:
47
+ # Create a dictionary representation of the instance
48
+ instance_dict = {
49
+ 'package': self.package,
50
+ 'object': self.object,
51
+ 'method': self.method,
52
+ 'span_name': self.span_name,
53
+ 'output_processor': self.output_processor,
54
+ 'wrapper_method': self.wrapper_method,
55
+ 'span_handler': self.span_handler,
56
+ 'scope_name': self.scope_name,
57
+ 'span_type': self.span_type
58
+ }
59
+ return instance_dict
60
+
61
+ def get_span_handler(self) -> SpanHandler:
62
+ return self.span_handler()
63
+
64
+ DEFAULT_METHODS_LIST = LANGCHAIN_METHODS + LLAMAINDEX_METHODS + HAYSTACK_METHODS + BOTOCORE_METHODS + FLASK_METHODS + REQUESTS_METHODS + LANGGRAPH_METHODS + OPENAI_METHODS
65
+
66
+ MONOCLE_SPAN_HANDLERS: Dict[str, SpanHandler] = {
67
+ "default": SpanHandler(),
68
+ "botocore_handler": BotoCoreSpanHandler(),
69
+ "flask_handler": FlaskSpanHandler(),
70
+ "request_handler": RequestSpanHandler(),
71
+ "non_framework_handler": NonFrameworkSpanHandler()
72
+ }
File without changes
@@ -0,0 +1,95 @@
1
+ """
2
+ This module provides utility functions for extracting system, user,
3
+ and assistant messages from various input formats.
4
+ """
5
+
6
+ import logging
7
+ import json
8
+ from io import BytesIO
9
+ from functools import wraps
10
+ from monocle_apptrace.instrumentation.common.span_handler import SpanHandler
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def extract_messages(args):
16
+ """Extract system and user messages"""
17
+ try:
18
+ messages = []
19
+ if args and isinstance(args, dict) and len(args) > 0:
20
+ if 'Body' in args and isinstance(args['Body'], str):
21
+ data = json.loads(args['Body'])
22
+ question = data.get("question")
23
+ messages.append(question)
24
+ if 'messages' in args and isinstance(args['messages'], list):
25
+ role = args['messages'][0]['role']
26
+ user_message = extract_query_from_content(args['messages'][0]['content'][0]['text'])
27
+ messages.append({role: user_message})
28
+ return [str(d) for d in messages]
29
+ except Exception as e:
30
+ logger.warning("Warning: Error occurred in extract_messages: %s", str(e))
31
+ return []
32
+
33
+
34
+ def extract_assistant_message(response):
35
+ try:
36
+ if "Body" in response and hasattr(response['Body'], "_raw_stream"):
37
+ raw_stream = getattr(response['Body'], "_raw_stream")
38
+ if hasattr(raw_stream, "data"):
39
+ response_bytes = getattr(raw_stream, "data")
40
+ response_str = response_bytes.decode('utf-8')
41
+ response_dict = json.loads(response_str)
42
+ response['Body'] = BytesIO(response_bytes)
43
+ return [response_dict["answer"]]
44
+ if "output" in response:
45
+ output = response.get("output", {})
46
+ message = output.get("message", {})
47
+ content = message.get("content", [])
48
+ if isinstance(content, list) and len(content) > 0 and "text" in content[0]:
49
+ reply = content[0]["text"]
50
+ return [reply]
51
+ except Exception as e:
52
+ logger.warning("Warning: Error occurred in extract_assistant_message: %s", str(e))
53
+ return []
54
+
55
+
56
+ def extract_query_from_content(content):
57
+ try:
58
+ query_prefix = "Query:"
59
+ answer_prefix = "Answer:"
60
+ query_start = content.find(query_prefix)
61
+ if query_start == -1:
62
+ return None
63
+
64
+ query_start += len(query_prefix)
65
+ answer_start = content.find(answer_prefix, query_start)
66
+ if answer_start == -1:
67
+ query = content[query_start:].strip()
68
+ else:
69
+ query = content[query_start:answer_start].strip()
70
+ return query
71
+ except Exception as e:
72
+ logger.warning("Warning: Error occurred in extract_query_from_content: %s", str(e))
73
+ return ""
74
+
75
+
76
+ def resolve_from_alias(my_map, alias):
77
+ """Find a alias that is not none from list of aliases"""
78
+
79
+ for i in alias:
80
+ if i in my_map.keys():
81
+ return my_map[i]
82
+ return None
83
+
84
+ def update_span_from_llm_response(response, instance):
85
+ meta_dict = {}
86
+ if response is not None and isinstance(response, dict) and "usage" in response:
87
+ token_usage = response["usage"]
88
+
89
+ if token_usage is not None:
90
+ temperature = instance.__dict__.get("temperature", None)
91
+ meta_dict.update({"temperature": temperature})
92
+ meta_dict.update({"completion_tokens": resolve_from_alias(token_usage,["completion_tokens","output_tokens","outputTokens"])})
93
+ meta_dict.update({"prompt_tokens": resolve_from_alias(token_usage,["prompt_tokens","input_tokens","inputTokens"])})
94
+ meta_dict.update({"total_tokens": resolve_from_alias(token_usage,["total_tokens","totalTokens"])})
95
+ return meta_dict
@@ -0,0 +1,65 @@
1
+ from monocle_apptrace.instrumentation.metamodel.botocore import (
2
+ _helper,
3
+ )
4
+
5
+ INFERENCE = {
6
+ "type": "inference",
7
+ "attributes": [
8
+ [
9
+ {
10
+ "_comment": "provider type , inference_endpoint",
11
+ "attribute": "type",
12
+ "accessor": lambda arguments: 'inference.aws_sagemaker'
13
+ },
14
+ {
15
+ "attribute": "inference_endpoint",
16
+ "accessor": lambda arguments: arguments['instance'].meta.endpoint_url
17
+ }
18
+ ],
19
+ [
20
+ {
21
+ "_comment": "LLM Model",
22
+ "attribute": "name",
23
+ "accessor": lambda arguments: _helper.resolve_from_alias(arguments['kwargs'],
24
+ ['EndpointName', 'modelId'])
25
+ },
26
+ {
27
+ "attribute": "type",
28
+ "accessor": lambda arguments: 'model.llm.' + _helper.resolve_from_alias(arguments['kwargs'],
29
+ ['EndpointName', 'modelId'])
30
+ }
31
+ ]
32
+ ],
33
+ "events": [
34
+ {"name": "data.input",
35
+ "attributes": [
36
+
37
+ {
38
+ "_comment": "this is instruction and user query to LLM",
39
+ "attribute": "input",
40
+ "accessor": lambda arguments: _helper.extract_messages(arguments['kwargs'])
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ "name": "data.output",
46
+ "attributes": [
47
+ {
48
+ "_comment": "this is response from LLM",
49
+ "attribute": "response",
50
+ "accessor": lambda arguments: _helper.extract_assistant_message(arguments['result'])
51
+ }
52
+ ]
53
+ },
54
+ {
55
+ "name": "metadata",
56
+ "attributes": [
57
+ {
58
+ "_comment": "this is metadata usage from LLM",
59
+ "accessor": lambda arguments: _helper.update_span_from_llm_response(arguments['result'],
60
+ arguments['instance'])
61
+ }
62
+ ]
63
+ }
64
+ ]
65
+ }