lmnr 0.4.6__py3-none-any.whl → 0.4.8__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.
Files changed (50) hide show
  1. lmnr/sdk/decorators.py +2 -7
  2. lmnr/sdk/evaluations.py +4 -4
  3. lmnr/sdk/laminar.py +69 -4
  4. lmnr/sdk/types.py +8 -8
  5. lmnr/traceloop_sdk/.flake8 +12 -0
  6. lmnr/traceloop_sdk/.python-version +1 -0
  7. lmnr/traceloop_sdk/README.md +16 -0
  8. lmnr/traceloop_sdk/__init__.py +138 -0
  9. lmnr/traceloop_sdk/config/__init__.py +13 -0
  10. lmnr/traceloop_sdk/decorators/__init__.py +131 -0
  11. lmnr/traceloop_sdk/decorators/base.py +253 -0
  12. lmnr/traceloop_sdk/instruments.py +29 -0
  13. lmnr/traceloop_sdk/metrics/__init__.py +0 -0
  14. lmnr/traceloop_sdk/metrics/metrics.py +176 -0
  15. lmnr/traceloop_sdk/tests/__init__.py +1 -0
  16. lmnr/traceloop_sdk/tests/cassettes/test_association_properties/test_langchain_and_external_association_properties.yaml +101 -0
  17. lmnr/traceloop_sdk/tests/cassettes/test_association_properties/test_langchain_association_properties.yaml +99 -0
  18. lmnr/traceloop_sdk/tests/cassettes/test_manual/test_manual_report.yaml +98 -0
  19. lmnr/traceloop_sdk/tests/cassettes/test_manual/test_resource_attributes.yaml +98 -0
  20. lmnr/traceloop_sdk/tests/cassettes/test_privacy_no_prompts/test_simple_workflow.yaml +199 -0
  21. lmnr/traceloop_sdk/tests/cassettes/test_prompt_management/test_prompt_management.yaml +202 -0
  22. lmnr/traceloop_sdk/tests/cassettes/test_sdk_initialization/test_resource_attributes.yaml +199 -0
  23. lmnr/traceloop_sdk/tests/cassettes/test_tasks/test_task_io_serialization_with_langchain.yaml +96 -0
  24. lmnr/traceloop_sdk/tests/cassettes/test_workflows/test_simple_aworkflow.yaml +98 -0
  25. lmnr/traceloop_sdk/tests/cassettes/test_workflows/test_simple_workflow.yaml +199 -0
  26. lmnr/traceloop_sdk/tests/cassettes/test_workflows/test_streaming_workflow.yaml +167 -0
  27. lmnr/traceloop_sdk/tests/conftest.py +111 -0
  28. lmnr/traceloop_sdk/tests/test_association_properties.py +229 -0
  29. lmnr/traceloop_sdk/tests/test_manual.py +48 -0
  30. lmnr/traceloop_sdk/tests/test_nested_tasks.py +47 -0
  31. lmnr/traceloop_sdk/tests/test_privacy_no_prompts.py +50 -0
  32. lmnr/traceloop_sdk/tests/test_sdk_initialization.py +57 -0
  33. lmnr/traceloop_sdk/tests/test_tasks.py +32 -0
  34. lmnr/traceloop_sdk/tests/test_workflows.py +261 -0
  35. lmnr/traceloop_sdk/tracing/__init__.py +2 -0
  36. lmnr/traceloop_sdk/tracing/content_allow_list.py +24 -0
  37. lmnr/traceloop_sdk/tracing/context_manager.py +13 -0
  38. lmnr/traceloop_sdk/tracing/manual.py +57 -0
  39. lmnr/traceloop_sdk/tracing/tracing.py +1078 -0
  40. lmnr/traceloop_sdk/utils/__init__.py +26 -0
  41. lmnr/traceloop_sdk/utils/in_memory_span_exporter.py +61 -0
  42. lmnr/traceloop_sdk/utils/json_encoder.py +20 -0
  43. lmnr/traceloop_sdk/utils/package_check.py +8 -0
  44. lmnr/traceloop_sdk/version.py +1 -0
  45. {lmnr-0.4.6.dist-info → lmnr-0.4.8.dist-info}/METADATA +40 -3
  46. lmnr-0.4.8.dist-info/RECORD +53 -0
  47. lmnr-0.4.6.dist-info/RECORD +0 -13
  48. {lmnr-0.4.6.dist-info → lmnr-0.4.8.dist-info}/LICENSE +0 -0
  49. {lmnr-0.4.6.dist-info → lmnr-0.4.8.dist-info}/WHEEL +0 -0
  50. {lmnr-0.4.6.dist-info → lmnr-0.4.8.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,1078 @@
1
+ import atexit
2
+ import logging
3
+ import os
4
+
5
+
6
+ from colorama import Fore
7
+ from opentelemetry import trace
8
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
9
+ OTLPSpanExporter as HTTPExporter,
10
+ )
11
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
12
+ OTLPSpanExporter as GRPCExporter,
13
+ )
14
+ from opentelemetry.sdk.resources import Resource
15
+ from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
16
+ from opentelemetry.propagators.textmap import TextMapPropagator
17
+ from opentelemetry.propagate import set_global_textmap
18
+ from opentelemetry.sdk.trace.export import (
19
+ SpanExporter,
20
+ SimpleSpanProcessor,
21
+ BatchSpanProcessor,
22
+ )
23
+ from opentelemetry.trace import get_tracer_provider, ProxyTracerProvider
24
+ from opentelemetry.context import get_value, attach, set_value
25
+ from opentelemetry.instrumentation.threading import ThreadingInstrumentor
26
+
27
+ from opentelemetry.semconv_ai import SpanAttributes
28
+
29
+ # from lmnr.traceloop_sdk import Telemetry
30
+ from lmnr.traceloop_sdk.instruments import Instruments
31
+ from lmnr.traceloop_sdk.tracing.content_allow_list import ContentAllowList
32
+ from lmnr.traceloop_sdk.utils import is_notebook
33
+ from lmnr.traceloop_sdk.utils.package_check import is_package_installed
34
+ from typing import Dict, Optional, Set
35
+
36
+
37
+ TRACER_NAME = "traceloop.tracer"
38
+ EXCLUDED_URLS = """
39
+ iam.cloud.ibm.com,
40
+ dataplatform.cloud.ibm.com,
41
+ ml.cloud.ibm.com,
42
+ api.openai.com,
43
+ openai.azure.com,
44
+ api.anthropic.com,
45
+ api.cohere.ai,
46
+ pinecone.io,
47
+ traceloop.com,
48
+ posthog.com,
49
+ sentry.io,
50
+ bedrock-runtime,
51
+ googleapis.com,
52
+ githubusercontent.com,
53
+ openaipublic.blob.core.windows.net"""
54
+
55
+
56
+ class TracerWrapper(object):
57
+ resource_attributes: dict = {}
58
+ enable_content_tracing: bool = True
59
+ endpoint: str = None
60
+ headers: Dict[str, str] = {}
61
+ __tracer_provider: TracerProvider = None
62
+
63
+ def __new__(
64
+ cls,
65
+ disable_batch=False,
66
+ processor: SpanProcessor = None,
67
+ propagator: TextMapPropagator = None,
68
+ exporter: SpanExporter = None,
69
+ should_enrich_metrics: bool = True,
70
+ instruments: Optional[Set[Instruments]] = None,
71
+ ) -> "TracerWrapper":
72
+ if not hasattr(cls, "instance"):
73
+ obj = cls.instance = super(TracerWrapper, cls).__new__(cls)
74
+ if not TracerWrapper.endpoint:
75
+ return obj
76
+
77
+ obj.__resource = Resource(attributes=TracerWrapper.resource_attributes)
78
+ obj.__tracer_provider = init_tracer_provider(resource=obj.__resource)
79
+ if processor:
80
+ # Telemetry().capture("tracer:init", {"processor": "custom"})
81
+ obj.__spans_processor: SpanProcessor = processor
82
+ obj.__spans_processor_original_on_start = processor.on_start
83
+ else:
84
+ # if exporter:
85
+ # Telemetry().capture(
86
+ # "tracer:init",
87
+ # {
88
+ # "exporter": "custom",
89
+ # "processor": "simple" if disable_batch else "batch",
90
+ # },
91
+ # )
92
+ # else:
93
+ # Telemetry().capture(
94
+ # "tracer:init",
95
+ # {
96
+ # "exporter": TracerWrapper.endpoint,
97
+ # "processor": "simple" if disable_batch else "batch",
98
+ # },
99
+ # )
100
+
101
+ obj.__spans_exporter: SpanExporter = (
102
+ exporter
103
+ if exporter
104
+ else init_spans_exporter(
105
+ TracerWrapper.endpoint, TracerWrapper.headers
106
+ )
107
+ )
108
+ if disable_batch or is_notebook():
109
+ obj.__spans_processor: SpanProcessor = SimpleSpanProcessor(
110
+ obj.__spans_exporter
111
+ )
112
+ else:
113
+ obj.__spans_processor: SpanProcessor = BatchSpanProcessor(
114
+ obj.__spans_exporter
115
+ )
116
+ obj.__spans_processor_original_on_start = None
117
+
118
+ obj.__spans_processor.on_start = obj._span_processor_on_start
119
+ obj.__tracer_provider.add_span_processor(obj.__spans_processor)
120
+
121
+ if propagator:
122
+ set_global_textmap(propagator)
123
+
124
+ # this makes sure otel context is propagated so we always want it
125
+ ThreadingInstrumentor().instrument()
126
+
127
+ instrument_set = False
128
+ if instruments is None:
129
+ init_instrumentations(should_enrich_metrics)
130
+ instrument_set = True
131
+ else:
132
+ for instrument in instruments:
133
+ if instrument == Instruments.OPENAI:
134
+ if not init_openai_instrumentor(should_enrich_metrics):
135
+ print(Fore.RED + "Warning: OpenAI library does not exist.")
136
+ print(Fore.RESET)
137
+ else:
138
+ instrument_set = True
139
+ elif instrument == Instruments.ANTHROPIC:
140
+ if not init_anthropic_instrumentor(should_enrich_metrics):
141
+ print(
142
+ Fore.RED + "Warning: Anthropic library does not exist."
143
+ )
144
+ print(Fore.RESET)
145
+ else:
146
+ instrument_set = True
147
+ elif instrument == Instruments.COHERE:
148
+ if not init_cohere_instrumentor():
149
+ print(Fore.RED + "Warning: Cohere library does not exist.")
150
+ print(Fore.RESET)
151
+ else:
152
+ instrument_set = True
153
+ elif instrument == Instruments.PINECONE:
154
+ if not init_pinecone_instrumentor():
155
+ print(
156
+ Fore.RED + "Warning: Pinecone library does not exist."
157
+ )
158
+ print(Fore.RESET)
159
+ else:
160
+ instrument_set = True
161
+ elif instrument == Instruments.CHROMA:
162
+ if not init_chroma_instrumentor():
163
+ print(Fore.RED + "Warning: Chroma library does not exist.")
164
+ print(Fore.RESET)
165
+ else:
166
+ instrument_set = True
167
+ elif instrument == Instruments.GOOGLE_GENERATIVEAI:
168
+ if not init_google_generativeai_instrumentor():
169
+ print(
170
+ Fore.RED
171
+ + "Warning: Google Generative AI library does not exist."
172
+ )
173
+ print(Fore.RESET)
174
+ else:
175
+ instrument_set = True
176
+ elif instrument == Instruments.LANGCHAIN:
177
+ if not init_langchain_instrumentor():
178
+ print(
179
+ Fore.RED + "Warning: LangChain library does not exist."
180
+ )
181
+ print(Fore.RESET)
182
+ else:
183
+ instrument_set = True
184
+ elif instrument == Instruments.MISTRAL:
185
+ if not init_mistralai_instrumentor():
186
+ print(
187
+ Fore.RED + "Warning: MistralAI library does not exist."
188
+ )
189
+ print(Fore.RESET)
190
+ else:
191
+ instrument_set = True
192
+ elif instrument == Instruments.OLLAMA:
193
+ if not init_ollama_instrumentor():
194
+ print(Fore.RED + "Warning: Ollama library does not exist.")
195
+ print(Fore.RESET)
196
+ else:
197
+ instrument_set = True
198
+ elif instrument == Instruments.LLAMA_INDEX:
199
+ if not init_llama_index_instrumentor():
200
+ print(
201
+ Fore.RED + "Warning: LlamaIndex library does not exist."
202
+ )
203
+ print(Fore.RESET)
204
+ else:
205
+ instrument_set = True
206
+ elif instrument == Instruments.MILVUS:
207
+ if not init_milvus_instrumentor():
208
+ print(Fore.RED + "Warning: Milvus library does not exist.")
209
+ print(Fore.RESET)
210
+ else:
211
+ instrument_set = True
212
+ elif instrument == Instruments.TRANSFORMERS:
213
+ if not init_transformers_instrumentor():
214
+ print(
215
+ Fore.RED
216
+ + "Warning: Transformers library does not exist."
217
+ )
218
+ print(Fore.RESET)
219
+ else:
220
+ instrument_set = True
221
+ elif instrument == Instruments.TOGETHER:
222
+ if not init_together_instrumentor():
223
+ print(
224
+ Fore.RED + "Warning: TogetherAI library does not exist."
225
+ )
226
+ print(Fore.RESET)
227
+ else:
228
+ instrument_set = True
229
+ elif instrument == Instruments.REQUESTS:
230
+ if not init_requests_instrumentor():
231
+ print(
232
+ Fore.RED + "Warning: Requests library does not exist."
233
+ )
234
+ print(Fore.RESET)
235
+ else:
236
+ instrument_set = True
237
+ elif instrument == Instruments.URLLIB3:
238
+ if not init_urllib3_instrumentor():
239
+ print(Fore.RED + "Warning: urllib3 library does not exist.")
240
+ print(Fore.RESET)
241
+ else:
242
+ instrument_set = True
243
+ elif instrument == Instruments.PYMYSQL:
244
+ if not init_pymysql_instrumentor():
245
+ print(Fore.RED + "Warning: PyMySQL library does not exist.")
246
+ print(Fore.RESET)
247
+ else:
248
+ instrument_set = True
249
+ elif instrument == Instruments.BEDROCK:
250
+ if not init_bedrock_instrumentor(should_enrich_metrics):
251
+ print(Fore.RED + "Warning: Bedrock library does not exist.")
252
+ print(Fore.RESET)
253
+ else:
254
+ instrument_set = True
255
+ elif instrument == Instruments.REPLICATE:
256
+ if not init_replicate_instrumentor():
257
+ print(
258
+ Fore.RED + "Warning: Replicate library does not exist."
259
+ )
260
+ print(Fore.RESET)
261
+ else:
262
+ instrument_set = True
263
+ elif instrument == Instruments.VERTEXAI:
264
+ if not init_vertexai_instrumentor():
265
+ print(
266
+ Fore.RED + "Warning: Vertex AI library does not exist."
267
+ )
268
+ print(Fore.RESET)
269
+ else:
270
+ instrument_set = True
271
+ elif instrument == Instruments.WATSONX:
272
+ if not init_watsonx_instrumentor():
273
+ print(Fore.RED + "Warning: Watsonx library does not exist.")
274
+ print(Fore.RESET)
275
+ else:
276
+ instrument_set = True
277
+ elif instrument == Instruments.WEAVIATE:
278
+ if not init_weaviate_instrumentor():
279
+ print(
280
+ Fore.RED + "Warning: Weaviate library does not exist."
281
+ )
282
+ print(Fore.RESET)
283
+ else:
284
+ instrument_set = True
285
+ elif instrument == Instruments.ALEPHALPHA:
286
+ if not init_alephalpha_instrumentor():
287
+ print(
288
+ Fore.RED
289
+ + "Warning: Aleph Alpha library does not exist."
290
+ )
291
+ print(Fore.RESET)
292
+ else:
293
+ instrument_set = True
294
+ elif instrument == Instruments.MARQO:
295
+ if not init_marqo_instrumentor():
296
+ print(Fore.RED + "Warning: marqo library does not exist.")
297
+ print(Fore.RESET)
298
+ else:
299
+ instrument_set = True
300
+ elif instrument == Instruments.LANCEDB:
301
+ if not init_lancedb_instrumentor():
302
+ print(Fore.RED + "Warning: LanceDB library does not exist.")
303
+ print(Fore.RESET)
304
+ else:
305
+ instrument_set = True
306
+ elif instrument == Instruments.REDIS:
307
+ if not init_redis_instrumentor():
308
+ print(Fore.RED + "Warning: redis library does not exist.")
309
+ print(Fore.RESET)
310
+ else:
311
+ instrument_set = True
312
+
313
+ else:
314
+ print(
315
+ Fore.RED
316
+ + "Warning: "
317
+ + instrument
318
+ + " instrumentation does not exist."
319
+ )
320
+ print(
321
+ "Usage:\n"
322
+ + "from lmnr.traceloop_sdk.instruments import Instruments\n"
323
+ + 'Traceloop.init(app_name="...", instruments=set([Instruments.OPENAI]))'
324
+ )
325
+ print(Fore.RESET)
326
+
327
+ if not instrument_set:
328
+ print(
329
+ Fore.RED + "Warning: No valid instruments set. Remove 'instrument' "
330
+ "argument to use all instruments, or set a valid instrument."
331
+ )
332
+ print(Fore.RESET)
333
+
334
+ obj.__content_allow_list = ContentAllowList()
335
+
336
+ # Force flushes for debug environments (e.g. local development)
337
+ atexit.register(obj.exit_handler)
338
+
339
+ return cls.instance
340
+
341
+ def exit_handler(self):
342
+ self.flush()
343
+
344
+ def _span_processor_on_start(self, span, parent_context):
345
+ workflow_name = get_value("workflow_name")
346
+ if workflow_name is not None:
347
+ span.set_attribute(SpanAttributes.TRACELOOP_WORKFLOW_NAME, workflow_name)
348
+
349
+ entity_path = get_value("entity_path")
350
+ if entity_path is not None:
351
+ span.set_attribute(SpanAttributes.TRACELOOP_ENTITY_PATH, entity_path)
352
+
353
+ association_properties = get_value("association_properties")
354
+ if association_properties is not None:
355
+ _set_association_properties_attributes(span, association_properties)
356
+
357
+ if not self.enable_content_tracing:
358
+ if self.__content_allow_list.is_allowed(association_properties):
359
+ attach(set_value("override_enable_content_tracing", True))
360
+ else:
361
+ attach(set_value("override_enable_content_tracing", False))
362
+
363
+ if is_llm_span(span):
364
+ managed_prompt = get_value("managed_prompt")
365
+ if managed_prompt is not None:
366
+ span.set_attribute(
367
+ SpanAttributes.TRACELOOP_PROMPT_MANAGED, managed_prompt
368
+ )
369
+
370
+ prompt_key = get_value("prompt_key")
371
+ if prompt_key is not None:
372
+ span.set_attribute(SpanAttributes.TRACELOOP_PROMPT_KEY, prompt_key)
373
+
374
+ prompt_version = get_value("prompt_version")
375
+ if prompt_version is not None:
376
+ span.set_attribute(
377
+ SpanAttributes.TRACELOOP_PROMPT_VERSION, prompt_version
378
+ )
379
+
380
+ prompt_version_name = get_value("prompt_version_name")
381
+ if prompt_version_name is not None:
382
+ span.set_attribute(
383
+ SpanAttributes.TRACELOOP_PROMPT_VERSION_NAME, prompt_version_name
384
+ )
385
+
386
+ prompt_version_hash = get_value("prompt_version_hash")
387
+ if prompt_version_hash is not None:
388
+ span.set_attribute(
389
+ SpanAttributes.TRACELOOP_PROMPT_VERSION_HASH, prompt_version_hash
390
+ )
391
+
392
+ prompt_template = get_value("prompt_template")
393
+ if prompt_template is not None:
394
+ span.set_attribute(
395
+ SpanAttributes.TRACELOOP_PROMPT_TEMPLATE, prompt_template
396
+ )
397
+
398
+ prompt_template_variables = get_value("prompt_template_variables")
399
+ if prompt_template_variables is not None:
400
+ for key, value in prompt_template_variables.items():
401
+ span.set_attribute(
402
+ f"{SpanAttributes.TRACELOOP_PROMPT_TEMPLATE_VARIABLES}.{key}",
403
+ value,
404
+ )
405
+
406
+ # Call original on_start method if it exists in custom processor
407
+ if self.__spans_processor_original_on_start:
408
+ self.__spans_processor_original_on_start(span, parent_context)
409
+
410
+ @staticmethod
411
+ def set_static_params(
412
+ resource_attributes: dict,
413
+ enable_content_tracing: bool,
414
+ endpoint: str,
415
+ headers: Dict[str, str],
416
+ ) -> None:
417
+ TracerWrapper.resource_attributes = resource_attributes
418
+ TracerWrapper.enable_content_tracing = enable_content_tracing
419
+ TracerWrapper.endpoint = endpoint
420
+ TracerWrapper.headers = headers
421
+
422
+ @classmethod
423
+ def verify_initialized(cls) -> bool:
424
+ if hasattr(cls, "instance"):
425
+ return True
426
+
427
+ if (os.getenv("TRACELOOP_SUPPRESS_WARNINGS") or "false").lower() == "true":
428
+ return False
429
+
430
+ print(
431
+ Fore.RED
432
+ + "Warning: Traceloop not initialized, make sure you call Traceloop.init()"
433
+ )
434
+ print(Fore.RESET)
435
+ return False
436
+
437
+ def flush(self):
438
+ self.__spans_processor.force_flush()
439
+
440
+ def get_tracer(self):
441
+ return self.__tracer_provider.get_tracer(TRACER_NAME)
442
+
443
+
444
+ def set_association_properties(properties: dict) -> None:
445
+ attach(set_value("association_properties", properties))
446
+
447
+ # Attach association properties to the current span, if it's a workflow or a task
448
+ span = trace.get_current_span()
449
+ if get_value("workflow_name") is not None or get_value("entity_name") is not None:
450
+ _set_association_properties_attributes(span, properties)
451
+
452
+
453
+ def _set_association_properties_attributes(span, properties: dict) -> None:
454
+ for key, value in properties.items():
455
+ span.set_attribute(
456
+ f"{SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.{key}", value
457
+ )
458
+
459
+
460
+ def set_workflow_name(workflow_name: str) -> None:
461
+ attach(set_value("workflow_name", workflow_name))
462
+
463
+
464
+ def set_entity_path(entity_path: str) -> None:
465
+ attach(set_value("entity_path", entity_path))
466
+
467
+
468
+ def get_chained_entity_path(entity_name: str) -> str:
469
+ parent = get_value("entity_path")
470
+ if parent is None:
471
+ return entity_name
472
+ else:
473
+ return f"{parent}.{entity_name}"
474
+
475
+
476
+ def set_managed_prompt_tracing_context(
477
+ key: str,
478
+ version: int,
479
+ version_name: str,
480
+ version_hash: str,
481
+ template_variables: dict,
482
+ ) -> None:
483
+ attach(set_value("managed_prompt", True))
484
+ attach(set_value("prompt_key", key))
485
+ attach(set_value("prompt_version", version))
486
+ attach(set_value("prompt_version_name", version_name))
487
+ attach(set_value("prompt_version_hash", version_hash))
488
+ attach(set_value("prompt_template_variables", template_variables))
489
+
490
+
491
+ def set_external_prompt_tracing_context(
492
+ template: str, variables: dict, version: int
493
+ ) -> None:
494
+ attach(set_value("managed_prompt", False))
495
+ attach(set_value("prompt_version", version))
496
+ attach(set_value("prompt_template", template))
497
+ attach(set_value("prompt_template_variables", variables))
498
+
499
+
500
+ def is_llm_span(span) -> bool:
501
+ return span.attributes.get(SpanAttributes.LLM_REQUEST_TYPE) is not None
502
+
503
+
504
+ def init_spans_exporter(api_endpoint: str, headers: Dict[str, str]) -> SpanExporter:
505
+ if "http" in api_endpoint.lower() or "https" in api_endpoint.lower():
506
+ return HTTPExporter(endpoint=f"{api_endpoint}/v1/traces", headers=headers)
507
+ else:
508
+ return GRPCExporter(endpoint=f"{api_endpoint}", headers=headers)
509
+
510
+
511
+ def init_tracer_provider(resource: Resource) -> TracerProvider:
512
+ provider: TracerProvider = None
513
+ default_provider: TracerProvider = get_tracer_provider()
514
+
515
+ if isinstance(default_provider, ProxyTracerProvider):
516
+ provider = TracerProvider(resource=resource)
517
+ trace.set_tracer_provider(provider)
518
+ elif not hasattr(default_provider, "add_span_processor"):
519
+ logging.error(
520
+ "Cannot add span processor to the default provider since it doesn't support it"
521
+ )
522
+ return
523
+ else:
524
+ provider = default_provider
525
+
526
+ return provider
527
+
528
+
529
+ def init_instrumentations(should_enrich_metrics: bool):
530
+ init_openai_instrumentor(should_enrich_metrics)
531
+ init_anthropic_instrumentor(should_enrich_metrics)
532
+ init_cohere_instrumentor()
533
+ init_pinecone_instrumentor()
534
+ init_qdrant_instrumentor()
535
+ init_chroma_instrumentor()
536
+ init_google_generativeai_instrumentor()
537
+ init_haystack_instrumentor()
538
+ init_langchain_instrumentor()
539
+ init_mistralai_instrumentor()
540
+ init_ollama_instrumentor()
541
+ init_llama_index_instrumentor()
542
+ init_milvus_instrumentor()
543
+ init_transformers_instrumentor()
544
+ init_together_instrumentor()
545
+ init_redis_instrumentor()
546
+ init_requests_instrumentor()
547
+ init_urllib3_instrumentor()
548
+ init_pymysql_instrumentor()
549
+ init_bedrock_instrumentor(should_enrich_metrics)
550
+ init_replicate_instrumentor()
551
+ init_vertexai_instrumentor()
552
+ init_watsonx_instrumentor()
553
+ init_weaviate_instrumentor()
554
+ init_alephalpha_instrumentor()
555
+ init_marqo_instrumentor()
556
+ init_lancedb_instrumentor()
557
+ init_groq_instrumentor()
558
+
559
+
560
+ def init_openai_instrumentor(should_enrich_metrics: bool):
561
+ try:
562
+ if is_package_installed("openai"):
563
+ # Telemetry().capture("instrumentation:openai:init")
564
+ from opentelemetry.instrumentation.openai import OpenAIInstrumentor
565
+
566
+ instrumentor = OpenAIInstrumentor(
567
+ # exception_logger=lambda e: Telemetry().log_exception(e),
568
+ enrich_assistant=should_enrich_metrics,
569
+ enrich_token_usage=should_enrich_metrics,
570
+ get_common_metrics_attributes=metrics_common_attributes,
571
+ )
572
+ if not instrumentor.is_instrumented_by_opentelemetry:
573
+ instrumentor.instrument()
574
+ return True
575
+
576
+ except Exception as e:
577
+ logging.error(f"Error initializing OpenAI instrumentor: {e}")
578
+ # Telemetry().log_exception(e)
579
+ return False
580
+
581
+
582
+ def init_anthropic_instrumentor(should_enrich_metrics: bool):
583
+ try:
584
+ if is_package_installed("anthropic"):
585
+ # Telemetry().capture("instrumentation:anthropic:init")
586
+ from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
587
+
588
+ instrumentor = AnthropicInstrumentor(
589
+ # exception_logger=lambda e: Telemetry().log_exception(e),
590
+ enrich_token_usage=should_enrich_metrics,
591
+ get_common_metrics_attributes=metrics_common_attributes,
592
+ )
593
+ if not instrumentor.is_instrumented_by_opentelemetry:
594
+ instrumentor.instrument()
595
+ return True
596
+ except Exception as e:
597
+ logging.error(f"Error initializing Anthropic instrumentor: {e}")
598
+ # Telemetry().log_exception(e)
599
+ return False
600
+
601
+
602
+ def init_cohere_instrumentor():
603
+ try:
604
+ if is_package_installed("cohere"):
605
+ # Telemetry().capture("instrumentation:cohere:init")
606
+ from opentelemetry.instrumentation.cohere import CohereInstrumentor
607
+
608
+ instrumentor = CohereInstrumentor(
609
+ # exception_logger=lambda e: Telemetry().log_exception(e),
610
+ )
611
+ if not instrumentor.is_instrumented_by_opentelemetry:
612
+ instrumentor.instrument()
613
+ return True
614
+ except Exception as e:
615
+ logging.error(f"Error initializing Cohere instrumentor: {e}")
616
+ # Telemetry().log_exception(e)
617
+ return False
618
+
619
+
620
+ def init_pinecone_instrumentor():
621
+ try:
622
+ if is_package_installed("pinecone"):
623
+ # Telemetry().capture("instrumentation:pinecone:init")
624
+ from opentelemetry.instrumentation.pinecone import PineconeInstrumentor
625
+
626
+ instrumentor = PineconeInstrumentor(
627
+ # exception_logger=lambda e: Telemetry().log_exception(e),
628
+ )
629
+ if not instrumentor.is_instrumented_by_opentelemetry:
630
+ instrumentor.instrument()
631
+ return True
632
+ except Exception as e:
633
+ logging.error(f"Error initializing Pinecone instrumentor: {e}")
634
+ # Telemetry().log_exception(e)
635
+ return False
636
+
637
+
638
+ def init_qdrant_instrumentor():
639
+ try:
640
+ if is_package_installed("qdrant_client"):
641
+ # Telemetry().capture("instrumentation:qdrant:init")
642
+ from opentelemetry.instrumentation.qdrant import QdrantInstrumentor
643
+
644
+ instrumentor = QdrantInstrumentor(
645
+ # exception_logger=lambda e: Telemetry().log_exception(e),
646
+ )
647
+ if not instrumentor.is_instrumented_by_opentelemetry:
648
+ instrumentor.instrument()
649
+ except Exception as e:
650
+ logging.error(f"Error initializing Qdrant instrumentor: {e}")
651
+ # Telemetry().log_exception(e)
652
+ return False
653
+
654
+
655
+ def init_chroma_instrumentor():
656
+ try:
657
+ if is_package_installed("chromadb"):
658
+ # Telemetry().capture("instrumentation:chromadb:init")
659
+ from opentelemetry.instrumentation.chromadb import ChromaInstrumentor
660
+
661
+ instrumentor = ChromaInstrumentor(
662
+ # exception_logger=lambda e: Telemetry().log_exception(e),
663
+ )
664
+ if not instrumentor.is_instrumented_by_opentelemetry:
665
+ instrumentor.instrument()
666
+ return True
667
+ except Exception as e:
668
+ logging.error(f"Error initializing Chroma instrumentor: {e}")
669
+ # Telemetry().log_exception(e)
670
+ return False
671
+
672
+
673
+ def init_google_generativeai_instrumentor():
674
+ try:
675
+ if is_package_installed("google.generativeai"):
676
+ # Telemetry().capture("instrumentation:gemini:init")
677
+ from opentelemetry.instrumentation.google_generativeai import (
678
+ GoogleGenerativeAiInstrumentor,
679
+ )
680
+
681
+ instrumentor = GoogleGenerativeAiInstrumentor(
682
+ # exception_logger=lambda e: Telemetry().log_exception(e),
683
+ )
684
+ if not instrumentor.is_instrumented_by_opentelemetry:
685
+ instrumentor.instrument()
686
+ return True
687
+ except Exception as e:
688
+ logging.error(f"Error initializing Gemini instrumentor: {e}")
689
+ # Telemetry().log_exception(e)
690
+ return False
691
+
692
+
693
+ def init_haystack_instrumentor():
694
+ try:
695
+ if is_package_installed("haystack"):
696
+ # Telemetry().capture("instrumentation:haystack:init")
697
+ from opentelemetry.instrumentation.haystack import HaystackInstrumentor
698
+
699
+ instrumentor = HaystackInstrumentor(
700
+ # exception_logger=lambda e: Telemetry().log_exception(e),
701
+ )
702
+ if not instrumentor.is_instrumented_by_opentelemetry:
703
+ instrumentor.instrument()
704
+ return True
705
+ except Exception as e:
706
+ logging.error(f"Error initializing Haystack instrumentor: {e}")
707
+ # Telemetry().log_exception(e)
708
+ return False
709
+
710
+
711
+ def init_langchain_instrumentor():
712
+ try:
713
+ if is_package_installed("langchain"):
714
+ # Telemetry().capture("instrumentation:langchain:init")
715
+ from opentelemetry.instrumentation.langchain import LangchainInstrumentor
716
+
717
+ instrumentor = LangchainInstrumentor(
718
+ # exception_logger=lambda e: Telemetry().log_exception(e),
719
+ )
720
+ if not instrumentor.is_instrumented_by_opentelemetry:
721
+ instrumentor.instrument()
722
+ return True
723
+ except Exception as e:
724
+ logging.error(f"Error initializing LangChain instrumentor: {e}")
725
+ # Telemetry().log_exception(e)
726
+ return False
727
+
728
+
729
+ def init_mistralai_instrumentor():
730
+ try:
731
+ if is_package_installed("mistralai"):
732
+ # Telemetry().capture("instrumentation:mistralai:init")
733
+ from opentelemetry.instrumentation.mistralai import MistralAiInstrumentor
734
+
735
+ instrumentor = MistralAiInstrumentor(
736
+ # exception_logger=lambda e: Telemetry().log_exception(e),
737
+ )
738
+ if not instrumentor.is_instrumented_by_opentelemetry:
739
+ instrumentor.instrument()
740
+ return True
741
+ except Exception as e:
742
+ logging.error(f"Error initializing MistralAI instrumentor: {e}")
743
+ # Telemetry().log_exception(e)
744
+ return False
745
+
746
+
747
+ def init_ollama_instrumentor():
748
+ try:
749
+ if is_package_installed("ollama"):
750
+ # Telemetry().capture("instrumentation:ollama:init")
751
+ from opentelemetry.instrumentation.ollama import OllamaInstrumentor
752
+
753
+ instrumentor = OllamaInstrumentor(
754
+ # exception_logger=lambda e: Telemetry().log_exception(e),
755
+ )
756
+ if not instrumentor.is_instrumented_by_opentelemetry:
757
+ instrumentor.instrument()
758
+ return True
759
+ except Exception as e:
760
+ logging.error(f"Error initializing Ollama instrumentor: {e}")
761
+ # Telemetry().log_exception(e)
762
+ return False
763
+
764
+
765
+ def init_transformers_instrumentor():
766
+ try:
767
+ if is_package_installed("transformers"):
768
+ # Telemetry().capture("instrumentation:transformers:init")
769
+ from opentelemetry.instrumentation.transformers import (
770
+ TransformersInstrumentor,
771
+ )
772
+
773
+ instrumentor = TransformersInstrumentor(
774
+ # exception_logger=lambda e: Telemetry().log_exception(e),
775
+ )
776
+ if not instrumentor.is_instrumented_by_opentelemetry:
777
+ instrumentor.instrument()
778
+ return True
779
+ except Exception as e:
780
+ logging.error(f"Error initializing Transformers instrumentor: {e}")
781
+ # Telemetry().log_exception(e)
782
+ return False
783
+
784
+
785
+ def init_together_instrumentor():
786
+ try:
787
+ if is_package_installed("together"):
788
+ # Telemetry().capture("instrumentation:together:init")
789
+ from opentelemetry.instrumentation.together import TogetherAiInstrumentor
790
+
791
+ instrumentor = TogetherAiInstrumentor(
792
+ # exception_logger=lambda e: Telemetry().log_exception(e),
793
+ )
794
+ if not instrumentor.is_instrumented_by_opentelemetry:
795
+ instrumentor.instrument()
796
+ return True
797
+ except Exception as e:
798
+ logging.error(f"Error initializing TogetherAI instrumentor: {e}")
799
+ # Telemetry().log_exception(e)
800
+ return False
801
+
802
+
803
+ def init_llama_index_instrumentor():
804
+ try:
805
+ if is_package_installed("llama-index") or is_package_installed("llama_index"):
806
+ # Telemetry().capture("instrumentation:llamaindex:init")
807
+ from opentelemetry.instrumentation.llamaindex import LlamaIndexInstrumentor
808
+
809
+ instrumentor = LlamaIndexInstrumentor(
810
+ # exception_logger=lambda e: Telemetry().log_exception(e),
811
+ )
812
+ if not instrumentor.is_instrumented_by_opentelemetry:
813
+ instrumentor.instrument()
814
+ return True
815
+ except Exception as e:
816
+ logging.error(f"Error initializing LlamaIndex instrumentor: {e}")
817
+ # Telemetry().log_exception(e)
818
+ return False
819
+
820
+
821
+ def init_milvus_instrumentor():
822
+ try:
823
+ if is_package_installed("pymilvus"):
824
+ # Telemetry().capture("instrumentation:milvus:init")
825
+ from opentelemetry.instrumentation.milvus import MilvusInstrumentor
826
+
827
+ instrumentor = MilvusInstrumentor(
828
+ # exception_logger=lambda e: Telemetry().log_exception(e),
829
+ )
830
+ if not instrumentor.is_instrumented_by_opentelemetry:
831
+ instrumentor.instrument()
832
+ return True
833
+ except Exception as e:
834
+ logging.error(f"Error initializing Milvus instrumentor: {e}")
835
+ # Telemetry().log_exception(e)
836
+ return False
837
+
838
+
839
+ def init_requests_instrumentor():
840
+ try:
841
+ if is_package_installed("requests"):
842
+ from opentelemetry.instrumentation.requests import RequestsInstrumentor
843
+
844
+ instrumentor = RequestsInstrumentor()
845
+ if not instrumentor.is_instrumented_by_opentelemetry:
846
+ instrumentor.instrument(excluded_urls=EXCLUDED_URLS)
847
+ return True
848
+ except Exception as e:
849
+ logging.error(f"Error initializing Requests instrumentor: {e}")
850
+ # Telemetry().log_exception(e)
851
+ return False
852
+
853
+
854
+ def init_urllib3_instrumentor():
855
+ try:
856
+ if is_package_installed("urllib3"):
857
+ from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor
858
+
859
+ instrumentor = URLLib3Instrumentor()
860
+ if not instrumentor.is_instrumented_by_opentelemetry:
861
+ instrumentor.instrument(excluded_urls=EXCLUDED_URLS)
862
+ return True
863
+ except Exception as e:
864
+ logging.error(f"Error initializing urllib3 instrumentor: {e}")
865
+ # Telemetry().log_exception(e)
866
+ return False
867
+
868
+
869
+ def init_pymysql_instrumentor():
870
+ try:
871
+ if is_package_installed("sqlalchemy"):
872
+ from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
873
+
874
+ instrumentor = SQLAlchemyInstrumentor()
875
+ if not instrumentor.is_instrumented_by_opentelemetry:
876
+ instrumentor.instrument()
877
+ return True
878
+ except Exception as e:
879
+ logging.error(f"Error initializing SQLAlchemy instrumentor: {e}")
880
+ # Telemetry().log_exception(e)
881
+ return False
882
+
883
+
884
+ def init_bedrock_instrumentor(should_enrich_metrics: bool):
885
+ try:
886
+ if is_package_installed("boto3"):
887
+ from opentelemetry.instrumentation.bedrock import BedrockInstrumentor
888
+
889
+ instrumentor = BedrockInstrumentor(
890
+ # exception_logger=lambda e: Telemetry().log_exception(e),
891
+ enrich_token_usage=should_enrich_metrics,
892
+ )
893
+ if not instrumentor.is_instrumented_by_opentelemetry:
894
+ instrumentor.instrument()
895
+ return True
896
+ except Exception as e:
897
+ logging.error(f"Error initializing Bedrock instrumentor: {e}")
898
+ # Telemetry().log_exception(e)
899
+ return False
900
+
901
+
902
+ def init_replicate_instrumentor():
903
+ try:
904
+ if is_package_installed("replicate"):
905
+ # Telemetry().capture("instrumentation:replicate:init")
906
+ from opentelemetry.instrumentation.replicate import ReplicateInstrumentor
907
+
908
+ instrumentor = ReplicateInstrumentor(
909
+ # exception_logger=lambda e: Telemetry().log_exception(e),
910
+ )
911
+ if not instrumentor.is_instrumented_by_opentelemetry:
912
+ instrumentor.instrument()
913
+ return True
914
+ except Exception as e:
915
+ logging.error(f"Error initializing Replicate instrumentor: {e}")
916
+ # Telemetry().log_exception(e)
917
+ return False
918
+
919
+
920
+ def init_vertexai_instrumentor():
921
+ try:
922
+ if is_package_installed("vertexai"):
923
+ # Telemetry().capture("instrumentation:vertexai:init")
924
+ from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor
925
+
926
+ instrumentor = VertexAIInstrumentor(
927
+ # exception_logger=lambda e: Telemetry().log_exception(e),
928
+ )
929
+ if not instrumentor.is_instrumented_by_opentelemetry:
930
+ instrumentor.instrument()
931
+ return True
932
+ except Exception as e:
933
+ logging.warning(f"Error initializing Vertex AI instrumentor: {e}")
934
+ # Telemetry().log_exception(e)
935
+ return False
936
+
937
+
938
+ def init_watsonx_instrumentor():
939
+ try:
940
+ if is_package_installed("ibm-watsonx-ai") or is_package_installed(
941
+ "ibm-watson-machine-learning"
942
+ ):
943
+ # Telemetry().capture("instrumentation:watsonx:init")
944
+ from opentelemetry.instrumentation.watsonx import WatsonxInstrumentor
945
+
946
+ instrumentor = WatsonxInstrumentor(
947
+ # exception_logger=lambda e: Telemetry().log_exception(e),
948
+ )
949
+ if not instrumentor.is_instrumented_by_opentelemetry:
950
+ instrumentor.instrument()
951
+ return True
952
+ except Exception as e:
953
+ logging.warning(f"Error initializing Watsonx instrumentor: {e}")
954
+ # Telemetry().log_exception(e)
955
+ return False
956
+
957
+
958
+ def init_weaviate_instrumentor():
959
+ try:
960
+ if is_package_installed("weaviate"):
961
+ # Telemetry().capture("instrumentation:weaviate:init")
962
+ from opentelemetry.instrumentation.weaviate import WeaviateInstrumentor
963
+
964
+ instrumentor = WeaviateInstrumentor(
965
+ # exception_logger=lambda e: Telemetry().log_exception(e),
966
+ )
967
+ if not instrumentor.is_instrumented_by_opentelemetry:
968
+ instrumentor.instrument()
969
+ return True
970
+ except Exception as e:
971
+ logging.warning(f"Error initializing Weaviate instrumentor: {e}")
972
+ # Telemetry().log_exception(e)
973
+ return False
974
+
975
+
976
+ def init_alephalpha_instrumentor():
977
+ try:
978
+ if is_package_installed("aleph_alpha_client"):
979
+ # Telemetry().capture("instrumentation:alephalpha:init")
980
+ from opentelemetry.instrumentation.alephalpha import AlephAlphaInstrumentor
981
+
982
+ instrumentor = AlephAlphaInstrumentor(
983
+ # exception_logger=lambda e: Telemetry().log_exception(e),
984
+ )
985
+ if not instrumentor.is_instrumented_by_opentelemetry:
986
+ instrumentor.instrument()
987
+ return True
988
+ except Exception as e:
989
+ logging.error(f"Error initializing Aleph Alpha instrumentor: {e}")
990
+ # Telemetry().log_exception(e)
991
+ return False
992
+
993
+
994
+ def init_marqo_instrumentor():
995
+ try:
996
+ if is_package_installed("marqo"):
997
+ # Telemetry().capture("instrumentation:marqo:init")
998
+ from opentelemetry.instrumentation.marqo import MarqoInstrumentor
999
+
1000
+ instrumentor = MarqoInstrumentor(
1001
+ # exception_logger=lambda e: Telemetry().log_exception(e),
1002
+ )
1003
+ if not instrumentor.is_instrumented_by_opentelemetry:
1004
+ instrumentor.instrument()
1005
+ return True
1006
+ except Exception as e:
1007
+ logging.error(f"Error initializing marqo instrumentor: {e}")
1008
+ # Telemetry().log_exception(e)
1009
+ return False
1010
+
1011
+
1012
+ def init_lancedb_instrumentor():
1013
+ try:
1014
+ if is_package_installed("lancedb"):
1015
+ # Telemetry().capture("instrumentation:lancedb:init")
1016
+ from opentelemetry.instrumentation.lancedb import LanceInstrumentor
1017
+
1018
+ instrumentor = LanceInstrumentor(
1019
+ # exception_logger=lambda e: Telemetry().log_exception(e),
1020
+ )
1021
+ if not instrumentor.is_instrumented_by_opentelemetry:
1022
+ instrumentor.instrument()
1023
+ return True
1024
+ except Exception as e:
1025
+ logging.error(f"Error initializing LanceDB instrumentor: {e}")
1026
+
1027
+
1028
+ def init_redis_instrumentor():
1029
+ try:
1030
+ if is_package_installed("redis"):
1031
+ from opentelemetry.instrumentation.redis import RedisInstrumentor
1032
+
1033
+ instrumentor = RedisInstrumentor()
1034
+ if not instrumentor.is_instrumented_by_opentelemetry:
1035
+ instrumentor.instrument(excluded_urls=EXCLUDED_URLS)
1036
+ return True
1037
+ except Exception as e:
1038
+ logging.error(f"Error initializing redis instrumentor: {e}")
1039
+ # Telemetry().log_exception(e)
1040
+ return False
1041
+
1042
+
1043
+ def init_groq_instrumentor():
1044
+ try:
1045
+ if is_package_installed("groq"):
1046
+ # Telemetry().capture("instrumentation:groq:init")
1047
+ from opentelemetry.instrumentation.groq import GroqInstrumentor
1048
+
1049
+ instrumentor = GroqInstrumentor(
1050
+ # exception_logger=lambda e: Telemetry().log_exception(e),
1051
+ )
1052
+ if not instrumentor.is_instrumented_by_opentelemetry:
1053
+ instrumentor.instrument()
1054
+ return True
1055
+ except Exception as e:
1056
+ logging.error(f"Error initializing Groq instrumentor: {e}")
1057
+ # Telemetry().log_exception(e)
1058
+ return False
1059
+
1060
+
1061
+ def metrics_common_attributes():
1062
+ common_attributes = {}
1063
+ workflow_name = get_value("workflow_name")
1064
+ if workflow_name is not None:
1065
+ common_attributes[SpanAttributes.TRACELOOP_WORKFLOW_NAME] = workflow_name
1066
+
1067
+ entity_name = get_value("entity_name")
1068
+ if entity_name is not None:
1069
+ common_attributes[SpanAttributes.TRACELOOP_ENTITY_NAME] = entity_name
1070
+
1071
+ association_properties = get_value("association_properties")
1072
+ if association_properties is not None:
1073
+ for key, value in association_properties.items():
1074
+ common_attributes[
1075
+ f"{SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.{key}"
1076
+ ] = value
1077
+
1078
+ return common_attributes