monocle-apptrace 0.1.1__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of monocle-apptrace might be problematic. Click here for more details.

Files changed (37) hide show
  1. monocle_apptrace/exporters/aws/s3_exporter.py +158 -0
  2. monocle_apptrace/exporters/azure/blob_exporter.py +128 -0
  3. monocle_apptrace/exporters/base_exporter.py +47 -0
  4. monocle_apptrace/exporters/exporter_processor.py +19 -0
  5. monocle_apptrace/exporters/monocle_exporters.py +27 -0
  6. monocle_apptrace/exporters/okahu/okahu_exporter.py +115 -0
  7. monocle_apptrace/haystack/__init__.py +4 -4
  8. monocle_apptrace/haystack/wrap_pipeline.py +3 -2
  9. monocle_apptrace/instrumentor.py +12 -15
  10. monocle_apptrace/langchain/__init__.py +6 -3
  11. monocle_apptrace/llamaindex/__init__.py +8 -7
  12. monocle_apptrace/metamodel/entities/README.md +33 -10
  13. monocle_apptrace/metamodel/entities/app_hosting_types.json +29 -0
  14. monocle_apptrace/metamodel/entities/entities.json +49 -0
  15. monocle_apptrace/metamodel/entities/inference_types.json +33 -0
  16. monocle_apptrace/metamodel/entities/model_types.json +41 -0
  17. monocle_apptrace/metamodel/entities/vector_store_types.json +25 -0
  18. monocle_apptrace/metamodel/entities/workflow_types.json +22 -0
  19. monocle_apptrace/metamodel/maps/attributes/inference/langchain_entities.json +35 -0
  20. monocle_apptrace/metamodel/maps/attributes/inference/llamaindex_entities.json +35 -0
  21. monocle_apptrace/metamodel/maps/attributes/retrieval/langchain_entities.json +27 -0
  22. monocle_apptrace/metamodel/maps/attributes/retrieval/llamaindex_entities.json +27 -0
  23. monocle_apptrace/metamodel/maps/{lang_chain_methods.json → langchain_methods.json} +31 -8
  24. monocle_apptrace/metamodel/maps/{llama_index_methods.json → llamaindex_methods.json} +12 -8
  25. monocle_apptrace/metamodel/spans/span_example.json +1 -1
  26. monocle_apptrace/metamodel/spans/span_types.json +16 -0
  27. monocle_apptrace/utils.py +90 -11
  28. monocle_apptrace/wrap_common.py +228 -122
  29. monocle_apptrace/wrapper.py +3 -1
  30. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.2.0.dist-info}/METADATA +5 -1
  31. monocle_apptrace-0.2.0.dist-info/RECORD +44 -0
  32. monocle_apptrace/metamodel/entities/entity_types.json +0 -157
  33. monocle_apptrace/metamodel/entities/entity_types.py +0 -51
  34. monocle_apptrace-0.1.1.dist-info/RECORD +0 -29
  35. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.2.0.dist-info}/WHEEL +0 -0
  36. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.2.0.dist-info}/licenses/LICENSE +0 -0
  37. {monocle_apptrace-0.1.1.dist-info → monocle_apptrace-0.2.0.dist-info}/licenses/NOTICE +0 -0
monocle_apptrace/utils.py CHANGED
@@ -3,7 +3,10 @@ import json
3
3
  from importlib import import_module
4
4
  import os
5
5
  from opentelemetry.trace import Span
6
+ from opentelemetry.context import attach, set_value, get_value
6
7
  from monocle_apptrace.constants import azure_service_map, aws_service_map
8
+ from json.decoder import JSONDecodeError
9
+ logger = logging.getLogger(__name__)
7
10
 
8
11
  embedding_model_context = {}
9
12
 
