monocle-apptrace 0.1.0__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 (41) hide show
  1. monocle_apptrace/README.md +50 -26
  2. monocle_apptrace/exporters/aws/s3_exporter.py +158 -0
  3. monocle_apptrace/exporters/azure/blob_exporter.py +128 -0
  4. monocle_apptrace/exporters/base_exporter.py +47 -0
  5. monocle_apptrace/exporters/exporter_processor.py +19 -0
  6. monocle_apptrace/exporters/monocle_exporters.py +27 -0
  7. monocle_apptrace/exporters/okahu/okahu_exporter.py +115 -0
  8. monocle_apptrace/haystack/__init__.py +4 -4
  9. monocle_apptrace/haystack/wrap_pipeline.py +18 -1
  10. monocle_apptrace/instrumentor.py +15 -18
  11. monocle_apptrace/langchain/__init__.py +6 -3
  12. monocle_apptrace/llamaindex/__init__.py +8 -7
  13. monocle_apptrace/metamodel/README.md +47 -0
  14. monocle_apptrace/metamodel/entities/README.md +77 -0
  15. monocle_apptrace/metamodel/entities/app_hosting_types.json +29 -0
  16. monocle_apptrace/metamodel/entities/entities.json +49 -0
  17. monocle_apptrace/metamodel/entities/inference_types.json +33 -0
  18. monocle_apptrace/metamodel/entities/model_types.json +41 -0
  19. monocle_apptrace/metamodel/entities/vector_store_types.json +25 -0
  20. monocle_apptrace/metamodel/entities/workflow_types.json +22 -0
  21. monocle_apptrace/metamodel/maps/attributes/inference/langchain_entities.json +35 -0
  22. monocle_apptrace/metamodel/maps/attributes/inference/llamaindex_entities.json +35 -0
  23. monocle_apptrace/metamodel/maps/attributes/retrieval/langchain_entities.json +27 -0
  24. monocle_apptrace/metamodel/maps/attributes/retrieval/llamaindex_entities.json +27 -0
  25. monocle_apptrace/{wrapper_config/lang_chain_methods.json → metamodel/maps/langchain_methods.json} +31 -8
  26. monocle_apptrace/{wrapper_config/llama_index_methods.json → metamodel/maps/llamaindex_methods.json} +12 -8
  27. monocle_apptrace/metamodel/spans/README.md +121 -0
  28. monocle_apptrace/metamodel/spans/span_example.json +140 -0
  29. monocle_apptrace/metamodel/spans/span_format.json +55 -0
  30. monocle_apptrace/metamodel/spans/span_types.json +16 -0
  31. monocle_apptrace/utils.py +108 -9
  32. monocle_apptrace/wrap_common.py +247 -98
  33. monocle_apptrace/wrapper.py +3 -1
  34. monocle_apptrace-0.2.0.dist-info/METADATA +115 -0
  35. monocle_apptrace-0.2.0.dist-info/RECORD +44 -0
  36. monocle_apptrace-0.1.0.dist-info/METADATA +0 -77
  37. monocle_apptrace-0.1.0.dist-info/RECORD +0 -22
  38. /monocle_apptrace/{wrapper_config → metamodel/maps}/haystack_methods.json +0 -0
  39. {monocle_apptrace-0.1.0.dist-info → monocle_apptrace-0.2.0.dist-info}/WHEEL +0 -0
  40. {monocle_apptrace-0.1.0.dist-info → monocle_apptrace-0.2.0.dist-info}/licenses/LICENSE +0 -0
  41. {monocle_apptrace-0.1.0.dist-info → monocle_apptrace-0.2.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,24 +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
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
- CONTEXT_PROPERTIES_KEY = "workflow_context_properties"
18
+ SESSION_PROPERTIES_KEY = "session"
19
19
  INFRA_SERVICE_KEY = "infra_service_name"
20
20
 
21
-
21
+ TYPE = "type"
22
+ PROVIDER = "provider_name"
23
+ EMBEDDING_MODEL = "embedding_model"
24
+ VECTOR_STORE = 'vector_store'
25
+ META_DATA = 'metadata'
22
26
 
23
27
  WORKFLOW_TYPE_MAP = {
24
28
  "llama_index": "workflow.llamaindex",
@@ -26,6 +30,53 @@ WORKFLOW_TYPE_MAP = {
26
30
  "haystack": "workflow.haystack"
27
31
  }
28
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
+
61
+ framework_vector_store_mapping = {
62
+ 'langchain_core.retrievers': lambda instance: {
63
+ 'provider': type(instance.vectorstore).__name__,
64
+ 'embedding_model': get_embedding_model_for_vectorstore(instance),
65
+ 'type': VECTOR_STORE,
66
+ },
67
+ 'llama_index.core.indices.base_retriever': lambda instance: {
68
+ 'provider': type(instance._vector_store).__name__,
69
+ 'embedding_model': get_embedding_model_for_vectorstore(instance),
70
+ 'type': VECTOR_STORE,
71
+ },
72
+ 'haystack.components.retrievers.in_memory': lambda instance: {
73
+ 'provider': instance.__dict__.get("document_store").__class__.__name__,
74
+ 'embedding_model': get_embedding_model(),
75
+ 'type': VECTOR_STORE,
76
+ },
77
+ }
78
+
79
+
29
80
  @with_tracer_wrapper
30
81
  def task_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
31
82
  """Instruments and calls every function defined in TO_WRAP."""
@@ -42,34 +93,82 @@ def task_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
42
93
  name = f"langchain.task.{instance.__class__.__name__}"
43
94
 
44
95
  with tracer.start_as_current_span(name) as span:
96
+ process_span(to_wrap, span, instance, args)
45
97
  pre_task_processing(to_wrap, instance, args, span)
46
98
  return_value = wrapped(*args, **kwargs)
47
99
  post_task_processing(to_wrap, span, return_value)
48
100
 
49
101
  return return_value
50
102
 
51
- def post_task_processing(to_wrap, span, return_value):
52
- update_span_with_context_output(to_wrap=to_wrap, return_value=return_value, span=span)
53
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
54
107
  if is_root_span(span):
55
- workflow_name = span.resource.attributes.get("service.name")
56
- span.set_attribute("workflow_name",workflow_name)
57
- update_span_with_prompt_output(to_wrap=to_wrap, wrapped_args=return_value, span=span)
58
- 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)
148
+
149
+ else:
150
+ logger.warning("empty or entities json is not in correct format")
59
151
 
60
- def pre_task_processing(to_wrap, instance, args, span):
61
- if is_root_span(span):
62
- update_span_with_prompt_input(to_wrap=to_wrap, wrapped_args=args, span=span)
63
152
 
64
- update_span_with_infra_name(span, INFRA_SERVICE_KEY)
153
+ def post_task_processing(to_wrap, span, return_value):
154
+ try:
155
+ update_span_with_context_output(to_wrap=to_wrap, return_value=return_value, span=span)
65
156
 
66
- #capture the tags attribute of the instance if present, else ignore
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")
161
+
162
+
163
+ def pre_task_processing(to_wrap, instance, args, span):
67
164
  try:
68
- update_tags(instance, span)
69
- except AttributeError:
70
- pass
71
- update_span_with_context_input(to_wrap=to_wrap, wrapped_args=args, span=span)
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)
72
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")
73
172
 
