monocle-apptrace 0.3.0b2__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.

Files changed (74) hide show
  1. monocle_apptrace/exporters/aws/s3_exporter.py +1 -1
  2. monocle_apptrace/exporters/aws/s3_exporter_opendal.py +126 -0
  3. monocle_apptrace/exporters/azure/blob_exporter_opendal.py +147 -0
  4. monocle_apptrace/exporters/monocle_exporters.py +38 -20
  5. monocle_apptrace/instrumentation/__init__.py +0 -0
  6. monocle_apptrace/instrumentation/common/__init__.py +0 -0
  7. monocle_apptrace/{constants.py → instrumentation/common/constants.py} +13 -0
  8. monocle_apptrace/instrumentation/common/instrumentor.py +208 -0
  9. monocle_apptrace/instrumentation/common/span_handler.py +154 -0
  10. monocle_apptrace/instrumentation/common/utils.py +171 -0
  11. monocle_apptrace/instrumentation/common/wrapper.py +69 -0
  12. monocle_apptrace/instrumentation/common/wrapper_method.py +45 -0
  13. monocle_apptrace/instrumentation/metamodel/__init__.py +0 -0
  14. monocle_apptrace/instrumentation/metamodel/botocore/__init__.py +0 -0
  15. monocle_apptrace/instrumentation/metamodel/botocore/_helper.py +126 -0
  16. monocle_apptrace/instrumentation/metamodel/botocore/entities/__init__.py +0 -0
  17. monocle_apptrace/instrumentation/metamodel/botocore/entities/inference.py +65 -0
  18. monocle_apptrace/instrumentation/metamodel/botocore/methods.py +16 -0
  19. monocle_apptrace/instrumentation/metamodel/haystack/__init__.py +0 -0
  20. monocle_apptrace/instrumentation/metamodel/haystack/_helper.py +127 -0
  21. monocle_apptrace/instrumentation/metamodel/haystack/entities/__init__.py +0 -0
  22. monocle_apptrace/instrumentation/metamodel/haystack/entities/inference.py +76 -0
  23. monocle_apptrace/instrumentation/metamodel/haystack/entities/retrieval.py +61 -0
  24. monocle_apptrace/instrumentation/metamodel/haystack/methods.py +42 -0
  25. monocle_apptrace/instrumentation/metamodel/langchain/__init__.py +0 -0
  26. monocle_apptrace/instrumentation/metamodel/langchain/_helper.py +121 -0
  27. monocle_apptrace/instrumentation/metamodel/langchain/entities/__init__.py +0 -0
  28. monocle_apptrace/instrumentation/metamodel/langchain/entities/inference.py +71 -0
  29. monocle_apptrace/instrumentation/metamodel/langchain/entities/retrieval.py +58 -0
  30. monocle_apptrace/instrumentation/metamodel/langchain/methods.py +105 -0
  31. monocle_apptrace/instrumentation/metamodel/llamaindex/__init__.py +0 -0
  32. monocle_apptrace/instrumentation/metamodel/llamaindex/_helper.py +154 -0
  33. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/__init__.py +0 -0
  34. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/inference.py +71 -0
  35. monocle_apptrace/instrumentation/metamodel/llamaindex/entities/retrieval.py +57 -0
  36. monocle_apptrace/{metamodel/maps/llamaindex_methods.json → instrumentation/metamodel/llamaindex/methods.py} +28 -31
  37. {monocle_apptrace-0.3.0b2.dist-info → monocle_apptrace-0.3.0b3.dist-info}/METADATA +14 -1
  38. monocle_apptrace-0.3.0b3.dist-info/RECORD +48 -0
  39. monocle_apptrace/botocore/__init__.py +0 -9
  40. monocle_apptrace/haystack/__init__.py +0 -9
  41. monocle_apptrace/haystack/wrap_pipeline.py +0 -63
  42. monocle_apptrace/instrumentor.py +0 -121
  43. monocle_apptrace/langchain/__init__.py +0 -9
  44. monocle_apptrace/llamaindex/__init__.py +0 -16
  45. monocle_apptrace/message_processing.py +0 -80
  46. monocle_apptrace/metamodel/README.md +0 -47
  47. monocle_apptrace/metamodel/entities/README.md +0 -77
  48. monocle_apptrace/metamodel/entities/app_hosting_types.json +0 -29
  49. monocle_apptrace/metamodel/entities/entities.json +0 -49
  50. monocle_apptrace/metamodel/entities/inference_types.json +0 -33
  51. monocle_apptrace/metamodel/entities/model_types.json +0 -41
  52. monocle_apptrace/metamodel/entities/vector_store_types.json +0 -25
  53. monocle_apptrace/metamodel/entities/workflow_types.json +0 -22
  54. monocle_apptrace/metamodel/maps/attributes/inference/botocore_entities.json +0 -27
  55. monocle_apptrace/metamodel/maps/attributes/inference/haystack_entities.json +0 -57
  56. monocle_apptrace/metamodel/maps/attributes/inference/langchain_entities.json +0 -57
  57. monocle_apptrace/metamodel/maps/attributes/inference/llamaindex_entities.json +0 -57
  58. monocle_apptrace/metamodel/maps/attributes/retrieval/haystack_entities.json +0 -31
  59. monocle_apptrace/metamodel/maps/attributes/retrieval/langchain_entities.json +0 -31
  60. monocle_apptrace/metamodel/maps/attributes/retrieval/llamaindex_entities.json +0 -31
  61. monocle_apptrace/metamodel/maps/botocore_methods.json +0 -13
  62. monocle_apptrace/metamodel/maps/haystack_methods.json +0 -45
  63. monocle_apptrace/metamodel/maps/langchain_methods.json +0 -129
  64. monocle_apptrace/metamodel/spans/README.md +0 -121
  65. monocle_apptrace/metamodel/spans/span_example.json +0 -140
  66. monocle_apptrace/metamodel/spans/span_format.json +0 -55
  67. monocle_apptrace/metamodel/spans/span_types.json +0 -16
  68. monocle_apptrace/utils.py +0 -252
  69. monocle_apptrace/wrap_common.py +0 -511
  70. monocle_apptrace/wrapper.py +0 -27
  71. monocle_apptrace-0.3.0b2.dist-info/RECORD +0 -48
  72. {monocle_apptrace-0.3.0b2.dist-info → monocle_apptrace-0.3.0b3.dist-info}/WHEEL +0 -0
  73. {monocle_apptrace-0.3.0b2.dist-info → monocle_apptrace-0.3.0b3.dist-info}/licenses/LICENSE +0 -0
  74. {monocle_apptrace-0.3.0b2.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
@@ -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
@@ -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
+ ]