lmnr 0.5.3__py3-none-any.whl → 0.6.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.
- lmnr/__init__.py +6 -1
- lmnr/opentelemetry_lib/__init__.py +16 -36
- lmnr/opentelemetry_lib/decorators/__init__.py +219 -0
- lmnr/opentelemetry_lib/tracing/__init__.py +139 -1
- lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +398 -0
- lmnr/opentelemetry_lib/tracing/attributes.py +14 -7
- lmnr/opentelemetry_lib/tracing/context_properties.py +53 -0
- lmnr/opentelemetry_lib/tracing/exporter.py +60 -0
- lmnr/opentelemetry_lib/tracing/instruments.py +121 -0
- lmnr/opentelemetry_lib/tracing/processor.py +96 -0
- lmnr/opentelemetry_lib/tracing/{context_manager.py → tracer.py} +6 -1
- lmnr/opentelemetry_lib/utils/package_check.py +3 -1
- lmnr/sdk/browser/browser_use_otel.py +1 -1
- lmnr/sdk/browser/pw_utils.py +8 -4
- lmnr/sdk/client/asynchronous/resources/agent.py +3 -1
- lmnr/sdk/client/synchronous/resources/agent.py +3 -1
- lmnr/sdk/decorators.py +4 -2
- lmnr/sdk/evaluations.py +3 -3
- lmnr/sdk/laminar.py +13 -31
- lmnr/sdk/utils.py +2 -3
- lmnr/version.py +1 -1
- {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/METADATA +64 -62
- {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/RECORD +26 -27
- lmnr/opentelemetry_lib/config/__init__.py +0 -12
- lmnr/opentelemetry_lib/decorators/base.py +0 -210
- lmnr/opentelemetry_lib/instruments.py +0 -42
- lmnr/opentelemetry_lib/tracing/content_allow_list.py +0 -24
- lmnr/opentelemetry_lib/tracing/tracing.py +0 -1016
- lmnr/opentelemetry_lib/utils/in_memory_span_exporter.py +0 -61
- {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/LICENSE +0 -0
- {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/WHEEL +0 -0
- {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,398 @@
|
|
1
|
+
import abc
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
5
|
+
|
6
|
+
from lmnr.opentelemetry_lib.utils.package_check import is_package_installed
|
7
|
+
|
8
|
+
|
9
|
+
class InstrumentorInitializer(abc.ABC):
|
10
|
+
@abc.abstractmethod
|
11
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
12
|
+
pass
|
13
|
+
|
14
|
+
|
15
|
+
class AlephAlphaInstrumentorInitializer(InstrumentorInitializer):
|
16
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
17
|
+
if not is_package_installed("aleph_alpha_client"):
|
18
|
+
return None
|
19
|
+
if not is_package_installed("opentelemetry-instrumentation-alephalpha"):
|
20
|
+
return None
|
21
|
+
|
22
|
+
from opentelemetry.instrumentation.alephalpha import AlephAlphaInstrumentor
|
23
|
+
|
24
|
+
return AlephAlphaInstrumentor()
|
25
|
+
|
26
|
+
|
27
|
+
class AnthropicInstrumentorInitializer(InstrumentorInitializer):
|
28
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
29
|
+
if not is_package_installed("anthropic"):
|
30
|
+
return None
|
31
|
+
if not is_package_installed("opentelemetry-instrumentation-anthropic"):
|
32
|
+
return None
|
33
|
+
|
34
|
+
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
35
|
+
|
36
|
+
return AnthropicInstrumentor(
|
37
|
+
upload_base64_image=None,
|
38
|
+
)
|
39
|
+
|
40
|
+
|
41
|
+
class BedrockInstrumentorInitializer(InstrumentorInitializer):
|
42
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
43
|
+
if not is_package_installed("boto3"):
|
44
|
+
return None
|
45
|
+
if not is_package_installed("opentelemetry-instrumentation-bedrock"):
|
46
|
+
return None
|
47
|
+
|
48
|
+
from opentelemetry.instrumentation.bedrock import BedrockInstrumentor
|
49
|
+
|
50
|
+
return BedrockInstrumentor()
|
51
|
+
|
52
|
+
|
53
|
+
class BrowserUseInstrumentorInitializer(InstrumentorInitializer):
|
54
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
55
|
+
if not is_package_installed("browser-use"):
|
56
|
+
return None
|
57
|
+
|
58
|
+
from lmnr.sdk.browser.browser_use_otel import BrowserUseInstrumentor
|
59
|
+
|
60
|
+
return BrowserUseInstrumentor()
|
61
|
+
|
62
|
+
|
63
|
+
class ChromaInstrumentorInitializer(InstrumentorInitializer):
|
64
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
65
|
+
if not is_package_installed("chromadb"):
|
66
|
+
return None
|
67
|
+
if not is_package_installed("opentelemetry-instrumentation-chromadb"):
|
68
|
+
return None
|
69
|
+
|
70
|
+
from opentelemetry.instrumentation.chromadb import ChromaInstrumentor
|
71
|
+
|
72
|
+
return ChromaInstrumentor()
|
73
|
+
|
74
|
+
|
75
|
+
class CohereInstrumentorInitializer(InstrumentorInitializer):
|
76
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
77
|
+
if not is_package_installed("cohere"):
|
78
|
+
return None
|
79
|
+
if not is_package_installed("opentelemetry-instrumentation-cohere"):
|
80
|
+
return None
|
81
|
+
|
82
|
+
from opentelemetry.instrumentation.cohere import CohereInstrumentor
|
83
|
+
|
84
|
+
return CohereInstrumentor()
|
85
|
+
|
86
|
+
|
87
|
+
class CrewAIInstrumentorInitializer(InstrumentorInitializer):
|
88
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
89
|
+
if not is_package_installed("crewai"):
|
90
|
+
return None
|
91
|
+
if not is_package_installed("opentelemetry-instrumentation-crewai"):
|
92
|
+
return None
|
93
|
+
|
94
|
+
from opentelemetry.instrumentation.crewai import CrewAiInstrumentor
|
95
|
+
|
96
|
+
return CrewAiInstrumentor()
|
97
|
+
|
98
|
+
|
99
|
+
class GoogleGenerativeAIInstrumentorInitializer(InstrumentorInitializer):
|
100
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
101
|
+
if not is_package_installed("google-generativeai"):
|
102
|
+
return None
|
103
|
+
if not is_package_installed(
|
104
|
+
"opentelemetry-instrumentation-google-generativeai"
|
105
|
+
):
|
106
|
+
return None
|
107
|
+
|
108
|
+
from opentelemetry.instrumentation.google_generativeai import (
|
109
|
+
GoogleGenerativeAiInstrumentor,
|
110
|
+
)
|
111
|
+
|
112
|
+
return GoogleGenerativeAiInstrumentor()
|
113
|
+
|
114
|
+
|
115
|
+
class GoogleGenAIInstrumentorInitializer(InstrumentorInitializer):
|
116
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
117
|
+
if not is_package_installed("google-genai"):
|
118
|
+
return None
|
119
|
+
|
120
|
+
from ..opentelemetry.instrumentation.google_genai import (
|
121
|
+
GoogleGenAiSdkInstrumentor,
|
122
|
+
)
|
123
|
+
|
124
|
+
return GoogleGenAiSdkInstrumentor()
|
125
|
+
|
126
|
+
|
127
|
+
class GroqInstrumentorInitializer(InstrumentorInitializer):
|
128
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
129
|
+
if not is_package_installed("groq"):
|
130
|
+
return None
|
131
|
+
if not is_package_installed("opentelemetry-instrumentation-groq"):
|
132
|
+
return None
|
133
|
+
|
134
|
+
from opentelemetry.instrumentation.groq import GroqInstrumentor
|
135
|
+
|
136
|
+
return GroqInstrumentor()
|
137
|
+
|
138
|
+
|
139
|
+
class HaystackInstrumentorInitializer(InstrumentorInitializer):
|
140
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
141
|
+
if not is_package_installed("haystack"):
|
142
|
+
return None
|
143
|
+
if not is_package_installed("opentelemetry-instrumentation-haystack"):
|
144
|
+
return None
|
145
|
+
|
146
|
+
from opentelemetry.instrumentation.haystack import HaystackInstrumentor
|
147
|
+
|
148
|
+
return HaystackInstrumentor()
|
149
|
+
|
150
|
+
|
151
|
+
class LanceDBInstrumentorInitializer(InstrumentorInitializer):
|
152
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
153
|
+
if not is_package_installed("lancedb"):
|
154
|
+
return None
|
155
|
+
if not is_package_installed("opentelemetry-instrumentation-lancedb"):
|
156
|
+
return None
|
157
|
+
|
158
|
+
from opentelemetry.instrumentation.lancedb import LanceInstrumentor
|
159
|
+
|
160
|
+
return LanceInstrumentor()
|
161
|
+
|
162
|
+
|
163
|
+
class LangchainInstrumentorInitializer(InstrumentorInitializer):
|
164
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
165
|
+
if not is_package_installed("langchain"):
|
166
|
+
return None
|
167
|
+
if not is_package_installed("opentelemetry-instrumentation-langchain"):
|
168
|
+
return None
|
169
|
+
|
170
|
+
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
171
|
+
|
172
|
+
return LangchainInstrumentor()
|
173
|
+
|
174
|
+
|
175
|
+
class LlamaIndexInstrumentorInitializer(InstrumentorInitializer):
|
176
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
177
|
+
if not (
|
178
|
+
is_package_installed("llama-index") or is_package_installed("llama_index")
|
179
|
+
):
|
180
|
+
return None
|
181
|
+
if not is_package_installed("opentelemetry-instrumentation-llamaindex"):
|
182
|
+
return None
|
183
|
+
|
184
|
+
from opentelemetry.instrumentation.llamaindex import LlamaIndexInstrumentor
|
185
|
+
|
186
|
+
return LlamaIndexInstrumentor()
|
187
|
+
|
188
|
+
|
189
|
+
class MarqoInstrumentorInitializer(InstrumentorInitializer):
|
190
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
191
|
+
if not is_package_installed("marqo"):
|
192
|
+
return None
|
193
|
+
if not is_package_installed("opentelemetry-instrumentation-marqo"):
|
194
|
+
return None
|
195
|
+
|
196
|
+
from opentelemetry.instrumentation.marqo import MarqoInstrumentor
|
197
|
+
|
198
|
+
return MarqoInstrumentor()
|
199
|
+
|
200
|
+
|
201
|
+
class MCPInstrumentorInitializer(InstrumentorInitializer):
|
202
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
203
|
+
if not is_package_installed("mcp"):
|
204
|
+
return None
|
205
|
+
if not is_package_installed("opentelemetry-instrumentation-mcp"):
|
206
|
+
return None
|
207
|
+
|
208
|
+
from opentelemetry.instrumentation.mcp import MCPInstrumentor
|
209
|
+
|
210
|
+
return MCPInstrumentor()
|
211
|
+
|
212
|
+
|
213
|
+
class MilvusInstrumentorInitializer(InstrumentorInitializer):
|
214
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
215
|
+
if not is_package_installed("pymilvus"):
|
216
|
+
return None
|
217
|
+
if not is_package_installed("opentelemetry-instrumentation-milvus"):
|
218
|
+
return None
|
219
|
+
|
220
|
+
from opentelemetry.instrumentation.milvus import MilvusInstrumentor
|
221
|
+
|
222
|
+
return MilvusInstrumentor()
|
223
|
+
|
224
|
+
|
225
|
+
class MistralInstrumentorInitializer(InstrumentorInitializer):
|
226
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
227
|
+
if not is_package_installed("mistralai"):
|
228
|
+
return None
|
229
|
+
if not is_package_installed("opentelemetry-instrumentation-mistralai"):
|
230
|
+
return None
|
231
|
+
|
232
|
+
from opentelemetry.instrumentation.mistralai import MistralAiInstrumentor
|
233
|
+
|
234
|
+
return MistralAiInstrumentor()
|
235
|
+
|
236
|
+
|
237
|
+
class OllamaInstrumentorInitializer(InstrumentorInitializer):
|
238
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
239
|
+
if not is_package_installed("ollama"):
|
240
|
+
return None
|
241
|
+
if not is_package_installed("opentelemetry-instrumentation-ollama"):
|
242
|
+
return None
|
243
|
+
|
244
|
+
from opentelemetry.instrumentation.ollama import OllamaInstrumentor
|
245
|
+
|
246
|
+
return OllamaInstrumentor()
|
247
|
+
|
248
|
+
|
249
|
+
class OpenAIInstrumentorInitializer(InstrumentorInitializer):
|
250
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
251
|
+
if not is_package_installed("openai"):
|
252
|
+
return None
|
253
|
+
if not is_package_installed("opentelemetry-instrumentation-openai"):
|
254
|
+
return None
|
255
|
+
|
256
|
+
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
|
257
|
+
|
258
|
+
return OpenAIInstrumentor(
|
259
|
+
# Default in the package provided is an empty function, which
|
260
|
+
# results in dropping the image data if we don't explicitly
|
261
|
+
# set it to None.
|
262
|
+
upload_base64_image=None,
|
263
|
+
)
|
264
|
+
|
265
|
+
|
266
|
+
class PatchrightInstrumentorInitializer(InstrumentorInitializer):
|
267
|
+
def init_instrumentor(
|
268
|
+
self, client, async_client, *args, **kwargs
|
269
|
+
) -> Optional[BaseInstrumentor]:
|
270
|
+
if not is_package_installed("patchright"):
|
271
|
+
return None
|
272
|
+
|
273
|
+
from lmnr.sdk.browser.patchright_otel import PatchrightInstrumentor
|
274
|
+
|
275
|
+
return PatchrightInstrumentor(client, async_client)
|
276
|
+
|
277
|
+
|
278
|
+
class PineconeInstrumentorInitializer(InstrumentorInitializer):
|
279
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
280
|
+
if not is_package_installed("pinecone"):
|
281
|
+
return None
|
282
|
+
if not is_package_installed("opentelemetry-instrumentation-pinecone"):
|
283
|
+
return None
|
284
|
+
|
285
|
+
from opentelemetry.instrumentation.pinecone import PineconeInstrumentor
|
286
|
+
|
287
|
+
return PineconeInstrumentor()
|
288
|
+
|
289
|
+
|
290
|
+
class PlaywrightInstrumentorInitializer(InstrumentorInitializer):
|
291
|
+
def init_instrumentor(
|
292
|
+
self, client, async_client, *args, **kwargs
|
293
|
+
) -> Optional[BaseInstrumentor]:
|
294
|
+
if not is_package_installed("playwright"):
|
295
|
+
return None
|
296
|
+
|
297
|
+
from lmnr.sdk.browser.playwright_otel import PlaywrightInstrumentor
|
298
|
+
|
299
|
+
return PlaywrightInstrumentor(client, async_client)
|
300
|
+
|
301
|
+
|
302
|
+
class QdrantInstrumentorInitializer(InstrumentorInitializer):
|
303
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
304
|
+
if not is_package_installed("qdrant_client"):
|
305
|
+
return None
|
306
|
+
if not is_package_installed("opentelemetry-instrumentation-qdrant"):
|
307
|
+
return None
|
308
|
+
|
309
|
+
from opentelemetry.instrumentation.qdrant import QdrantInstrumentor
|
310
|
+
|
311
|
+
return QdrantInstrumentor()
|
312
|
+
|
313
|
+
|
314
|
+
class ReplicateInstrumentorInitializer(InstrumentorInitializer):
|
315
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
316
|
+
if not is_package_installed("replicate"):
|
317
|
+
return None
|
318
|
+
if not is_package_installed("opentelemetry-instrumentation-replicate"):
|
319
|
+
return None
|
320
|
+
|
321
|
+
from opentelemetry.instrumentation.replicate import ReplicateInstrumentor
|
322
|
+
|
323
|
+
return ReplicateInstrumentor()
|
324
|
+
|
325
|
+
|
326
|
+
class SageMakerInstrumentorInitializer(InstrumentorInitializer):
|
327
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
328
|
+
if not is_package_installed("boto3"):
|
329
|
+
return None
|
330
|
+
if not is_package_installed("opentelemetry-instrumentation-sagemaker"):
|
331
|
+
return None
|
332
|
+
|
333
|
+
from opentelemetry.instrumentation.sagemaker import SageMakerInstrumentor
|
334
|
+
|
335
|
+
return SageMakerInstrumentor()
|
336
|
+
|
337
|
+
|
338
|
+
class TogetherInstrumentorInitializer(InstrumentorInitializer):
|
339
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
340
|
+
if not is_package_installed("together"):
|
341
|
+
return None
|
342
|
+
if not is_package_installed("opentelemetry-instrumentation-together"):
|
343
|
+
return None
|
344
|
+
|
345
|
+
from opentelemetry.instrumentation.together import TogetherAiInstrumentor
|
346
|
+
|
347
|
+
return TogetherAiInstrumentor()
|
348
|
+
|
349
|
+
|
350
|
+
class TransformersInstrumentorInitializer(InstrumentorInitializer):
|
351
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
352
|
+
if not is_package_installed("transformers"):
|
353
|
+
return None
|
354
|
+
if not is_package_installed("opentelemetry-instrumentation-transformers"):
|
355
|
+
return None
|
356
|
+
|
357
|
+
from opentelemetry.instrumentation.transformers import TransformersInstrumentor
|
358
|
+
|
359
|
+
return TransformersInstrumentor()
|
360
|
+
|
361
|
+
|
362
|
+
class VertexAIInstrumentorInitializer(InstrumentorInitializer):
|
363
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
364
|
+
if not is_package_installed("vertexai"):
|
365
|
+
return None
|
366
|
+
if not is_package_installed("opentelemetry-instrumentation-vertexai"):
|
367
|
+
return None
|
368
|
+
|
369
|
+
from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor
|
370
|
+
|
371
|
+
return VertexAIInstrumentor()
|
372
|
+
|
373
|
+
|
374
|
+
class WatsonxInstrumentorInitializer(InstrumentorInitializer):
|
375
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
376
|
+
if not (
|
377
|
+
is_package_installed("ibm-watsonx-ai")
|
378
|
+
or is_package_installed("ibm-watson-machine-learning")
|
379
|
+
):
|
380
|
+
return None
|
381
|
+
if not is_package_installed("opentelemetry-instrumentation-watsonx"):
|
382
|
+
return None
|
383
|
+
|
384
|
+
from opentelemetry.instrumentation.watsonx import WatsonxInstrumentor
|
385
|
+
|
386
|
+
return WatsonxInstrumentor()
|
387
|
+
|
388
|
+
|
389
|
+
class WeaviateInstrumentorInitializer(InstrumentorInitializer):
|
390
|
+
def init_instrumentor(self, *args, **kwargs) -> Optional[BaseInstrumentor]:
|
391
|
+
if not is_package_installed("weaviate"):
|
392
|
+
return None
|
393
|
+
if not is_package_installed("opentelemetry-instrumentation-weaviate"):
|
394
|
+
return None
|
395
|
+
|
396
|
+
from opentelemetry.instrumentation.weaviate import WeaviateInstrumentor
|
397
|
+
|
398
|
+
return WeaviateInstrumentor()
|
@@ -1,4 +1,12 @@
|
|
1
1
|
from enum import Enum
|
2
|
+
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import (
|
3
|
+
GEN_AI_SYSTEM,
|
4
|
+
GEN_AI_REQUEST_MODEL,
|
5
|
+
GEN_AI_RESPONSE_MODEL,
|
6
|
+
GEN_AI_USAGE_INPUT_TOKENS,
|
7
|
+
GEN_AI_USAGE_OUTPUT_TOKENS,
|
8
|
+
GEN_AI_RESPONSE_ID,
|
9
|
+
)
|
2
10
|
from opentelemetry.semconv_ai import SpanAttributes
|
3
11
|
|
4
12
|
SPAN_INPUT = "lmnr.span.input"
|
@@ -21,14 +29,12 @@ TRACING_LEVEL = "tracing_level"
|
|
21
29
|
class Attributes(Enum):
|
22
30
|
# == This is the minimum set of attributes for a proper LLM span ==
|
23
31
|
#
|
24
|
-
|
25
|
-
|
26
|
-
# not SpanAttributes.LLM_USAGE_COMPLETION_TOKENS,
|
27
|
-
OUTPUT_TOKEN_COUNT = "gen_ai.usage.output_tokens"
|
32
|
+
INPUT_TOKEN_COUNT = GEN_AI_USAGE_INPUT_TOKENS
|
33
|
+
OUTPUT_TOKEN_COUNT = GEN_AI_USAGE_OUTPUT_TOKENS
|
28
34
|
TOTAL_TOKEN_COUNT = SpanAttributes.LLM_USAGE_TOTAL_TOKENS
|
29
|
-
PROVIDER =
|
30
|
-
REQUEST_MODEL =
|
31
|
-
RESPONSE_MODEL =
|
35
|
+
PROVIDER = GEN_AI_SYSTEM
|
36
|
+
REQUEST_MODEL = GEN_AI_REQUEST_MODEL
|
37
|
+
RESPONSE_MODEL = GEN_AI_RESPONSE_MODEL
|
32
38
|
#
|
33
39
|
## == End of minimum set ==
|
34
40
|
# == Additional attributes ==
|
@@ -36,5 +42,6 @@ class Attributes(Enum):
|
|
36
42
|
INPUT_COST = "gen_ai.usage.input_cost"
|
37
43
|
OUTPUT_COST = "gen_ai.usage.output_cost"
|
38
44
|
TOTAL_COST = "gen_ai.usage.cost"
|
45
|
+
RESPONSE_ID = GEN_AI_RESPONSE_ID
|
39
46
|
#
|
40
47
|
# == End of additional attributes ==
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import copy
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from lmnr.opentelemetry_lib.tracing.attributes import (
|
5
|
+
ASSOCIATION_PROPERTIES,
|
6
|
+
TRACING_LEVEL,
|
7
|
+
)
|
8
|
+
|
9
|
+
from opentelemetry.context import Context, attach, set_value, get_value
|
10
|
+
from opentelemetry.trace import Span
|
11
|
+
from opentelemetry import trace
|
12
|
+
|
13
|
+
|
14
|
+
def set_association_properties(properties: dict) -> None:
|
15
|
+
attach(set_value("association_properties", properties))
|
16
|
+
|
17
|
+
span = trace.get_current_span()
|
18
|
+
_set_association_properties_attributes(span, properties)
|
19
|
+
|
20
|
+
|
21
|
+
def get_association_properties(context: Optional[Context] = None) -> dict:
|
22
|
+
return get_value("association_properties", context) or {}
|
23
|
+
|
24
|
+
|
25
|
+
def update_association_properties(
|
26
|
+
properties: dict,
|
27
|
+
set_on_current_span: bool = True,
|
28
|
+
context: Optional[Context] = None,
|
29
|
+
) -> None:
|
30
|
+
"""Only adds or updates properties that are not already present"""
|
31
|
+
association_properties = get_value("association_properties", context) or {}
|
32
|
+
association_properties.update(properties)
|
33
|
+
|
34
|
+
attach(set_value("association_properties", association_properties, context))
|
35
|
+
|
36
|
+
if set_on_current_span:
|
37
|
+
span = trace.get_current_span()
|
38
|
+
_set_association_properties_attributes(span, properties)
|
39
|
+
|
40
|
+
|
41
|
+
def remove_association_properties(properties: dict) -> None:
|
42
|
+
props: dict = copy.copy(get_value("association_properties") or {})
|
43
|
+
for k in properties.keys():
|
44
|
+
props.pop(k, None)
|
45
|
+
set_association_properties(props)
|
46
|
+
|
47
|
+
|
48
|
+
def _set_association_properties_attributes(span: Span, properties: dict) -> None:
|
49
|
+
for key, value in properties.items():
|
50
|
+
if key == TRACING_LEVEL:
|
51
|
+
span.set_attribute(f"lmnr.internal.{TRACING_LEVEL}", value)
|
52
|
+
continue
|
53
|
+
span.set_attribute(f"{ASSOCIATION_PROPERTIES}.{key}", value)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import grpc
|
2
|
+
import re
|
3
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
4
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
5
|
+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
|
6
|
+
OTLPSpanExporter,
|
7
|
+
)
|
8
|
+
from opentelemetry.exporter.otlp.proto.http import Compression
|
9
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
10
|
+
OTLPSpanExporter as HTTPOTLPSpanExporter,
|
11
|
+
)
|
12
|
+
from typing import Optional, Union
|
13
|
+
|
14
|
+
from lmnr.sdk.utils import from_env
|
15
|
+
|
16
|
+
|
17
|
+
class LaminarSpanExporter(SpanExporter):
|
18
|
+
instance: Union[OTLPSpanExporter, HTTPOTLPSpanExporter]
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
base_url: Optional[str] = None,
|
23
|
+
port: Optional[int] = None,
|
24
|
+
api_key: Optional[str] = None,
|
25
|
+
timeout_seconds: int = 30,
|
26
|
+
force_http: bool = False,
|
27
|
+
):
|
28
|
+
url = base_url or from_env("LMNR_BASE_URL") or "https://api.lmnr.ai"
|
29
|
+
url = url.rstrip("/")
|
30
|
+
if match := re.search(r":(\d{1,5})$", url):
|
31
|
+
url = url[: -len(match.group(0))]
|
32
|
+
if port is None:
|
33
|
+
port = int(match.group(1))
|
34
|
+
if port is None:
|
35
|
+
port = 443 if force_http else 8443
|
36
|
+
final_url = f"{url}:{port or 443}"
|
37
|
+
api_key = api_key or from_env("LMNR_PROJECT_API_KEY")
|
38
|
+
if force_http:
|
39
|
+
self.instance = HTTPOTLPSpanExporter(
|
40
|
+
endpoint=f"{final_url}/v1/traces",
|
41
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
42
|
+
compression=Compression.Gzip,
|
43
|
+
timeout=timeout_seconds,
|
44
|
+
)
|
45
|
+
else:
|
46
|
+
self.instance = OTLPSpanExporter(
|
47
|
+
endpoint=final_url,
|
48
|
+
headers={"authorization": f"Bearer {api_key}"},
|
49
|
+
compression=grpc.Compression.Gzip,
|
50
|
+
timeout=timeout_seconds,
|
51
|
+
)
|
52
|
+
|
53
|
+
def export(self, spans: list[ReadableSpan]) -> SpanExportResult:
|
54
|
+
return self.instance.export(spans)
|
55
|
+
|
56
|
+
def shutdown(self) -> None:
|
57
|
+
return self.instance.shutdown()
|
58
|
+
|
59
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
60
|
+
return self.instance.force_flush(timeout_millis)
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from enum import Enum
|
4
|
+
from typing import Optional, Set, Dict
|
5
|
+
|
6
|
+
from opentelemetry.trace import TracerProvider
|
7
|
+
import lmnr.opentelemetry_lib.tracing._instrument_initializers as initializers
|
8
|
+
from lmnr.sdk.client.synchronous.sync_client import LaminarClient
|
9
|
+
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
|
10
|
+
|
11
|
+
module_logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
class Instruments(Enum):
|
15
|
+
# The list of libraries which will be autoinstrumented
|
16
|
+
# if no specific instruments are provided to initialize()
|
17
|
+
ALEPHALPHA = "alephalpha"
|
18
|
+
ANTHROPIC = "anthropic"
|
19
|
+
BEDROCK = "bedrock"
|
20
|
+
BROWSER_USE = "browser_use"
|
21
|
+
CHROMA = "chroma"
|
22
|
+
COHERE = "cohere"
|
23
|
+
CREWAI = "crewai"
|
24
|
+
GOOGLE_GENERATIVEAI = "google_generativeai"
|
25
|
+
GOOGLE_GENAI = "google_genai"
|
26
|
+
GROQ = "groq"
|
27
|
+
HAYSTACK = "haystack"
|
28
|
+
LANCEDB = "lancedb"
|
29
|
+
LANGCHAIN = "langchain"
|
30
|
+
LLAMA_INDEX = "llama_index"
|
31
|
+
MARQO = "marqo"
|
32
|
+
MCP = "mcp"
|
33
|
+
MILVUS = "milvus"
|
34
|
+
MISTRAL = "mistral"
|
35
|
+
OLLAMA = "ollama"
|
36
|
+
OPENAI = "openai"
|
37
|
+
PATCHRIGHT = "patchright"
|
38
|
+
PINECONE = "pinecone"
|
39
|
+
PLAYWRIGHT = "playwright"
|
40
|
+
QDRANT = "qdrant"
|
41
|
+
REPLICATE = "replicate"
|
42
|
+
SAGEMAKER = "sagemaker"
|
43
|
+
TOGETHER = "together"
|
44
|
+
TRANSFORMERS = "transformers"
|
45
|
+
VERTEXAI = "vertexai"
|
46
|
+
WATSONX = "watsonx"
|
47
|
+
WEAVIATE = "weaviate"
|
48
|
+
|
49
|
+
|
50
|
+
INSTRUMENTATION_INITIALIZERS: Dict[
|
51
|
+
Instruments, initializers.InstrumentorInitializer
|
52
|
+
] = {
|
53
|
+
Instruments.ALEPHALPHA: initializers.AlephAlphaInstrumentorInitializer(),
|
54
|
+
Instruments.ANTHROPIC: initializers.AnthropicInstrumentorInitializer(),
|
55
|
+
Instruments.BEDROCK: initializers.BedrockInstrumentorInitializer(),
|
56
|
+
Instruments.BROWSER_USE: initializers.BrowserUseInstrumentorInitializer(),
|
57
|
+
Instruments.CHROMA: initializers.ChromaInstrumentorInitializer(),
|
58
|
+
Instruments.COHERE: initializers.CohereInstrumentorInitializer(),
|
59
|
+
Instruments.CREWAI: initializers.CrewAIInstrumentorInitializer(),
|
60
|
+
Instruments.GOOGLE_GENERATIVEAI: initializers.GoogleGenerativeAIInstrumentorInitializer(),
|
61
|
+
Instruments.GOOGLE_GENAI: initializers.GoogleGenAIInstrumentorInitializer(),
|
62
|
+
Instruments.GROQ: initializers.GroqInstrumentorInitializer(),
|
63
|
+
Instruments.HAYSTACK: initializers.HaystackInstrumentorInitializer(),
|
64
|
+
Instruments.LANCEDB: initializers.LanceDBInstrumentorInitializer(),
|
65
|
+
Instruments.LANGCHAIN: initializers.LangchainInstrumentorInitializer(),
|
66
|
+
Instruments.LLAMA_INDEX: initializers.LlamaIndexInstrumentorInitializer(),
|
67
|
+
Instruments.MARQO: initializers.MarqoInstrumentorInitializer(),
|
68
|
+
Instruments.MCP: initializers.MCPInstrumentorInitializer(),
|
69
|
+
Instruments.MILVUS: initializers.MilvusInstrumentorInitializer(),
|
70
|
+
Instruments.MISTRAL: initializers.MistralInstrumentorInitializer(),
|
71
|
+
Instruments.OLLAMA: initializers.OllamaInstrumentorInitializer(),
|
72
|
+
Instruments.OPENAI: initializers.OpenAIInstrumentorInitializer(),
|
73
|
+
Instruments.PATCHRIGHT: initializers.PatchrightInstrumentorInitializer(),
|
74
|
+
Instruments.PINECONE: initializers.PineconeInstrumentorInitializer(),
|
75
|
+
Instruments.PLAYWRIGHT: initializers.PlaywrightInstrumentorInitializer(),
|
76
|
+
Instruments.QDRANT: initializers.QdrantInstrumentorInitializer(),
|
77
|
+
Instruments.REPLICATE: initializers.ReplicateInstrumentorInitializer(),
|
78
|
+
Instruments.SAGEMAKER: initializers.SageMakerInstrumentorInitializer(),
|
79
|
+
Instruments.TOGETHER: initializers.TogetherInstrumentorInitializer(),
|
80
|
+
Instruments.TRANSFORMERS: initializers.TransformersInstrumentorInitializer(),
|
81
|
+
Instruments.VERTEXAI: initializers.VertexAIInstrumentorInitializer(),
|
82
|
+
Instruments.WATSONX: initializers.WatsonxInstrumentorInitializer(),
|
83
|
+
Instruments.WEAVIATE: initializers.WeaviateInstrumentorInitializer(),
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
def init_instrumentations(
|
88
|
+
tracer_provider: TracerProvider,
|
89
|
+
instruments: Optional[Set[Instruments]] = None,
|
90
|
+
block_instruments: Optional[Set[Instruments]] = None,
|
91
|
+
client: Optional[LaminarClient] = None,
|
92
|
+
async_client: Optional[AsyncLaminarClient] = None,
|
93
|
+
):
|
94
|
+
block_instruments = block_instruments or set()
|
95
|
+
if instruments is None:
|
96
|
+
instruments = set(Instruments)
|
97
|
+
if not isinstance(instruments, set):
|
98
|
+
instruments = set(instruments)
|
99
|
+
|
100
|
+
# Remove any instruments that were explicitly blocked
|
101
|
+
instruments = instruments - block_instruments
|
102
|
+
|
103
|
+
for instrument in instruments:
|
104
|
+
initializer = INSTRUMENTATION_INITIALIZERS.get(instrument)
|
105
|
+
if initializer is None:
|
106
|
+
module_logger.error(f"Invalid instrument: {instrument}")
|
107
|
+
continue
|
108
|
+
|
109
|
+
try:
|
110
|
+
instrumentor = initializer.init_instrumentor(client, async_client)
|
111
|
+
if instrumentor is None:
|
112
|
+
continue
|
113
|
+
if not instrumentor.is_instrumented_by_opentelemetry:
|
114
|
+
instrumentor.instrument(tracer_provider=tracer_provider)
|
115
|
+
except Exception as e:
|
116
|
+
if "No module named 'langchain_community'" in str(e):
|
117
|
+
# LangChain instrumentor does not require langchain_community,
|
118
|
+
# but throws this error if it's not installed.
|
119
|
+
continue
|
120
|
+
module_logger.error(f"Error initializing instrumentor: {e}")
|
121
|
+
continue
|