74
173
 
75
174
  @with_tracer_wrapper
@@ -87,12 +186,14 @@ async def atask_wrapper(tracer, to_wrap, wrapped, instance, args, kwargs):
87
186
  else:
88
187
  name = f"langchain.task.{instance.__class__.__name__}"
89
188
  with tracer.start_as_current_span(name) as span:
189
+ process_span(to_wrap, span, instance, args)
90
190
  pre_task_processing(to_wrap, instance, args, span)
91
191
  return_value = await wrapped(*args, **kwargs)
92
192
  post_task_processing(to_wrap, span, return_value)
93
193
 
94
194
  return return_value
95
195
 
196
+
96
197
  @with_tracer_wrapper
97
198
  async def allm_wrapper(tracer, to_wrap, wrapped, instance, args, kwargs):
98
199
  # Some Langchain objects are wrapped elsewhere, so we ignore them here
@@ -109,16 +210,24 @@ async def allm_wrapper(tracer, to_wrap, wrapped, instance, args, kwargs):
109
210
  else:
110
211
  name = f"langchain.task.{instance.__class__.__name__}"
111
212
  with tracer.start_as_current_span(name) as span:
112
- 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)
113
220
 
114
221
  return_value = await wrapped(*args, **kwargs)
115
- 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)
116
225
 
