monocle-apptrace 0.3.0b1__py3-none-any.whl → 0.3.0b3__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/exporters/aws/s3_exporter.py +1 -1
- monocle_apptrace/exporters/aws/s3_exporter_opendal.py +126 -0
- monocle_apptrace/exporters/azure/blob_exporter_opendal.py +147 -0
- monocle_apptrace/exporters/monocle_exporters.py +38 -20
- monocle_apptrace/instrumentation/__init__.py +0 -0
- monocle_apptrace/instrumentation/common/__init__.py +0 -0
- monocle_apptrace/{constants.py → instrumentation/common/constants.py} +13 -0
- monocle_apptrace/instrumentation/common/instrumentor.py +208 -0
- monocle_apptrace/instrumentation/common/span_handler.py +154 -0
- monocle_apptrace/instrumentation/common/utils.py +171 -0
- monocle_apptrace/instrumentation/common/wrapper.py +69 -0
- monocle_apptrace/instrumentation/common/wrapper_method.py +45 -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 +126 -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/methods.py +16 -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 +42 -0
- monocle_apptrace/instrumentation/metamodel/langchain/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +121 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +71 -0
- monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +58 -0
- monocle_apptrace/instrumentation/metamodel/langchain/methods.py +105 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +154 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/__init__.py +0 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +71 -0
- monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +57 -0
- monocle_apptrace/{metamodel/maps/llamaindex_methods.json → instrumentation/metamodel/llamaindex/methods.py} +28 -31
- {monocle_apptrace-0.3.0b1.dist-info → monocle_apptrace-0.3.0b3.dist-info}/METADATA +17 -2
- monocle_apptrace-0.3.0b3.dist-info/RECORD +48 -0
- {monocle_apptrace-0.3.0b1.dist-info → monocle_apptrace-0.3.0b3.dist-info}/WHEEL +1 -1
- monocle_apptrace/botocore/__init__.py +0 -9
- monocle_apptrace/haystack/__init__.py +0 -9
- monocle_apptrace/haystack/wrap_pipeline.py +0 -63
- monocle_apptrace/instrumentor.py +0 -121
- monocle_apptrace/langchain/__init__.py +0 -9
- monocle_apptrace/llamaindex/__init__.py +0 -16
- monocle_apptrace/message_processing.py +0 -80
- monocle_apptrace/metamodel/README.md +0 -47
- monocle_apptrace/metamodel/entities/README.md +0 -77
- monocle_apptrace/metamodel/entities/app_hosting_types.json +0 -29
- monocle_apptrace/metamodel/entities/entities.json +0 -49
- monocle_apptrace/metamodel/entities/inference_types.json +0 -33
- monocle_apptrace/metamodel/entities/model_types.json +0 -41
- monocle_apptrace/metamodel/entities/vector_store_types.json +0 -25
- monocle_apptrace/metamodel/entities/workflow_types.json +0 -22
- monocle_apptrace/metamodel/maps/attributes/inference/botocore_entities.json +0 -27
- monocle_apptrace/metamodel/maps/attributes/inference/haystack_entities.json +0 -57
- monocle_apptrace/metamodel/maps/attributes/inference/langchain_entities.json +0 -57
- monocle_apptrace/metamodel/maps/attributes/inference/llamaindex_entities.json +0 -57
- monocle_apptrace/metamodel/maps/attributes/retrieval/haystack_entities.json +0 -31
- monocle_apptrace/metamodel/maps/attributes/retrieval/langchain_entities.json +0 -31
- monocle_apptrace/metamodel/maps/attributes/retrieval/llamaindex_entities.json +0 -31
- monocle_apptrace/metamodel/maps/botocore_methods.json +0 -13
- monocle_apptrace/metamodel/maps/haystack_methods.json +0 -45
- monocle_apptrace/metamodel/maps/langchain_methods.json +0 -129
- 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/metamodel/spans/span_types.json +0 -16
- monocle_apptrace/utils.py +0 -252
- monocle_apptrace/wrap_common.py +0 -511
- monocle_apptrace/wrapper.py +0 -27
- monocle_apptrace-0.3.0b1.dist-info/RECORD +0 -48
- {monocle_apptrace-0.3.0b1.dist-info → monocle_apptrace-0.3.0b3.dist-info}/licenses/LICENSE +0 -0
- {monocle_apptrace-0.3.0b1.dist-info → monocle_apptrace-0.3.0b3.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
from opentelemetry.context import get_current
|
|
5
|
+
from opentelemetry.context import get_value
|
|
6
|
+
from opentelemetry.sdk.trace import Span
|
|
7
|
+
|
|
8
|
+
from monocle_apptrace.instrumentation.common.constants import (
|
|
9
|
+
QUERY,
|
|
10
|
+
service_name_map,
|
|
11
|
+
service_type_map,
|
|
12
|
+
)
|
|
13
|
+
from monocle_apptrace.instrumentation.common.utils import set_attribute
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
WORKFLOW_TYPE_MAP = {
|
|
18
|
+
"llama_index": "workflow.llamaindex",
|
|
19
|
+
"langchain": "workflow.langchain",
|
|
20
|
+
"haystack": "workflow.haystack"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpanHandler:
|
|
26
|
+
|
|
27
|
+
def validate(self, to_wrap, wrapped, instance, args, kwargs):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def pre_task_processing(self, to_wrap, wrapped, instance, args, span):
|
|
31
|
+
if self.__is_root_span(span):
|
|
32
|
+
try:
|
|
33
|
+
sdk_version = version("monocle_apptrace")
|
|
34
|
+
span.set_attribute("monocle_apptrace.version", sdk_version)
|
|
35
|
+
except Exception as e:
|
|
36
|
+
logger.warning("Exception finding monocle-apptrace version.")
|
|
37
|
+
if "pipeline" in to_wrap['package']:
|
|
38
|
+
set_attribute(QUERY, args[0]['prompt_builder']['question'])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def post_task_processing(self, to_wrap, wrapped, instance, args, kwargs, result, span):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def set_context_properties(self, to_wrap, wrapped, instance, args, kwargs):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def hydrate_span(self, to_wrap, wrapped, instance, args, kwargs, result, span):
|
|
49
|
+
self.hydrate_attributes(to_wrap, wrapped, instance, args, kwargs, result, span)
|
|
50
|
+
self.hydrate_events(to_wrap, wrapped, instance, args, kwargs, result, span)
|
|
51
|
+
|
|
52
|
+
def hydrate_attributes(self, to_wrap, wrapped, instance, args, kwargs, result, span):
|
|
53
|
+
span_index = 0
|
|
54
|
+
if self.__is_root_span(span):
|
|
55
|
+
span_index += self.set_workflow_attributes(to_wrap, span, span_index+1)
|
|
56
|
+
span_index += self.set_app_hosting_identifier_attribute(span, span_index+1)
|
|
57
|
+
|
|
58
|
+
if 'output_processor' in to_wrap and to_wrap["output_processor"] is not None:
|
|
59
|
+
output_processor=to_wrap['output_processor']
|
|
60
|
+
if 'type' in output_processor:
|
|
61
|
+
span.set_attribute("span.type", output_processor['type'])
|
|
62
|
+
else:
|
|
63
|
+
logger.warning("type of span not found or incorrect written in entity json")
|
|
64
|
+
if 'attributes' in output_processor:
|
|
65
|
+
for processors in output_processor["attributes"]:
|
|
66
|
+
for processor in processors:
|
|
67
|
+
attribute = processor.get('attribute')
|
|
68
|
+
accessor = processor.get('accessor')
|
|
69
|
+
|
|
70
|
+
if attribute and accessor:
|
|
71
|
+
attribute_name = f"entity.{span_index+1}.{attribute}"
|
|
72
|
+
try:
|
|
73
|
+
arguments = {"instance":instance, "args":args, "kwargs":kwargs, "result":result}
|
|
74
|
+
result = accessor(arguments)
|
|
75
|
+
if result and isinstance(result, str):
|
|
76
|
+
span.set_attribute(attribute_name, result)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Error processing accessor: {e}")
|
|
79
|
+
else:
|
|
80
|
+
logger.warning(f"{' and '.join([key for key in ['attribute', 'accessor'] if not processor.get(key)])} not found or incorrect in entity JSON")
|
|
81
|
+
span_index += 1
|
|
82
|
+
else:
|
|
83
|
+
logger.warning("attributes not found or incorrect written in entity json")
|
|
84
|
+
|
|
85
|
+
if span_index > 0:
|
|
86
|
+
span.set_attribute("entity.count", span_index)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def hydrate_events(self, to_wrap, wrapped, instance, args, kwargs, result, span):
|
|
90
|
+
if 'output_processor' in to_wrap and to_wrap["output_processor"] is not None:
|
|
91
|
+
output_processor=to_wrap['output_processor']
|
|
92
|
+
arguments = {"instance": instance, "args": args, "kwargs": kwargs, "result": result}
|
|
93
|
+
if 'events' in output_processor:
|
|
94
|
+
events = output_processor['events']
|
|
95
|
+
for event in events:
|
|
96
|
+
event_name = event.get("name")
|
|
97
|
+
event_attributes = {}
|
|
98
|
+
attributes = event.get("attributes", [])
|
|
99
|
+
for attribute in attributes:
|
|
100
|
+
attribute_key = attribute.get("attribute")
|
|
101
|
+
accessor = attribute.get("accessor")
|
|
102
|
+
if accessor:
|
|
103
|
+
try:
|
|
104
|
+
if attribute_key is not None:
|
|
105
|
+
event_attributes[attribute_key] = accessor(arguments)
|
|
106
|
+
else:
|
|
107
|
+
event_attributes.update(accessor(arguments))
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Error evaluating accessor for attribute '{attribute_key}': {e}")
|
|
110
|
+
span.add_event(name=event_name, attributes=event_attributes)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def set_workflow_attributes(self, to_wrap, span: Span, span_index):
|
|
115
|
+
return_value = 1
|
|
116
|
+
workflow_name = self.get_workflow_name(span=span)
|
|
117
|
+
if workflow_name:
|
|
118
|
+
span.set_attribute("span.type", "workflow")
|
|
119
|
+
span.set_attribute(f"entity.{span_index}.name", workflow_name)
|
|
120
|
+
# workflow type
|
|
121
|
+
package_name = to_wrap.get('package')
|
|
122
|
+
workflow_type_set = False
|
|
123
|
+
for (package, workflow_type) in WORKFLOW_TYPE_MAP.items():
|
|
124
|
+
if (package_name is not None and package in package_name):
|
|
125
|
+
span.set_attribute(f"entity.{span_index}.type", workflow_type)
|
|
126
|
+
workflow_type_set = True
|
|
127
|
+
if not workflow_type_set:
|
|
128
|
+
span.set_attribute(f"entity.{span_index}.type", "workflow.generic")
|
|
129
|
+
return return_value
|
|
130
|
+
|
|
131
|
+
def set_app_hosting_identifier_attribute(self, span, span_index):
|
|
132
|
+
return_value = 0
|
|
133
|
+
# Search env to indentify the infra service type, if found check env for service name if possible
|
|
134
|
+
for type_env, type_name in service_type_map.items():
|
|
135
|
+
if type_env in os.environ:
|
|
136
|
+
return_value = 1
|
|
137
|
+
span.set_attribute(f"entity.{span_index}.type", f"app_hosting.{type_name}")
|
|
138
|
+
entity_name_env = service_name_map.get(type_name, "unknown")
|
|
139
|
+
span.set_attribute(f"entity.{span_index}.name", os.environ.get(entity_name_env, "generic"))
|
|
140
|
+
return return_value
|
|
141
|
+
|
|
142
|
+
def get_workflow_name(self, span: Span) -> str:
|
|
143
|
+
try:
|
|
144
|
+
return get_value("workflow_name") or span.resource.attributes.get("service.name")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.exception(f"Error getting workflow name: {e}")
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
def __is_root_span(self, curr_span: Span) -> bool:
|
|
150
|
+
try:
|
|
151
|
+
if curr_span is not None and hasattr(curr_span, "parent"):
|
|
152
|
+
return curr_span.parent is None or get_current().get("root_span_id") == curr_span.parent.span_id
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.warning(f"Error finding root span: {e}")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Callable, Generic, Optional, TypeVar
|
|
3
|
+
|
|
4
|
+
from opentelemetry.context import attach, detach, get_current, get_value, set_value
|
|
5
|
+
from opentelemetry.trace import NonRecordingSpan, Span
|
|
6
|
+
from opentelemetry.trace.propagation import _SPAN_KEY
|
|
7
|
+
|
|
8
|
+
T = TypeVar('T')
|
|
9
|
+
U = TypeVar('U')
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
embedding_model_context = {}
|
|
14
|
+
|
|
15
|
+
def set_span_attribute(span, name, value):
|
|
16
|
+
if value is not None:
|
|
17
|
+
if value != "":
|
|
18
|
+
span.set_attribute(name, value)
|
|
19
|
+
|
|
20
|
+
def dont_throw(func):
|
|
21
|
+
"""
|
|
22
|
+
A decorator that wraps the passed in function and logs exceptions instead of throwing them.
|
|
23
|
+
|
|
24
|
+
@param func: The function to wrap
|
|
25
|
+
@return: The wrapper function
|
|
26
|
+
"""
|
|
27
|
+
# Obtain a logger specific to the function's module
|
|
28
|
+
logger = logging.getLogger(func.__module__)
|
|
29
|
+
|
|
30
|
+
# pylint: disable=inconsistent-return-statements
|
|
31
|
+
def wrapper(*args, **kwargs):
|
|
32
|
+
try:
|
|
33
|
+
return func(*args, **kwargs)
|
|
34
|
+
except Exception as ex:
|
|
35
|
+
logger.warning("Failed to execute %s, error: %s", func.__name__, str(ex))
|
|
36
|
+
|
|
37
|
+
return wrapper
|
|
38
|
+
|
|
39
|
+
def with_tracer_wrapper(func):
|
|
40
|
+
"""Helper for providing tracer for wrapper functions."""
|
|
41
|
+
|
|
42
|
+
def _with_tracer(tracer, handler, to_wrap):
|
|
43
|
+
def wrapper(wrapped, instance, args, kwargs):
|
|
44
|
+
try:
|
|
45
|
+
# get and log the parent span context if injected by the application
|
|
46
|
+
# This is useful for debugging and tracing of Azure functions
|
|
47
|
+
_parent_span_context = get_current()
|
|
48
|
+
if _parent_span_context is not None and _parent_span_context.get(_SPAN_KEY, None):
|
|
49
|
+
parent_span: Span = _parent_span_context.get(_SPAN_KEY, None)
|
|
50
|
+
is_span = isinstance(parent_span, NonRecordingSpan)
|
|
51
|
+
if is_span:
|
|
52
|
+
logger.debug(
|
|
53
|
+
f"Parent span is found with trace id {hex(parent_span.get_span_context().trace_id)}")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.error("Exception in attaching parent context: %s", e)
|
|
56
|
+
|
|
57
|
+
val = func(tracer, handler, to_wrap, wrapped, instance, args, kwargs)
|
|
58
|
+
return val
|
|
59
|
+
|
|
60
|
+
return wrapper
|
|
61
|
+
|
|
62
|
+
return _with_tracer
|
|
63
|
+
|
|
64
|
+
def resolve_from_alias(my_map, alias):
|
|
65
|
+
"""Find a alias that is not none from list of aliases"""
|
|
66
|
+
|
|
67
|
+
for i in alias and my_map[i] is not None:
|
|
68
|
+
if i in my_map.keys():
|
|
69
|
+
return my_map[i]
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def set_embedding_model(model_name: str):
|
|
73
|
+
"""
|
|
74
|
+
Sets the embedding model in the global context.
|
|
75
|
+
|
|
76
|
+
@param model_name: The name of the embedding model to set
|
|
77
|
+
"""
|
|
78
|
+
embedding_model_context['embedding_model'] = model_name
|
|
79
|
+
|
|
80
|
+
def get_embedding_model() -> str:
|
|
81
|
+
"""
|
|
82
|
+
Retrieves the embedding model from the global context.
|
|
83
|
+
|
|
84
|
+
@return: The name of the embedding model, or 'unknown' if not set
|
|
85
|
+
"""
|
|
86
|
+
return embedding_model_context.get('embedding_model', 'unknown')
|
|
87
|
+
|
|
88
|
+
def set_attribute(key: str, value: str):
|
|
89
|
+
"""
|
|
90
|
+
Set a value in the global context for a given key.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
key: The key for the context value to set.
|
|
94
|
+
value: The value to set for the given key.
|
|
95
|
+
"""
|
|
96
|
+
attach(set_value(key, value))
|
|
97
|
+
|
|
98
|
+
def get_attribute(key: str) -> str:
|
|
99
|
+
"""
|
|
100
|
+
Retrieve a value from the global context for a given key.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
key: The key for the context value to retrieve.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The value associated with the given key.
|
|
107
|
+
"""
|
|
108
|
+
return get_value(key)
|
|
109
|
+
|
|
110
|
+
def flatten_dict(d, parent_key='', sep='_'):
|
|
111
|
+
items = []
|
|
112
|
+
for k, v in d.items():
|
|
113
|
+
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
|
114
|
+
if isinstance(v, dict):
|
|
115
|
+
items.extend(flatten_dict(v, new_key, sep=sep).items())
|
|
116
|
+
else:
|
|
117
|
+
items.append((new_key, v))
|
|
118
|
+
return dict(items)
|
|
119
|
+
|
|
120
|
+
def get_fully_qualified_class_name(instance):
|
|
121
|
+
if instance is None:
|
|
122
|
+
return None
|
|
123
|
+
module_name = instance.__class__.__module__
|
|
124
|
+
qualname = instance.__class__.__qualname__
|
|
125
|
+
return f"{module_name}.{qualname}"
|
|
126
|
+
|
|
127
|
+
# returns json path like key probe in a dictionary
|
|
128
|
+
def get_nested_value(data, keys):
|
|
129
|
+
for key in keys:
|
|
130
|
+
if isinstance(data, dict) and key in data:
|
|
131
|
+
data = data[key]
|
|
132
|
+
elif hasattr(data, key):
|
|
133
|
+
data = getattr(data, key)
|
|
134
|
+
else:
|
|
135
|
+
return None
|
|
136
|
+
return data
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_keys_as_tuple(dictionary, *keys):
|
|
140
|
+
return tuple(next((value for key, value in dictionary.items() if key.endswith(k) and value is not None), None) for k in keys)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Option(Generic[T]):
|
|
144
|
+
def __init__(self, value: Optional[T]):
|
|
145
|
+
self.value = value
|
|
146
|
+
|
|
147
|
+
def is_some(self) -> bool:
|
|
148
|
+
return self.value is not None
|
|
149
|
+
|
|
150
|
+
def is_none(self) -> bool:
|
|
151
|
+
return self.value is None
|
|
152
|
+
|
|
153
|
+
def unwrap_or(self, default: T) -> T:
|
|
154
|
+
return self.value if self.is_some() else default
|
|
155
|
+
|
|
156
|
+
def map(self, func: Callable[[T], U]) -> 'Option[U]':
|
|
157
|
+
if self.is_some():
|
|
158
|
+
return Option(func(self.value))
|
|
159
|
+
return Option(None)
|
|
160
|
+
|
|
161
|
+
def and_then(self, func: Callable[[T], 'Option[U]']) -> 'Option[U]':
|
|
162
|
+
if self.is_some():
|
|
163
|
+
return func(self.value)
|
|
164
|
+
return Option(None)
|
|
165
|
+
|
|
166
|
+
# Example usage
|
|
167
|
+
def try_option(func: Callable[..., T], *args, **kwargs) -> Option[T]:
|
|
168
|
+
try:
|
|
169
|
+
return Option(func(*args, **kwargs))
|
|
170
|
+
except Exception:
|
|
171
|
+
return Option(None)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# pylint: disable=protected-access
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from opentelemetry.trace import Tracer
|
|
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
|
+
)
|
|
11
|
+
from monocle_apptrace.instrumentation.metamodel.botocore import _helper
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@with_tracer_wrapper
|
|
16
|
+
def task_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
|
|
17
|
+
|
|
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
|
+
handler.validate(to_wrap, wrapped, instance, args, kwargs)
|
|
30
|
+
handler.set_context_properties(to_wrap, wrapped, instance, args, kwargs)
|
|
31
|
+
|
|
32
|
+
if to_wrap.get('skip_span'):
|
|
33
|
+
return_value = wrapped(*args, **kwargs)
|
|
34
|
+
_helper.botocore_processor(tracer, to_wrap, wrapped, instance, args, kwargs, return_value)
|
|
35
|
+
return return_value
|
|
36
|
+
|
|
37
|
+
with tracer.start_as_current_span(name) as span:
|
|
38
|
+
handler.pre_task_processing(to_wrap, wrapped, instance, args, span)
|
|
39
|
+
return_value = wrapped(*args, **kwargs)
|
|
40
|
+
handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
41
|
+
handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
42
|
+
|
|
43
|
+
return return_value
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@with_tracer_wrapper
|
|
47
|
+
async def atask_wrapper(tracer: Tracer, handler: SpanHandler, to_wrap, wrapped, instance, args, kwargs):
|
|
48
|
+
"""Instruments and calls every function defined in TO_WRAP."""
|
|
49
|
+
|
|
50
|
+
# Some Langchain objects are wrapped elsewhere, so we ignore them here
|
|
51
|
+
if instance.__class__.__name__ in ("AgentExecutor"):
|
|
52
|
+
return wrapped(*args, **kwargs)
|
|
53
|
+
|
|
54
|
+
if hasattr(instance, "name") and instance.name:
|
|
55
|
+
name = f"{to_wrap.get('span_name')}.{instance.name.lower()}"
|
|
56
|
+
elif to_wrap.get("span_name"):
|
|
57
|
+
name = to_wrap.get("span_name")
|
|
58
|
+
else:
|
|
59
|
+
name = get_fully_qualified_class_name(instance)
|
|
60
|
+
|
|
61
|
+
handler.validate(to_wrap, wrapped, instance, args, kwargs)
|
|
62
|
+
handler.set_context_properties(to_wrap, wrapped, instance, args, kwargs)
|
|
63
|
+
with tracer.start_as_current_span(name) as span:
|
|
64
|
+
handler.pre_task_processing(to_wrap, wrapped, instance, args, span)
|
|
65
|
+
return_value = wrapped(*args, **kwargs)
|
|
66
|
+
handler.hydrate_span(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
67
|
+
handler.post_task_processing(to_wrap, wrapped, instance, args, kwargs, return_value, span)
|
|
68
|
+
|
|
69
|
+
return return_value
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# pylint: disable=too-few-public-methods
|
|
2
|
+
from monocle_apptrace.instrumentation.common.wrapper import task_wrapper
|
|
3
|
+
from monocle_apptrace.instrumentation.metamodel.botocore.methods import BOTOCORE_METHODS
|
|
4
|
+
from monocle_apptrace.instrumentation.metamodel.langchain.methods import (
|
|
5
|
+
LANGCHAIN_METHODS,
|
|
6
|
+
)
|
|
7
|
+
from monocle_apptrace.instrumentation.metamodel.llamaindex.methods import (LLAMAINDEX_METHODS, )
|
|
8
|
+
from monocle_apptrace.instrumentation.metamodel.haystack.methods import (HAYSTACK_METHODS, )
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class WrapperMethod:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
package: str,
|
|
15
|
+
object_name: str,
|
|
16
|
+
method: str,
|
|
17
|
+
span_name: str = None,
|
|
18
|
+
output_processor : str = None,
|
|
19
|
+
wrapper_method = task_wrapper,
|
|
20
|
+
span_handler = 'default'
|
|
21
|
+
):
|
|
22
|
+
self.package = package
|
|
23
|
+
self.object = object_name
|
|
24
|
+
self.method = method
|
|
25
|
+
self.span_name = span_name
|
|
26
|
+
self.output_processor=output_processor
|
|
27
|
+
self.span_handler = span_handler
|
|
28
|
+
|
|
29
|
+
self.wrapper_method = wrapper_method
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> dict:
|
|
32
|
+
# Create a dictionary representation of the instance
|
|
33
|
+
instance_dict = {
|
|
34
|
+
'package': self.package,
|
|
35
|
+
'object': self.object,
|
|
36
|
+
'method': self.method,
|
|
37
|
+
'span_name': self.span_name,
|
|
38
|
+
'output_processor': self.output_processor,
|
|
39
|
+
'wrapper_method': self.wrapper_method,
|
|
40
|
+
'span_handler': self.span_handler
|
|
41
|
+
}
|
|
42
|
+
return instance_dict
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
DEFAULT_METHODS_LIST = LANGCHAIN_METHODS + LLAMAINDEX_METHODS + HAYSTACK_METHODS + BOTOCORE_METHODS
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
|
|
85
|
+
def botocore_processor(tracer, to_wrap, wrapped, instance, args, kwargs,return_value):
|
|
86
|
+
service_name = kwargs.get("service_name")
|
|
87
|
+
service_method_mapping = {
|
|
88
|
+
"sagemaker-runtime": "invoke_endpoint",
|
|
89
|
+
"bedrock-runtime": "converse",
|
|
90
|
+
}
|
|
91
|
+
if service_name in service_method_mapping:
|
|
92
|
+
method_name = service_method_mapping[service_name]
|
|
93
|
+
original_method = getattr(return_value, method_name, None)
|
|
94
|
+
|
|
95
|
+
if original_method:
|
|
96
|
+
instrumented_method = _instrumented_endpoint_invoke(
|
|
97
|
+
to_wrap, wrapped,return_value, original_method, tracer, service_name
|
|
98
|
+
)
|
|
99
|
+
setattr(return_value, method_name, instrumented_method)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _instrumented_endpoint_invoke(to_wrap,wrapped, instance, fn, tracer,service_name):
|
|
103
|
+
@wraps(fn)
|
|
104
|
+
def with_instrumentation(*args, **kwargs):
|
|
105
|
+
span_name="botocore-"+service_name+"-invoke-endpoint"
|
|
106
|
+
handler = SpanHandler()
|
|
107
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
108
|
+
response = fn(*args, **kwargs)
|
|
109
|
+
handler.hydrate_span(to_wrap, span=span,wrapped=wrapped, instance=instance,args=args, kwargs=kwargs, result=response)
|
|
110
|
+
return response
|
|
111
|
+
|
|
112
|
+
return with_instrumentation
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def update_span_from_llm_response(response, instance):
|
|
116
|
+
meta_dict = {}
|
|
117
|
+
if response is not None and isinstance(response, dict) and "usage" in response:
|
|
118
|
+
token_usage = response["usage"]
|
|
119
|
+
|
|
120
|
+
if token_usage is not None:
|
|
121
|
+
temperature = instance.__dict__.get("temperature", None)
|
|
122
|
+
meta_dict.update({"temperature": temperature})
|
|
123
|
+
meta_dict.update({"completion_tokens": resolve_from_alias(token_usage,["completion_tokens","output_tokens","outputTokens"])})
|
|
124
|
+
meta_dict.update({"prompt_tokens": resolve_from_alias(token_usage,["prompt_tokens","input_tokens","inputTokens"])})
|
|
125
|
+
meta_dict.update({"total_tokens": resolve_from_alias(token_usage,["total_tokens","totalTokens"])})
|
|
126
|
+
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
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from monocle_apptrace.instrumentation.common.wrapper import task_wrapper
|
|
2
|
+
from monocle_apptrace.instrumentation.metamodel.botocore.entities.inference import (
|
|
3
|
+
INFERENCE,
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
BOTOCORE_METHODS = [{
|
|
7
|
+
|
|
8
|
+
"package": "botocore.client",
|
|
9
|
+
"object": "ClientCreator",
|
|
10
|
+
"method": "create_client",
|
|
11
|
+
"wrapper_method": task_wrapper,
|
|
12
|
+
"skip_span": True,
|
|
13
|
+
"output_processor": INFERENCE
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
]
|
|
File without changes
|