@@ -21,12 +24,14 @@ def dont_throw(func):
21
24
  """
22
25
  # Obtain a logger specific to the function's module
23
26
  logger = logging.getLogger(func.__module__)
27
+
24
28
  # pylint: disable=inconsistent-return-statements
25
29
  def wrapper(*args, **kwargs):
26
30
  try:
27
31
  return func(*args, **kwargs)
28
32
  except Exception as ex:
29
33
  logger.warning("Failed to execute %s, error: %s", func.__name__, str(ex))
34
+
30
35
  return wrapper
31
36
 
32
37
  def with_tracer_wrapper(func):
@@ -48,33 +53,79 @@ def resolve_from_alias(my_map, alias):
48
53
  return my_map[i]
49
54
  return None
50
55
 
51
- def load_wrapper_from_config(config_file_path: str, module_name: str = None):
52
- wrapper_methods = []
53
- with open(config_file_path, encoding='UTF-8') as config_file:
56
+ def load_output_processor(wrapper_method, attributes_config_base_path):
57
+ """Load the output processor from a file if the file path is provided and valid."""
58
+ logger = logging.getLogger()
59
+ output_processor_file_path = wrapper_method["output_processor"][0]
60
+ logger.info(f'Output processor file path is: {output_processor_file_path}')
61
+
62
+ if isinstance(output_processor_file_path, str) and output_processor_file_path: # Combined condition
63
+ if not attributes_config_base_path:
64
+ absolute_file_path = os.path.abspath(output_processor_file_path)
65
+ else:
66
+ absolute_file_path = os.path.join(attributes_config_base_path, output_processor_file_path)
67
+
68
+ logger.info(f'Absolute file path is: {absolute_file_path}')
69
+ try:
70
+ with open(absolute_file_path, encoding='UTF-8') as op_file:
71
+ wrapper_method["output_processor"] = json.load(op_file)
72
+ logger.info('Output processor loaded successfully.')
73
+ except FileNotFoundError:
74
+ logger.error(f"Error: File not found at {absolute_file_path}.")
75
+ except JSONDecodeError:
76
+ logger.error(f"Error: Invalid JSON content in the file {absolute_file_path}.")
77
+ except Exception as e:
78
+ logger.error(f"Error: An unexpected error occurred: {e}")
79
+ else:
80
+ logger.error("Invalid or missing output processor file path.")
81
+
82
+ def get_wrapper_methods_config(
83
+ wrapper_methods_config_path: str,
84
+ attributes_config_base_path: str = None
85
+ ):
86
+ parent_dir = os.path.dirname(os.path.join(os.path.dirname(__file__), '..'))
87
+ wrapper_methods_config = load_wrapper_methods_config_from_file(
88
+ wrapper_methods_config_path=os.path.join(parent_dir, wrapper_methods_config_path))
89
+ process_wrapper_method_config(
90
+ wrapper_methods_config=wrapper_methods_config,
91
+ attributes_config_base_path=attributes_config_base_path)
92
+ return wrapper_methods_config
93
+
94
+ def load_wrapper_methods_config_from_file(
95
+ wrapper_methods_config_path: str):
96
+ json_data = {}
97
+
98
+ with open(wrapper_methods_config_path, encoding='UTF-8') as config_file:
54
99
  json_data = json.load(config_file)
55
- wrapper_methods = json_data["wrapper_methods"]
56
- for wrapper_method in wrapper_methods:
100
+
101
+ return json_data["wrapper_methods"]
102
+
103
+ def process_wrapper_method_config(
104
+ wrapper_methods_config: str,
105
+ attributes_config_base_path: str = ""):
106
+ for wrapper_method in wrapper_methods_config:
107
+ if "wrapper_package" in wrapper_method and "wrapper_method" in wrapper_method:
57
108
  wrapper_method["wrapper"] = get_wrapper_method(
58
109
  wrapper_method["wrapper_package"], wrapper_method["wrapper_method"])
59
- if "span_name_getter_method" in wrapper_method :
110
+ if "span_name_getter_method" in wrapper_method:
60
111
  wrapper_method["span_name_getter"] = get_wrapper_method(
61
112
  wrapper_method["span_name_getter_package"],
62
113
  wrapper_method["span_name_getter_method"])
63
- return wrapper_methods
114
+ if "output_processor" in wrapper_method and wrapper_method["output_processor"]:
115
+ load_output_processor(wrapper_method, attributes_config_base_path)
64
116
 
65
117
  def get_wrapper_method(package_name: str, method_name: str):
66
118
  wrapper_module = import_module("monocle_apptrace." + package_name)
67
119
  return getattr(wrapper_module, method_name)
68
120
 
69
121
  def update_span_with_infra_name(span: Span, span_key: str):
70
- for key,val in azure_service_map.items():
122
+ for key, val in azure_service_map.items():
71
123
  if key in os.environ:
72
124
  span.set_attribute(span_key, val)
73
- for key,val in aws_service_map.items():
125
+ for key, val in aws_service_map.items():
74
126
  if key in os.environ:
75
127
  span.set_attribute(span_key, val)
76
128
 
77
-
78
129
  def set_embedding_model(model_name: str):
79
130
  """
80
131
  Sets the embedding model in the global context.
@@ -83,7 +134,6 @@ def set_embedding_model(model_name: str):
83
134
  """
84
135
  embedding_model_context['embedding_model'] = model_name
85
136
 
86
-
87
137
  def get_embedding_model() -> str:
88
138
  """