117
226
  return return_value
118
227
 
228
+
119
229
  @with_tracer_wrapper
120
230
  def llm_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
121
-
122
231
  # Some Langchain objects are wrapped elsewhere, so we ignore them here
123
232
  if instance.__class__.__name__ in ("AgentExecutor"):
124
233
  return wrapped(*args, **kwargs)
@@ -132,86 +241,121 @@ def llm_wrapper(tracer: Tracer, to_wrap, wrapped, instance, args, kwargs):
132
241
  name = to_wrap.get("span_name")
133
242
  else:
134
243
  name = f"langchain.task.{instance.__class__.__name__}"
244
+
135
245
  with tracer.start_as_current_span(name) as span:
136
- update_llm_endpoint(curr_span= span, instance=instance)
246
+ if 'haystack.components.retrievers' in to_wrap['package'] and 'haystack.retriever' in span.name:
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)
137
253
 
138
254
  return_value = wrapped(*args, **kwargs)
139
- 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)
140
258
 
141
259
  return return_value
142
260
 
261
+
143
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
+
144
268
  triton_llm_endpoint = os.environ.get("TRITON_LLM_ENDPOINT")
145
269
  if triton_llm_endpoint is not None and len(triton_llm_endpoint) > 0:
146
270
  curr_span.set_attribute("server_url", triton_llm_endpoint)
147
271
  else:
148
- if 'temperature' in instance.__dict__:
149
- temp_val = instance.__dict__.get("temperature")
150
- curr_span.set_attribute("temperature", temp_val)
151
- # handling for model name
152
- model_name = resolve_from_alias(instance.__dict__ , ["model","model_name"])
153
- curr_span.set_attribute("model_name", model_name)
154
- set_provider_name(curr_span, instance)
155
- # handling AzureOpenAI deployment
156
- deployment_name = resolve_from_alias(instance.__dict__ , [ "engine", "azure_deployment",
157
- "deployment_name", "deployment_id", "deployment"])
158
- curr_span.set_attribute("az_openai_deployment", deployment_name)
159
- # handling the inference endpoint
160
- inference_ep = resolve_from_alias(instance.__dict__,["azure_endpoint","api_base"])
161
- curr_span.set_attribute("inference_endpoint",inference_ep)
162
-
163
- 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):
164
293
  provider_url = ""
165
-
166
- try :
167
- if isinstance(instance.client._client.base_url.host, str) :
168
- 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)
169
302
  except:
170
303
  pass
171
304
 
172
- try :
305
+ try:
173
306
  if isinstance(instance.api_base, str):
174
307
  provider_url = instance.api_base
175
308
  except:
176
309
  pass
177
310
 
178
- try :
311
+ try:
179
312
  if len(provider_url) > 0:
180
313
  parsed_provider_url = urlparse(provider_url)
181
- curr_span.set_attribute("provider_name", parsed_provider_url.hostname or provider_url)
182
314
  except:
183
315
  pass
316
+ return parsed_provider_url.hostname or provider_url,inference_endpoint
317
+
184
318
 
185
319
  def is_root_span(curr_span: Span) -> bool:
186
320
  return curr_span.parent is None
187
321
 
322
+
188
323
  def get_input_from_args(chain_args):
189
324
  if len(chain_args) > 0 and isinstance(chain_args[0], str):
190
325
  return chain_args[0]
191
326
  return ""
