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.
- monocle_apptrace/__init__.py +1 -0
- monocle_apptrace/__main__.py +19 -0
- monocle_apptrace/exporters/aws/s3_exporter.py +181 -0
- monocle_apptrace/exporters/aws/s3_exporter_opendal.py +137 -0
- monocle_apptrace/exporters/azure/blob_exporter.py +146 -0
- monocle_apptrace/exporters/azure/blob_exporter_opendal.py +162 -0
- monocle_apptrace/exporters/base_exporter.py +48 -0
- monocle_apptrace/exporters/exporter_processor.py +144 -0
- monocle_apptrace/exporters/file_exporter.py +16 -0
- monocle_apptrace/exporters/monocle_exporters.py +55 -0
- monocle_apptrace/exporters/okahu/okahu_exporter.py +117 -0
- monocle_apptrace/instrumentation/__init__.py +1 -0
- monocle_apptrace/instrumentation/common/__init__.py +2 -0
- monocle_apptrace/instrumentation/common/constants.py +70 -0
- monocle_apptrace/instrumentation/common/instrumentor.py +362 -0
- monocle_apptrace/instrumentation/common/span_handler.py +220 -0
- monocle_apptrace/instrumentation/common/utils.py +356 -0
- monocle_apptrace/instrumentation/common/wrapper.py +92 -0
- monocle_apptrace/instrumentation/common/wrapper_method.py +72 -0
- monocle_apptrace/instrumentation/metamodel/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/botocore/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +95 -0
- monocle_apptrace/instrumentation/metamodel/botocore/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +65 -0
- monocle_apptrace/instrumentation/metamodel/botocore/handlers/botocore_span_handler.py +26 -0
- monocle_apptrace/instrumentation/metamodel/botocore/methods.py +16 -0
- monocle_apptrace/instrumentation/metamodel/flask/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/flask/_helper.py +29 -0
- monocle_apptrace/instrumentation/metamodel/flask/methods.py +13 -0
- monocle_apptrace/instrumentation/metamodel/haystack/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +127 -0
- monocle_apptrace/instrumentation/metamodel/haystack/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +76 -0
- monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +61 -0
- monocle_apptrace/instrumentation/metamodel/haystack/methods.py +43 -0
- monocle_apptrace/instrumentation/metamodel/langchain/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +127 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +72 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +58 -0
- monocle_apptrace/{metamodel/maps/lang_chain_methods.json → instrumentation/metamodel/langchain/methods.py} +48 -43
- monocle_apptrace/instrumentation/metamodel/langgraph/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/_helper.py +48 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/entities/inference.py +56 -0
- monocle_apptrace/instrumentation/metamodel/langgraph/methods.py +14 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +172 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/agent.py +47 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +73 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +57 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/methods.py +101 -0
- monocle_apptrace/instrumentation/metamodel/openai/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/openai/_helper.py +112 -0
- monocle_apptrace/instrumentation/metamodel/openai/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/openai/entities/inference.py +71 -0
- monocle_apptrace/instrumentation/metamodel/openai/entities/retrieval.py +43 -0
- monocle_apptrace/instrumentation/metamodel/openai/methods.py +45 -0
- monocle_apptrace/instrumentation/metamodel/requests/__init__.py +4 -0
- monocle_apptrace/instrumentation/metamodel/requests/_helper.py +31 -0
- monocle_apptrace/instrumentation/metamodel/requests/methods.py +12 -0
- {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/METADATA +23 -2
- monocle_apptrace-0.3.0.dist-info/RECORD +68 -0
- {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/WHEEL +1 -1
- monocle_apptrace/constants.py +0 -22
- monocle_apptrace/haystack/__init__.py +0 -9
- monocle_apptrace/haystack/wrap_node.py +0 -27
- monocle_apptrace/haystack/wrap_openai.py +0 -44
- monocle_apptrace/haystack/wrap_pipeline.py +0 -62
- monocle_apptrace/instrumentor.py +0 -124
- monocle_apptrace/langchain/__init__.py +0 -6
- monocle_apptrace/llamaindex/__init__.py +0 -15
- monocle_apptrace/metamodel/README.md +0 -47
- monocle_apptrace/metamodel/entities/README.md +0 -54
- monocle_apptrace/metamodel/entities/entity_types.json +0 -157
- monocle_apptrace/metamodel/entities/entity_types.py +0 -51
- monocle_apptrace/metamodel/maps/haystack_methods.json +0 -25
- monocle_apptrace/metamodel/maps/llama_index_methods.json +0 -70
- monocle_apptrace/metamodel/spans/README.md +0 -121
- monocle_apptrace/metamodel/spans/span_example.json +0 -140
- monocle_apptrace/metamodel/spans/span_format.json +0 -55
- monocle_apptrace/utils.py +0 -93
- monocle_apptrace/wrap_common.py +0 -311
- monocle_apptrace/wrapper.py +0 -24
- monocle_apptrace-0.1.1.dist-info/RECORD +0 -29
- {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
|
File without changes
|
|
@@ -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
|
+
}
|