89
139
  Retrieves the embedding model from the global context.
@@ -91,3 +141,32 @@ def get_embedding_model() -> str:
91
141
  @return: The name of the embedding model, or 'unknown' if not set
92
142
  """
93
143
  return embedding_model_context.get('embedding_model', 'unknown')
144
+
145
+ def set_attribute(key: str, value: str):
146
+ """
147
+ Set a value in the global context for a given key.
148
+
149
+ Args:
150
+ key: The key for the context value to set.
151
+ value: The value to set for the given key.
152
+ """
153
+ attach(set_value(key, value))
154
+
155
+ def get_attribute(key: str) -> str:
156
+ """
157
+ Retrieve a value from the global context for a given key.
158
+
159
+ Args:
160
+ key: The key for the context value to retrieve.
161
+
162
+ Returns:
163
+ The value associated with the given key.
164
+ """
165
+ return get_value(key)
166
+
167
+ def get_workflow_name(span: Span) -> str:
168
+ try:
169
+ return get_value("workflow_name") or span.resource.attributes.get("service.name")
170
+ except Exception as e:
171
+ logger.exception(f"Error getting workflow name: {e}")
172
+ return None
@@ -1,27 +1,28 @@
1
- #pylint: disable=protected-access
1
+ # pylint: disable=protected-access
2
2
  import logging
3
3
  import os
4
+ import inspect
4
5
  from urllib.parse import urlparse
5
-
6
6
  from opentelemetry.trace import Span, Tracer
7
- from monocle_apptrace.utils import resolve_from_alias, update_span_with_infra_name, with_tracer_wrapper, get_embedding_model
8
-
7
+ from monocle_apptrace.utils import resolve_from_alias, update_span_with_infra_name, with_tracer_wrapper, get_embedding_model, get_attribute, get_workflow_name
8
+ from monocle_apptrace.utils import set_attribute
9
+ from opentelemetry.context import get_value, attach, set_value
9
10
  logger = logging.getLogger(__name__)
10
11
  WORKFLOW_TYPE_KEY = "workflow_type"
11
- CONTEXT_INPUT_KEY = "context_input"
12
- CONTEXT_OUTPUT_KEY = "context_output"
13
- PROMPT_INPUT_KEY = "input"
14
- PROMPT_OUTPUT_KEY = "output"
12
+ DATA_INPUT_KEY = "data.input"
13
+ DATA_OUTPUT_KEY = "data.output"
14
+ PROMPT_INPUT_KEY = "data.input"
15
+ PROMPT_OUTPUT_KEY = "data.output"
15
16
  QUERY = "question"
16
17
  RESPONSE = "response"
17
- TAGS = "tags"
18
18
  SESSION_PROPERTIES_KEY = "session"
19
19
  INFRA_SERVICE_KEY = "infra_service_name"
20
+
20
21
  TYPE = "type"
21
22
  PROVIDER = "provider_name"
22
23
  EMBEDDING_MODEL = "embedding_model"
23
24
  VECTOR_STORE = 'vector_store'
24
-
25
+ META_DATA = 'metadata'
25
26
 
26
27
  WORKFLOW_TYPE_MAP = {
27
28
  "llama_index": "workflow.llamaindex",
@@ -29,24 +30,53 @@ WORKFLOW_TYPE_MAP = {
29
30
  "haystack": "workflow.haystack"
30
31
  }
31
32
 
33
+ def get_embedding_model_for_vectorstore(instance):
34
+ # Handle Langchain or other frameworks where vectorstore exists
35
+ if hasattr(instance, 'vectorstore'):
36
+ vectorstore_dict = instance.vectorstore.__dict__
37
+
38
+ # Use inspect to check if the embedding function is from Sagemaker
39
+ if 'embedding_func' in vectorstore_dict:
40
+ embedding_func = vectorstore_dict['embedding_func']
41
+ class_name = embedding_func.__class__.__name__
42
+ file_location = inspect.getfile(embedding_func.__class__)
43
+
44
+ # Check if the class is SagemakerEndpointEmbeddings
45
+ if class_name == 'SagemakerEndpointEmbeddings' and 'langchain_community' in file_location:
46
+ # Set embedding_model as endpoint_name if it's Sagemaker
47
+ if hasattr(embedding_func, 'endpoint_name'):
48
+ return embedding_func.endpoint_name
49
+
50
+ # Default to the regular embedding model if not Sagemaker
51
+ return instance.vectorstore.embeddings.model
52
+
53
+ # Handle llama_index where _embed_model is present
54
+ if hasattr(instance, '_embed_model') and hasattr(instance._embed_model, 'model_name'):
55
+ return instance._embed_model.model_name
56
+
57
+ # Fallback if no specific model is found
58
+ return "Unknown Embedding Model"
59
+
60
+
32
61
  framework_vector_store_mapping = {
33
62
  'langchain_core.retrievers': lambda instance: {
34
- 'provider': instance.tags[0],
35
- 'embedding_model': instance.tags[1],
63
+ 'provider': type(instance.vectorstore).__name__,
64
+ 'embedding_model': get_embedding_model_for_vectorstore(instance),
36
65
  'type': VECTOR_STORE,
37
66
  },
38
67
  'llama_index.core.indices.base_retriever': lambda instance: {
39
68
  'provider': type(instance._vector_store).__name__,
40
- 'embedding_model': instance._embed_model.model_name,
69
+ 'embedding_model': get_embedding_model_for_vectorstore(instance),
41
70
  'type': VECTOR_STORE,
42
71
  },
43
- 'haystack.components.retrievers': lambda instance: {
72
+ 'haystack.components.retrievers.in_memory': lambda instance: {
44
73
  'provider': instance.__dict__.get("document_store").__class__.__name__,
45
74
  'embedding_model': get_embedding_model(),
46
75
  'type': VECTOR_STORE,
47
76
  },
48
77
  }
49
78
 
79
+
50
80
  @with_tracer_wrapper
51
81
  def task_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
52
82
  """Instruments and calls every function defined in TO_WRAP."""
@@ -63,36 +93,83 @@ def task_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
63
93
  name = f"langchain.task.{instance.__class__.__name__}"
64
94
 
65
95
  with tracer.start_as_current_span(name) as span:
96
+ process_span(to_wrap, span, instance, args)
66
97
  pre_task_processing(to_wrap, instance, args, span)
67
98
  return_value = wrapped(*args, **kwargs)
68
99
  post_task_processing(to_wrap, span, return_value)
69
100
 
70
101
  return return_value
71
102
 
72
- def post_task_processing(to_wrap, span, return_value):
73
- update_span_with_context_output(to_wrap=to_wrap, return_value=return_value, span=span)
74
103
 
104
+ def process_span(to_wrap, span, instance, args):
105
+ # Check if the output_processor is a valid JSON (in Python, that means it's a dictionary)
106
+ span_index = 1
75
107
  if is_root_span(span):
76
- workflow_name = span.resource.attributes.get("service.name")
77
- span.set_attribute("workflow_name",workflow_name)
78
- update_span_with_prompt_output(to_wrap=to_wrap, wrapped_args=return_value, span=span)
79
- update_workflow_type(to_wrap, span)
108
+ workflow_name = get_workflow_name(span)
109
+ if workflow_name:
110
+ span.set_attribute(f"entity.{span_index}.name", workflow_name)
111
+ # workflow type
112
+ package_name = to_wrap.get('package')
113
+ for (package, workflow_type) in WORKFLOW_TYPE_MAP.items():
114
+ if (package_name is not None and package in package_name):
115
+ span.set_attribute(f"entity.{span_index}.type", workflow_type)
116
+ span_index += 1
117
+ if 'output_processor' in to_wrap:
118
+ output_processor=to_wrap['output_processor']
119
+ if isinstance(output_processor, dict) and len(output_processor) > 0:
120
+ if 'type' in output_processor:
121
+ span.set_attribute("span.type", output_processor['type'])
122
+ else:
123
+ logger.warning("type of span not found or incorrect written in entity json")
124
+ count = 0
125
+ if 'attributes' in output_processor:
126
+ count = len(output_processor["attributes"])
127
+ span.set_attribute("entity.count", count)
128
+ span_index = 1
129
+ for processors in output_processor["attributes"]:
130
+ for processor in processors:
131
+ attribute = processor.get('attribute')
132
+ accessor = processor.get('accessor')
133
+
134
+ if attribute and accessor:
135
+ attribute_name = f"entity.{span_index}.{attribute}"
136
+ try:
137
+ result = eval(accessor)(instance, args)
138
+ if result and isinstance(result, str):
139
+ span.set_attribute(attribute_name, result)
140
+ except Exception as e:
141
+ logger.error(f"Error processing accessor: {e}")
142
+ else:
143
+ logger.warning(f"{' and '.join([key for key in ['attribute', 'accessor'] if not processor.get(key)])} not found or incorrect in entity JSON")
144
+ span_index += 1
145
+ else:
146
+ logger.warning("attributes not found or incorrect written in entity json")
147
+ span.set_attribute("span.count", count)
80
148
 
81
- def pre_task_processing(to_wrap, instance, args, span):
82
- if is_root_span(span):
83
- update_span_with_prompt_input(to_wrap=to_wrap, wrapped_args=args, span=span)
149
+ else:
150
+ logger.warning("empty or entities json is not in correct format")
84
151
 
85
- update_span_with_infra_name(span, INFRA_SERVICE_KEY)
86
152
 
87
- #capture the tags attribute of the instance if present, else ignore
153
+ def post_task_processing(to_wrap, span, return_value):
88
154
  try:
89
- update_tags(instance, span)
90
- update_vectorstore_attributes(to_wrap, instance, span)
91
- except AttributeError:
92
- pass
93
- update_span_with_context_input(to_wrap=to_wrap, wrapped_args=args, span=span)
155
+ update_span_with_context_output(to_wrap=to_wrap, return_value=return_value, span=span)
156
+
157
+ if is_root_span(span):
158
+ update_span_with_prompt_output(to_wrap=to_wrap, wrapped_args=return_value, span=span)
159
+ except:
160
+ logger.exception("exception in post_task_processing")
94
161
 
95
162
 
163
+ def pre_task_processing(to_wrap, instance, args, span):
164
+ try:
165
+ if is_root_span(span):
166
+ update_span_with_prompt_input(to_wrap=to_wrap, wrapped_args=args, span=span)
167
+ update_span_with_infra_name(span, INFRA_SERVICE_KEY)
168
+
169
+ update_span_with_context_input(to_wrap=to_wrap, wrapped_args=args, span=span)
170
+ except:
171
+ logger.exception("exception in pre_task_processing")
172
+
96
173
 
97
174
  @with_tracer_wrapper
98
175
  async def atask_wrapper(tracer, to_wrap, wrapped, instance, args, kwargs):
@@ -109,12 +186,14 @@ async def atask_wrapper(tracer, to_wrap, wrapped, instance, args, kwargs):
109
186
  else:
110
187
  name = f"langchain.task.{instance.__class__.__name__}"
111
188
  with tracer.start_as_current_span(name) as span:
189
+ process_span(to_wrap, span, instance, args)
112
190
  pre_task_processing(to_wrap, instance, args, span)
113
191
  return_value = await wrapped(*args, **kwargs)
114
192
  post_task_processing(to_wrap, span, return_value)
115
193
 
116
194
  return return_value
117
195
 
196
+
118
197
  @with_tracer_wrapper
119
198
  async def allm_wrapper(tracer, to_wrap, wrapped, instance, args, kwargs):
120
199
  # Some Langchain objects are wrapped elsewhere, so we ignore them here
@@ -131,16 +210,24 @@ async def allm_wrapper(tracer, to_wrap, wrapped, instance, args, kwargs):
131
210
  else:
132
211
  name = f"langchain.task.{instance.__class__.__name__}"
133
212
  with tracer.start_as_current_span(name) as span:
134
- update_llm_endpoint(curr_span= span, instance=instance)
213
+ if 'haystack.components.retrievers' in to_wrap['package'] and 'haystack.retriever' in span.name:
214
+ input_arg_text = get_attribute(DATA_INPUT_KEY)
215
+ span.add_event(DATA_INPUT_KEY, {QUERY: input_arg_text})
216
+ provider_name, inference_endpoint = get_provider_name(instance)
217
+ instance_args = {"provider_name": provider_name, "inference_endpoint": inference_endpoint}
218
+
219
+ process_span(to_wrap, span, instance, instance_args)
135
220
 
136
221
  return_value = await wrapped(*args, **kwargs)
137
- update_span_from_llm_response(response = return_value, span = span)
222
+ if 'haystack.components.retrievers' in to_wrap['package'] and 'haystack.retriever' in span.name:
223
+ update_span_with_context_output(to_wrap=to_wrap, return_value=return_value, span=span)
224
+ update_span_from_llm_response(response=return_value, span=span, instance=instance)
138
225
 
139
226
  return return_value
140
227
 
228
+
141
229
  @with_tracer_wrapper
142
230
  def llm_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
143
-
144
231
  # Some Langchain objects are wrapped elsewhere, so we ignore them here
145
232
  if instance.__class__.__name__ in ("AgentExecutor"):
146
233
  return wrapped(*args, **kwargs)
@@ -154,87 +241,121 @@ def llm_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
154
241
  name = to_wrap.get("span_name")
155
242
  else:
156
243
  name = f"langchain.task.{instance.__class__.__name__}"
244
+
157
245
  with tracer.start_as_current_span(name) as span:
158
246
  if 'haystack.components.retrievers' in to_wrap['package'] and 'haystack.retriever' in span.name:
159
- update_vectorstore_attributes(to_wrap, instance, span)
160
- update_llm_endpoint(curr_span= span, instance=instance)
247
+ input_arg_text = get_attribute(DATA_INPUT_KEY)
248
+ span.add_event(DATA_INPUT_KEY, {QUERY: input_arg_text})
249
+ provider_name, inference_endpoint = get_provider_name(instance)
250
+ instance_args = {"provider_name": provider_name, "inference_endpoint": inference_endpoint}
251
+
252
+ process_span(to_wrap, span, instance, instance_args)
161
253
 
162
254
  return_value = wrapped(*args, **kwargs)
163
- update_span_from_llm_response(response = return_value, span = span)
255
+ if 'haystack.components.retrievers' in to_wrap['package'] and 'haystack.retriever' in span.name:
256
+ update_span_with_context_output(to_wrap=to_wrap, return_value=return_value, span=span)
257
+ update_span_from_llm_response(response=return_value, span=span, instance=instance)
164
258
 
165
259
  return return_value
166
260
 
261
+
167
262
  def update_llm_endpoint(curr_span: Span, instance):
263
+ # Lambda to set attributes if values are not None
264
+ __set_span_attribute_if_not_none = lambda span, **kwargs: [
265
+ span.set_attribute(k, v) for k, v in kwargs.items() if v is not None
266
+ ]
267
+
168
268
  triton_llm_endpoint = os.environ.get("TRITON_LLM_ENDPOINT")
169
269
  if triton_llm_endpoint is not None and len(triton_llm_endpoint) > 0:
170
270
  curr_span.set_attribute("server_url", triton_llm_endpoint)
171
271
  else:
172
- if 'temperature' in instance.__dict__:
173
- temp_val = instance.__dict__.get("temperature")
174
- curr_span.set_attribute("temperature", temp_val)
175
- # handling for model name
176
- model_name = resolve_from_alias(instance.__dict__ , ["model","model_name"])
177
- curr_span.set_attribute("model_name", model_name)
178
- set_provider_name(curr_span, instance)
179
- # handling AzureOpenAI deployment
180
- deployment_name = resolve_from_alias(instance.__dict__ , [ "engine", "azure_deployment",
181
- "deployment_name", "deployment_id", "deployment"])
182
- curr_span.set_attribute("az_openai_deployment", deployment_name)
183
- # handling the inference endpoint
184
- inference_ep = resolve_from_alias(instance.__dict__,["azure_endpoint","api_base"])
185
- curr_span.set_attribute("inference_endpoint",inference_ep)
186
-
187
- def set_provider_name(curr_span, instance):
272
+ # Get temperature if present
273
+ temp_val = instance.__dict__.get("temperature")
274
+
275
+ # Resolve values for model name, deployment, and inference endpoint
276
+ model_name = resolve_from_alias(instance.__dict__, ["model", "model_name"])
277
+ deployment_name = resolve_from_alias(instance.__dict__,
278
+ ["engine", "azure_deployment", "deployment_name", "deployment_id",
279
+ "deployment"])
280
+ inference_ep = resolve_from_alias(instance.__dict__, ["azure_endpoint", "api_base"])
281
+
282
+ # Use the lambda to set attributes conditionally
283
+ __set_span_attribute_if_not_none(
284
+ curr_span,
285
+ temperature=temp_val,
286
+ model_name=model_name,
287
+ az_openai_deployment=deployment_name,
288
+ inference_endpoint=inference_ep
289
+ )
290
+
291
+
292
+ def get_provider_name(instance):
188
293
  provider_url = ""
189
-
190
- try :
191
- if isinstance(instance.client._client.base_url.host, str) :
192
- provider_url = instance. client._client.base_url.host
294
+ inference_endpoint = ""
295
+ try:
296
+ if isinstance(instance.client._client.base_url.host, str):
297
+ provider_url = instance.client._client.base_url.host
298
+ if isinstance(instance.client._client.base_url, str):
299
+ inference_endpoint = instance.client._client.base_url
300
+ else:
301
+ inference_endpoint = str(instance.client._client.base_url)
193
302
  except:
194
303
  pass
195
304
 
196
- try :
305
+ try:
197
306
  if isinstance(instance.api_base, str):
198
307
  provider_url = instance.api_base
199
308
  except:
200
309
  pass
201
310
 
202
- try :
311
+ try:
203
312
  if len(provider_url) > 0:
204
313
  parsed_provider_url = urlparse(provider_url)
205
- curr_span.set_attribute("provider_name", parsed_provider_url.hostname or provider_url)
206
314
  except:
207
315
  pass
316
+ return parsed_provider_url.hostname or provider_url,inference_endpoint
317
+
208
318
 
209
319
  def is_root_span(curr_span: Span) -> bool:
210
320
  return curr_span.parent is None
211
321
 
322
+
212
323
  def get_input_from_args(chain_args):
213
324
  if len(chain_args) > 0 and isinstance(chain_args[0], str):
214
325
  return chain_args[0]
215
326
  return ""
216
327
 
217
- def update_span_from_llm_response(response, span: Span):
328
+
329
+ def update_span_from_llm_response(response, span: Span, instance):
218
330
  # extract token uasge from langchain openai
219
331
  if (response is not None and hasattr(response, "response_metadata")):
220
332
  response_metadata = response.response_metadata
221
333
  token_usage = response_metadata.get("token_usage")
334
+ meta_dict = {}
222
335
  if token_usage is not None:
223
- span.set_attribute("completion_tokens", token_usage.get("completion_tokens"))
224
- span.set_attribute("prompt_tokens", token_usage.get("prompt_tokens"))
225
- span.set_attribute("total_tokens", token_usage.get("total_tokens"))
336
+ temperature = instance.__dict__.get("temperature", None)
337
+ meta_dict.update({"temperature": temperature})
338
+ meta_dict.update({"completion_tokens": token_usage.get("completion_tokens")})
339
+ meta_dict.update({"prompt_tokens": token_usage.get("prompt_tokens")})
340
+ meta_dict.update({"total_tokens": token_usage.get("total_tokens")})
341
+ span.add_event(META_DATA, meta_dict)
226
342
  # extract token usage from llamaindex openai
227
- if(response is not None and hasattr(response, "raw")):
343
+ if (response is not None and hasattr(response, "raw")):
228
344
  try:
345
+ meta_dict = {}
229
346
  if response.raw is not None:
230
- token_usage = response.raw.get("usage") if isinstance(response.raw, dict) else getattr(response.raw, "usage", None)
347
+ token_usage = response.raw.get("usage") if isinstance(response.raw, dict) else getattr(response.raw,
348
+ "usage", None)
231
349
  if token_usage is not None:
350
+ temperature = instance.__dict__.get("temperature", None)
351
+ meta_dict.update({"temperature": temperature})
232
352
  if getattr(token_usage, "completion_tokens", None):
233
- span.set_attribute("completion_tokens", getattr(token_usage, "completion_tokens"))
353
+ meta_dict.update({"completion_tokens": getattr(token_usage, "completion_tokens")})
234
354
  if getattr(token_usage, "prompt_tokens", None):
235
- span.set_attribute("prompt_tokens", getattr(token_usage, "prompt_tokens"))
355
+ meta_dict.update({"prompt_tokens": getattr(token_usage, "prompt_tokens")})
236
356
  if getattr(token_usage, "total_tokens", None):
237
- span.set_attribute("total_tokens", getattr(token_usage, "total_tokens"))
357
+ meta_dict.update({"total_tokens": getattr(token_usage, "total_tokens")})
358
+ span.add_event(META_DATA, meta_dict)
238
359
  except AttributeError:
239
360
  token_usage = None
240
361
 
@@ -243,69 +364,54 @@ def update_workflow_type(to_wrap, span: Span):
243
364
  package_name = to_wrap.get('package')
244
365
 
245
366
  for (package, workflow_type) in WORKFLOW_TYPE_MAP.items():
246
- if(package_name is not None and package in package_name):
367
+ if (package_name is not None and package in package_name):
247
368
  span.set_attribute(WORKFLOW_TYPE_KEY, workflow_type)
248
369
 
249
- def update_span_with_context_input(to_wrap, wrapped_args ,span: Span):
250
- package_name: str = to_wrap.get('package')
251
- if "langchain_core.retrievers" in package_name:
252
- input_arg_text = wrapped_args[0]
253
- span.add_event(CONTEXT_INPUT_KEY, {QUERY:input_arg_text})
254
- if "llama_index.core.indices.base_retriever" in package_name:
255
- input_arg_text = wrapped_args[0].query_str
256
- span.add_event(CONTEXT_INPUT_KEY, {QUERY:input_arg_text})
257
370
 
258
- def update_span_with_context_output(to_wrap, return_value ,span: Span):
371
+ def update_span_with_context_input(to_wrap, wrapped_args, span: Span):
259
372
  package_name: str = to_wrap.get('package')
260
- if "llama_index.core.indices.base_retriever" in package_name:
261
- output_arg_text = return_value[0].text
262
- span.add_event(CONTEXT_OUTPUT_KEY, {RESPONSE:output_arg_text})
263
-
264
- def update_span_with_prompt_input(to_wrap, wrapped_args ,span: Span):
373
+ input_arg_text = ""
374
+ if "langchain_core.retrievers" in package_name and len(wrapped_args) > 0:
375
+ input_arg_text += wrapped_args[0]
376
+ if "llama_index.core.indices.base_retriever" in package_name and len(wrapped_args) > 0:
377
+ input_arg_text += wrapped_args[0].query_str
378
+ if "haystack.components.retrievers.in_memory" in package_name:
379
+ input_arg_text += get_attribute(DATA_INPUT_KEY)
380
+ if input_arg_text:
381
+ span.add_event(DATA_INPUT_KEY, {QUERY: input_arg_text})
382
+
383
+
384
+ def update_span_with_context_output(to_wrap, return_value, span: Span):
385
+ package_name: str = to_wrap.get('package')
386
+ output_arg_text = ""
387
+ if "langchain_core.retrievers" in package_name:
388
+ output_arg_text += " ".join([doc.page_content for doc in return_value if hasattr(doc, 'page_content')])
389
+ if len(output_arg_text) > 100:
390
+ output_arg_text = output_arg_text[:100] + "..."
391
+ if "llama_index.core.indices.base_retriever" in package_name and len(return_value) > 0:
392
+ output_arg_text += return_value[0].text
393
+ if "haystack.components.retrievers.in_memory" in package_name:
394
+ output_arg_text += " ".join([doc.content for doc in return_value['documents']])
395
+ if len(output_arg_text) > 100:
396
+ output_arg_text = output_arg_text[:100] + "..."
397
+ if output_arg_text:
398
+ span.add_event(DATA_OUTPUT_KEY, {RESPONSE: output_arg_text})
399
+
400
+
401
+ def update_span_with_prompt_input(to_wrap, wrapped_args, span: Span):
265
402
  input_arg_text = wrapped_args[0]
266
403
 
267
404
  if isinstance(input_arg_text, dict):
268
- span.add_event(PROMPT_INPUT_KEY,input_arg_text)
405
+ span.add_event(PROMPT_INPUT_KEY, input_arg_text)
269
406
  else:
270
- span.add_event(PROMPT_INPUT_KEY,{QUERY:input_arg_text})
407
+ span.add_event(PROMPT_INPUT_KEY, {QUERY: input_arg_text})
271
408
 
272
- def update_span_with_prompt_output(to_wrap, wrapped_args ,span: Span):
409
+
410
+ def update_span_with_prompt_output(to_wrap, wrapped_args, span: Span):
273
411
  package_name: str = to_wrap.get('package')
274
412
  if isinstance(wrapped_args, str):
275
- span.add_event(PROMPT_OUTPUT_KEY, {RESPONSE:wrapped_args})
413
+ span.add_event(PROMPT_OUTPUT_KEY, {RESPONSE: wrapped_args})
414
+ if isinstance(wrapped_args, dict):
415
+ span.add_event(PROMPT_OUTPUT_KEY, wrapped_args)
276
416
  if "llama_index.core.base.base_query_engine" in package_name:
277
- span.add_event(PROMPT_OUTPUT_KEY, {RESPONSE:wrapped_args.response})
278
-
279
- def update_tags(instance, span):
280
- try:
281
- # copy tags as is from langchain
282
- span.set_attribute(TAGS, getattr(instance, TAGS))
283
- except:
284
- pass
285
- try:
286
- # extract embed model and vector store names for llamaindex
287
- model_name = instance.retriever._embed_model.model_name
288
- vector_store_name = type(instance.retriever._vector_store).__name__
289
- span.set_attribute(TAGS, [model_name, vector_store_name])
290
- except:
291
- pass
292
-
293
-
294
- def update_vectorstore_attributes(to_wrap, instance, span):
295
- """
296
- Updates the telemetry span attributes for vector store retrieval tasks.
297
- """
298
- try:
299
- package = to_wrap.get('package')
300
- if package in framework_vector_store_mapping:
301
- attributes = framework_vector_store_mapping[package](instance)
302
- span._attributes.update({
303
- TYPE: attributes['type'],
304
- PROVIDER: attributes['provider'],
305
- EMBEDDING_MODEL: attributes['embedding_model']
306
- })
307
- else:
308
- logger.warning(f"Package '{package}' not recognized for vector store telemetry.")
309
-
310
- except Exception as e:
311
- logger.error(f"Error updating span attributes: {e}")
417
+ span.add_event(PROMPT_OUTPUT_KEY, {RESPONSE: wrapped_args.response})