192
327
 
193
- def update_span_from_llm_response(response, span: Span):
194
-
328
+
329
+ def update_span_from_llm_response(response, span: Span, instance):
195
330
  # extract token uasge from langchain openai
196
331
  if (response is not None and hasattr(response, "response_metadata")):
197
332
  response_metadata = response.response_metadata
198
333
  token_usage = response_metadata.get("token_usage")
334
+ meta_dict = {}
199
335
  if token_usage is not None:
200
- span.set_attribute("completion_tokens", token_usage.get("completion_tokens"))
201
- span.set_attribute("prompt_tokens", token_usage.get("prompt_tokens"))
202
- 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)
203
342
  # extract token usage from llamaindex openai
204
- if(response is not None and hasattr(response, "raw")):
343
+ if (response is not None and hasattr(response, "raw")):
205
344
  try:
345
+ meta_dict = {}
206
346
  if response.raw is not None:
207
- 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)
208
349
  if token_usage is not None:
350
+ temperature = instance.__dict__.get("temperature", None)
351
+ meta_dict.update({"temperature": temperature})
209
352
  if getattr(token_usage, "completion_tokens", None):
210
- span.set_attribute("completion_tokens", getattr(token_usage, "completion_tokens"))
353
+ meta_dict.update({"completion_tokens": getattr(token_usage, "completion_tokens")})
211
354
  if getattr(token_usage, "prompt_tokens", None):
212
- span.set_attribute("prompt_tokens", getattr(token_usage, "prompt_tokens"))
355
+ meta_dict.update({"prompt_tokens": getattr(token_usage, "prompt_tokens")})
213
356
  if getattr(token_usage, "total_tokens", None):
214
- 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)
215
359
  except AttributeError:
216
360
  token_usage = None
217
361
 
@@ -220,49 +364,54 @@ def update_workflow_type(to_wrap, span: Span):
220
364
  package_name = to_wrap.get('package')
221
365
 
222
366
  for (package, workflow_type) in WORKFLOW_TYPE_MAP.items():
223
- if(package_name is not None and package in package_name):
367
+ if (package_name is not None and package in package_name):
224
368
  span.set_attribute(WORKFLOW_TYPE_KEY, workflow_type)
225
369
 
226
- def update_span_with_context_input(to_wrap, wrapped_args ,span: Span):
227
- package_name: str = to_wrap.get('package')
228
- if "langchain_core.retrievers" in package_name:
229
- input_arg_text = wrapped_args[0]
230
- span.add_event(CONTEXT_INPUT_KEY, {QUERY:input_arg_text})
231
- if "llama_index.core.indices.base_retriever" in package_name:
232
- input_arg_text = wrapped_args[0].query_str
233
- span.add_event(CONTEXT_INPUT_KEY, {QUERY:input_arg_text})
234
370
 
235
- 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):
236
372
  package_name: str = to_wrap.get('package')
237
- if "llama_index.core.indices.base_retriever" in package_name:
238
- output_arg_text = return_value[0].text
239
- span.add_event(CONTEXT_OUTPUT_KEY, {RESPONSE:output_arg_text})
240
-
241
- 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):
242
402
  input_arg_text = wrapped_args[0]
243
403
 
244
404
  if isinstance(input_arg_text, dict):
245
- span.add_event(PROMPT_INPUT_KEY,input_arg_text)
405
+ span.add_event(PROMPT_INPUT_KEY, input_arg_text)
246
406
  else:
247
- span.add_event(PROMPT_INPUT_KEY,{QUERY:input_arg_text})
407
+ span.add_event(PROMPT_INPUT_KEY, {QUERY: input_arg_text})
248
408
 
249
- 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):
250
411
  package_name: str = to_wrap.get('package')
251
412
  if isinstance(wrapped_args, str):
252
- 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)
253
416
  if "llama_index.core.base.base_query_engine" in package_name:
254
- span.add_event(PROMPT_OUTPUT_KEY, {RESPONSE:wrapped_args.response})
255
-
256
- def update_tags(instance, span):
257
- try:
258
- # copy tags as is from langchain
259
- span.set_attribute(TAGS, getattr(instance, TAGS))
260
- except:
261
- pass
262
- try:
263
- # extract embed model and vector store names for llamaindex
264
- model_name = instance.retriever._embed_model.model_name
265
- vector_store_name = type(instance.retriever._vector_store).__name__
266
- span.set_attribute(TAGS, [model_name, vector_store_name])
267
- except:
268
- pass
417
+ span.add_event(PROMPT_OUTPUT_KEY, {RESPONSE: wrapped_args.response})
@@ -1,5 +1,4 @@
1
1
 
2
-
3
2
  from monocle_apptrace.haystack import HAYSTACK_METHODS
4
3
  from monocle_apptrace.langchain import LANGCHAIN_METHODS
5
4
  from monocle_apptrace.llamaindex import LLAMAINDEX_METHODS
@@ -13,12 +12,15 @@ class WrapperMethod:
13
12
  object_name: str,
14
13
  method: str,
15
14
  span_name: str = None,
15
+ output_processor : list[str] = None,
16
16
  wrapper = task_wrapper
17
17
  ):
18
18
  self.package = package
19
19
  self.object = object_name
20
20
  self.method = method
21
21
  self.span_name = span_name
22
+ self.output_processor=output_processor
23
+
22
24
  self.wrapper = wrapper
23
25
 
24
26
  INBUILT_METHODS_LIST = LANGCHAIN_METHODS + LLAMAINDEX_METHODS + HAYSTACK_METHODS
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.3
2
+ Name: monocle_apptrace
3
+ Version: 0.2.0
4
+ Summary: package with monocle genAI tracing
5
+ Project-URL: Homepage, https://github.com/monocle2ai/monocle
6
+ Project-URL: Issues, https://github.com/monocle2ai/monocle/issues
7
+ License-File: LICENSE
8
+ License-File: NOTICE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.8
13
+ Requires-Dist: opentelemetry-api>=1.21.0
14
+ Requires-Dist: opentelemetry-instrumentation
15
+ Requires-Dist: opentelemetry-sdk>=1.21.0
16
+ Requires-Dist: requests
17
+ Requires-Dist: wrapt>=1.14.0
18
+ Provides-Extra: aws
19
+ Requires-Dist: boto3==1.35.19; extra == 'aws'
20
+ Provides-Extra: azure
21
+ Requires-Dist: azure-storage-blob==12.22.0; extra == 'azure'
22
+ Provides-Extra: dev
23
+ Requires-Dist: datasets==2.20.0; extra == 'dev'
24
+ Requires-Dist: faiss-cpu==1.8.0; extra == 'dev'
25
+ Requires-Dist: instructorembedding==1.0.1; extra == 'dev'
26
+ Requires-Dist: langchain-chroma==0.1.1; extra == 'dev'
27
+ Requires-Dist: langchain-community==0.2.5; extra == 'dev'
28
+ Requires-Dist: langchain-openai==0.1.8; extra == 'dev'
29
+ Requires-Dist: langchain==0.2.5; extra == 'dev'
30
+ Requires-Dist: llama-index-embeddings-huggingface==0.2.0; extra == 'dev'
31
+ Requires-Dist: llama-index-vector-stores-chroma==0.1.9; extra == 'dev'
32
+ Requires-Dist: llama-index==0.10.30; extra == 'dev'
33
+ Requires-Dist: numpy==1.26.4; extra == 'dev'
34
+ Requires-Dist: parameterized==0.9.0; extra == 'dev'
35
+ Requires-Dist: pytest==8.0.0; extra == 'dev'
36
+ Requires-Dist: sentence-transformers==2.6.1; extra == 'dev'
37
+ Requires-Dist: types-requests==2.31.0.20240106; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # Monocle for tracing GenAI app code
41
+
42
+ **Monocle** helps developers and platform engineers building or managing GenAI apps monitor these in prod by making it easy to instrument their code to capture traces that are compliant with open-source cloud-native observability ecosystem.
43
+
44
+ **Monocle** is a community-driven OSS framework for tracing GenAI app code governed as a [Linux Foundation AI & Data project](https://lfaidata.foundation/projects/monocle/).
45
+
46
+ ## Why Monocle
47
+
48
+ Monocle is built for:
49
+ - **app developers** to trace their app code in any environment without lots of custom code decoration
50
+ - **platform engineers** to instrument apps in prod through wrapping instead of asking app devs to recode
51
+ - **GenAI component providers** to add observability features to their products
52
+ - **enterprises** to consume traces from GenAI apps in their existing open-source observability stack
53
+
54
+ Benefits:
55
+ - Monocle provides an implementation + package, not just a spec
56
+ - No expertise in OpenTelemetry spec required
57
+ - No bespoke implementation of that spec required
58
+ - No last-mile GenAI domain specific code required to instrument your app
59
+ - Monocle provides consistency
60
+ - Connect traces across app code executions, model inference or data retrievals
61
+ - No cleansing of telemetry data across GenAI component providers required
62
+ - Works the same in personal lab dev or org cloud prod environments
63
+ - Send traces to location that fits your scale, budget and observability stack
64
+ - Monocle is fully open source and community driven
65
+ - No vendor lock-in
66
+ - Implementation is transparent
67
+ - You can freely use or customize it to fit your needs
68
+
69
+ ## What Monocle provides
70
+
71
+ - Easy to [use](#use-monocle) code instrumentation
72
+ - OpenTelemetry compatible format for [spans](src/monocle_apptrace/metamodel/spans/span_format.json).
73
+ - Community-curated and extensible [metamodel](src/monocle_apptrace/metamodel/README.md) for consisent tracing of GenAI components.
74
+ - Export to local and cloud storage
75
+
76
+ ## Use Monocle
77
+
78
+ - Get the Monocle package
79
+
80
+ ```
81
+ pip install monocle_apptrace
82
+ ```
83
+ - Instrument your app code
84
+ - Import the Monocle package
85
+ ```
86
+ from monocle_apptrace.instrumentor import setup_monocle_telemetry
87
+ ```
88
+ - Setup instrumentation in your ```main()``` function
89
+ ```
90
+ setup_monocle_telemetry(workflow_name="your-app-name")
91
+ ```
92
+ - (Optionally) Modify config to alter where traces are sent
93
+
94
+ See [Monocle user guide](Monocle_User_Guide.md) for more details.
95
+
96
+
97
+ ## Roadmap
98
+
99
+ Goal of Monocle is to support tracing for apps written in *any language* with *any LLM orchestration or agentic framework* and built using models, vectors, agents or other components served up by *any cloud or model inference provider*.
100
+
101
+ Current version supports:
102
+ - Language: (🟢) Python , (🔜) [Typescript](https://github.com/monocle2ai/monocle-typescript)
103
+ - LLM-frameworks: (🟢) Langchain, (🟢) Llamaindex, (🟢) Haystack, (🔜) Flask
104
+ - LLM inference providers: (🟢) OpenAI, (🟢) Azure OpenAI, (🟢) Nvidia Triton, (🔜) AWS Bedrock, (🔜) Google Vertex, (🔜) Azure ML, (🔜) Hugging Face
105
+ - Vector stores: (🟢) FAISS, (🔜) OpenSearch, (🔜) Milvus
106
+ - Exporter: (🟢) stdout, (🟢) file, (🔜) Azure Blob Storage, (🔜) AWS S3, (🔜) Google Cloud Storage
107
+
108
+
109
+ ## Get involved
110
+ ### Provide feedback
111
+ - Submit issues and enhancements requests via Github issues
112
+
113
+ ### Contribute
114
+ - Monocle is community based open source project. We welcome your contributions. Please refer to the CONTRIBUTING and CODE_OF_CONDUCT for guidelines. The [contributor's guide](CONTRIBUTING.md) provides technical details of the project.
115
+
@@ -0,0 +1,44 @@
1
+ monocle_apptrace/README.md,sha256=T5NFC01bF8VR0oVnAX_n0bhsEtttwqfTxDNAe5Y_ivE,3765
2
+ monocle_apptrace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ monocle_apptrace/constants.py,sha256=wjObbmMTFL201x-bf3EOXevYygwkFH_1ng5dDrpE3z0,810
4
+ monocle_apptrace/instrumentor.py,sha256=FMQ8yVNYGRBTmyUjC2578G8RzSRpHY5HtVN5WK9ndVE,5291
5
+ monocle_apptrace/utils.py,sha256=l-affXaMX6U_wG1rWRE2GPB6dDGtha7kL0MQK4PEptI,6395
6
+ monocle_apptrace/wrap_common.py,sha256=ux2Ob7g1FYDtt1gF4g8XKQhl6Ed5X_iR4CP4GHX4upM,18178
7
+ monocle_apptrace/wrapper.py,sha256=8bsMaCCCbaEJN8hW5YoYkn2XLJ7vFI1HJk6QHihbkto,830
8
+ monocle_apptrace/exporters/base_exporter.py,sha256=1UvywEiKZ0nilcXiy-mZue012yVvYXgwib1x1qQvpBc,1647
9
+ monocle_apptrace/exporters/exporter_processor.py,sha256=BTcBgMuFLHCdCgVvc9TKIo9y8g1BvShI0L4vX6Q-cmk,393
10
+ monocle_apptrace/exporters/file_exporter.py,sha256=gN9pJ_X5pcstVVsyivgHsjWhr443eRa6Y6Hx1rGLQAM,2280
11
+ monocle_apptrace/exporters/monocle_exporters.py,sha256=mvVoGr4itvUBuakH17zNEuXYpHtqRhq2CBH5JXhleoM,1418
12
+ monocle_apptrace/exporters/aws/s3_exporter.py,sha256=S_w2OBkM8eEYK6bBDbfcRV4LdojU1QygOBqjdhcjAl4,6730
13
+ monocle_apptrace/exporters/azure/blob_exporter.py,sha256=dtL8MPISyP1eKM0gGpSoqTUmFuGEfycqF9OCjFF6H6E,5716
14
+ monocle_apptrace/exporters/okahu/okahu_exporter.py,sha256=p2rjStwo0OMEdHWQt_QvREpUWXbDm5jGx3qXeYai4_M,4407
15
+ monocle_apptrace/haystack/__init__.py,sha256=zMvF6Dh1-vVIAQD__HC1ubT5bs-EupUghg7HNhDv7_c,448
16
+ monocle_apptrace/haystack/wrap_node.py,sha256=IK07Wn3Lk1Os9URsyrmB1HXOH2FNdzK9fNLlR8TZdYc,908
17
+ monocle_apptrace/haystack/wrap_openai.py,sha256=Yp916DhOl0WI6virRi3L43snfsQm7PhI28wlDsg19v8,1536
18
+ monocle_apptrace/haystack/wrap_pipeline.py,sha256=yZAw7Hdv7FXe6rrM7gA2y5SjaZYQZCAi0q-R-uqUEvk,2254
19
+ monocle_apptrace/langchain/__init__.py,sha256=3yhbdw1h9I1nVEfnOOPKz9yD5NqwdLSZsxtXbMplRkw,400
20
+ monocle_apptrace/llamaindex/__init__.py,sha256=TgV1ZM8Cz113pZ2aAgpYwsCJyHA2aHOvNoW1QMBp0mM,709
21
+ monocle_apptrace/metamodel/README.md,sha256=KYuuYqgA9PNbOjG0zYj2nAdvNEpyNN_Bk9M2tNdnZ_s,4598
22
+ monocle_apptrace/metamodel/entities/README.md,sha256=dY7Q8QT_Ju-2ul65J-k0x6beDLvRirlbGufZN1Q0tpk,2068
23
+ monocle_apptrace/metamodel/entities/app_hosting_types.json,sha256=MWeHUe77n4HvO1hm2PX8iWjbRONBuNy2I84vd-lymYk,568
24
+ monocle_apptrace/metamodel/entities/entities.json,sha256=6eDoDm_6aPUgH_ROjI2H9Tk3J5Lj55VkX3Li-TJmaxg,1285
25
+ monocle_apptrace/metamodel/entities/inference_types.json,sha256=Gkq7qNw5Qn91tuq-rBxHJ4BSzT-etCshNjT5_G4Bg8I,651
26
+ monocle_apptrace/metamodel/entities/model_types.json,sha256=Oz9DzuX_ptpLEVTc1Epv4_Y80TrYxR7cyc9kM0Zk780,785
27
+ monocle_apptrace/metamodel/entities/vector_store_types.json,sha256=EA0KayHMOG7wMIkec54en03_3yT1qpdGh5TiDrAoh4g,462
28
+ monocle_apptrace/metamodel/entities/workflow_types.json,sha256=eD0W3_FocKrzrrW0bks0hIJb9Kj7w8c1dpXzxmGLOwk,386
29
+ monocle_apptrace/metamodel/maps/haystack_methods.json,sha256=JmngkaKICAzOyrWNTWEOLYFrp99l5wcERYKE_SQRNxE,698
30
+ monocle_apptrace/metamodel/maps/langchain_methods.json,sha256=lfU8nd6td5qzTI01glM063Vg0UoZM9HGPirwN4OZcKc,4285
31
+ monocle_apptrace/metamodel/maps/llamaindex_methods.json,sha256=pZ4d1DS4D6Wgpz9EBOrEuJUFN58jtu75E8ELyGRBHyM,2636
32
+ monocle_apptrace/metamodel/maps/attributes/inference/langchain_entities.json,sha256=E_K3eHv_e9Va2AcjNLHcXeAgePB1uolTMdRnkxxtNqU,1152
33
+ monocle_apptrace/metamodel/maps/attributes/inference/llamaindex_entities.json,sha256=LuAa9ZKl3mO2VUiXMVGZNEfSHOZ0n5qsVQLqWRHczVM,1122
34
+ monocle_apptrace/metamodel/maps/attributes/retrieval/langchain_entities.json,sha256=vSe2kbeJ7izmj5TBdQpX5pJ3hbjdY4SkcXvWPiam7oo,718
35
+ monocle_apptrace/metamodel/maps/attributes/retrieval/llamaindex_entities.json,sha256=BtWeSXUPzKR9-vuhZUUhn2Gz9Y4SXBZoFC81K1q0XwQ,712
36
+ monocle_apptrace/metamodel/spans/README.md,sha256=_uMkLLaWitQ_rPh7oQbW5Oe7uGSv2h_QA6YwxHRJi74,5433
37
+ monocle_apptrace/metamodel/spans/span_example.json,sha256=7ZAUssL1UYXlbKHre8PfeNlIhHnM28e2CH9EyHDTOt8,4402
38
+ monocle_apptrace/metamodel/spans/span_format.json,sha256=GhfioGgMhG7St0DeYA1fgNtMkbr9wiQ1L2hovekRQ24,1512
39
+ monocle_apptrace/metamodel/spans/span_types.json,sha256=jwVyPbYMhEf2Ea6Egmb3m1Za28ap9dgZIpJDhdE1BlY,361
40
+ monocle_apptrace-0.2.0.dist-info/METADATA,sha256=zaIOrJwa-RLX1bWeBEfTCfeBGC2cuNeXKwPHAYYNRT8,5364
41
+ monocle_apptrace-0.2.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
42
+ monocle_apptrace-0.2.0.dist-info/licenses/LICENSE,sha256=ay9trLiP5I7ZsFXo6AqtkLYdRqe5S9r-DrPOvsNlZrg,9136
43
+ monocle_apptrace-0.2.0.dist-info/licenses/NOTICE,sha256=9jn4xtwM_uUetJMx5WqGnhrR7MIhpoRlpokjSTlyt8c,112
44
+ monocle_apptrace-0.2.0.dist-info/RECORD,,