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.
Files changed (32) hide show
  1. lmnr/__init__.py +6 -1
  2. lmnr/opentelemetry_lib/__init__.py +16 -36
  3. lmnr/opentelemetry_lib/decorators/__init__.py +219 -0
  4. lmnr/opentelemetry_lib/tracing/__init__.py +139 -1
  5. lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +398 -0
  6. lmnr/opentelemetry_lib/tracing/attributes.py +14 -7
  7. lmnr/opentelemetry_lib/tracing/context_properties.py +53 -0
  8. lmnr/opentelemetry_lib/tracing/exporter.py +60 -0
  9. lmnr/opentelemetry_lib/tracing/instruments.py +121 -0
  10. lmnr/opentelemetry_lib/tracing/processor.py +96 -0
  11. lmnr/opentelemetry_lib/tracing/{context_manager.py → tracer.py} +6 -1
  12. lmnr/opentelemetry_lib/utils/package_check.py +3 -1
  13. lmnr/sdk/browser/browser_use_otel.py +1 -1
  14. lmnr/sdk/browser/pw_utils.py +8 -4
  15. lmnr/sdk/client/asynchronous/resources/agent.py +3 -1
  16. lmnr/sdk/client/synchronous/resources/agent.py +3 -1
  17. lmnr/sdk/decorators.py +4 -2
  18. lmnr/sdk/evaluations.py +3 -3
  19. lmnr/sdk/laminar.py +13 -31
  20. lmnr/sdk/utils.py +2 -3
  21. lmnr/version.py +1 -1
  22. {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/METADATA +64 -62
  23. {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/RECORD +26 -27
  24. lmnr/opentelemetry_lib/config/__init__.py +0 -12
  25. lmnr/opentelemetry_lib/decorators/base.py +0 -210
  26. lmnr/opentelemetry_lib/instruments.py +0 -42
  27. lmnr/opentelemetry_lib/tracing/content_allow_list.py +0 -24
  28. lmnr/opentelemetry_lib/tracing/tracing.py +0 -1016
  29. lmnr/opentelemetry_lib/utils/in_memory_span_exporter.py +0 -61
  30. {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/LICENSE +0 -0
  31. {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/WHEEL +0 -0
  32. {lmnr-0.5.3.dist-info → lmnr-0.6.0.dist-info}/entry_points.txt +0 -0
@@ -1,210 +0,0 @@
1
- import json
2
- from functools import wraps
3
- import logging
4
- import pydantic
5
- import types
6
- from typing import Any, Literal, Optional, Union
7
-
8
- from opentelemetry import trace
9
- from opentelemetry import context as context_api
10
- from opentelemetry.trace import Span
11
-
12
- from lmnr.sdk.utils import get_input_from_func_args, is_method
13
- from lmnr.opentelemetry_lib.tracing import get_tracer
14
- from lmnr.opentelemetry_lib.tracing.attributes import SPAN_INPUT, SPAN_OUTPUT, SPAN_TYPE
15
- from lmnr.opentelemetry_lib.tracing.tracing import TracerWrapper
16
- from lmnr.opentelemetry_lib.utils.json_encoder import JSONEncoder
17
- from lmnr.opentelemetry_lib.config import MAX_MANUAL_SPAN_PAYLOAD_SIZE
18
-
19
-
20
- class CustomJSONEncoder(JSONEncoder):
21
- def default(self, o: Any) -> Any:
22
- if isinstance(o, pydantic.BaseModel):
23
- return o.model_dump_json()
24
- try:
25
- return super().default(o)
26
- except TypeError:
27
- return str(o) # Fallback to string representation for unsupported types
28
-
29
-
30
- def json_dumps(data: dict) -> str:
31
- try:
32
- return json.dumps(data, cls=CustomJSONEncoder)
33
- except Exception:
34
- # Log the exception and return a placeholder if serialization completely fails
35
- logging.warning("Failed to serialize data to JSON, type: %s", type(data))
36
- return "{}" # Return an empty JSON object as a fallback
37
-
38
-
39
- def entity_method(
40
- name: Optional[str] = None,
41
- ignore_input: bool = False,
42
- ignore_inputs: Optional[list[str]] = None,
43
- ignore_output: bool = False,
44
- span_type: Union[Literal["DEFAULT"], Literal["LLM"], Literal["TOOL"]] = "DEFAULT",
45
- ):
46
- def decorate(fn):
47
- @wraps(fn)
48
- def wrap(*args, **kwargs):
49
- if not TracerWrapper.verify_initialized():
50
- return fn(*args, **kwargs)
51
-
52
- span_name = name or fn.__name__
53
-
54
- with get_tracer() as tracer:
55
- span = tracer.start_span(span_name, attributes={SPAN_TYPE: span_type})
56
-
57
- ctx = trace.set_span_in_context(span, context_api.get_current())
58
- ctx_token = context_api.attach(ctx)
59
-
60
- try:
61
- if not ignore_input:
62
- inp = json_dumps(
63
- get_input_from_func_args(
64
- fn,
65
- is_method=is_method(fn),
66
- func_args=args,
67
- func_kwargs=kwargs,
68
- ignore_inputs=ignore_inputs,
69
- )
70
- )
71
- if len(inp) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
72
- span.set_attribute(
73
- SPAN_INPUT, "Laminar: input too large to record"
74
- )
75
- else:
76
- span.set_attribute(SPAN_INPUT, inp)
77
- except TypeError:
78
- pass
79
-
80
- try:
81
- res = fn(*args, **kwargs)
82
- except Exception as e:
83
- _process_exception(span, e)
84
- span.end()
85
- raise e
86
-
87
- # span will be ended in the generator
88
- if isinstance(res, types.GeneratorType):
89
- return _handle_generator(span, res)
90
-
91
- try:
92
- if not ignore_output:
93
- output = json_dumps(res)
94
- if len(output) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
95
- span.set_attribute(
96
- SPAN_OUTPUT, "Laminar: output too large to record"
97
- )
98
- else:
99
- span.set_attribute(SPAN_OUTPUT, output)
100
- except TypeError:
101
- pass
102
-
103
- span.end()
104
- context_api.detach(ctx_token)
105
-
106
- return res
107
-
108
- return wrap
109
-
110
- return decorate
111
-
112
-
113
- # Async Decorators
114
- def aentity_method(
115
- name: Optional[str] = None,
116
- ignore_input: bool = False,
117
- ignore_inputs: Optional[list[str]] = None,
118
- ignore_output: bool = False,
119
- span_type: Union[Literal["DEFAULT"], Literal["LLM"], Literal["TOOL"]] = "DEFAULT",
120
- ):
121
- def decorate(fn):
122
- @wraps(fn)
123
- async def wrap(*args, **kwargs):
124
- if not TracerWrapper.verify_initialized():
125
- return await fn(*args, **kwargs)
126
-
127
- span_name = name or fn.__name__
128
-
129
- with get_tracer() as tracer:
130
- span = tracer.start_span(span_name, attributes={SPAN_TYPE: span_type})
131
-
132
- ctx = trace.set_span_in_context(span, context_api.get_current())
133
- ctx_token = context_api.attach(ctx)
134
-
135
- try:
136
- if not ignore_input:
137
- inp = json_dumps(
138
- get_input_from_func_args(
139
- fn,
140
- is_method=is_method(fn),
141
- func_args=args,
142
- func_kwargs=kwargs,
143
- ignore_inputs=ignore_inputs,
144
- )
145
- )
146
- if len(inp) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
147
- span.set_attribute(
148
- SPAN_INPUT, "Laminar: input too large to record"
149
- )
150
- else:
151
- span.set_attribute(SPAN_INPUT, inp)
152
- except TypeError:
153
- pass
154
-
155
- try:
156
- res = await fn(*args, **kwargs)
157
- except Exception as e:
158
- _process_exception(span, e)
159
- span.end()
160
- raise e
161
-
162
- # span will be ended in the generator
163
- if isinstance(res, types.AsyncGeneratorType):
164
- return await _ahandle_generator(span, ctx_token, res)
165
-
166
- try:
167
- if not ignore_output:
168
- output = json_dumps(res)
169
- if len(output) > MAX_MANUAL_SPAN_PAYLOAD_SIZE:
170
- span.set_attribute(
171
- SPAN_OUTPUT, "Laminar: output too large to record"
172
- )
173
- else:
174
- span.set_attribute(SPAN_OUTPUT, output)
175
- except TypeError:
176
- pass
177
-
178
- span.end()
179
- context_api.detach(ctx_token)
180
-
181
- return res
182
-
183
- return wrap
184
-
185
- return decorate
186
-
187
-
188
- def _handle_generator(span, res):
189
- # for some reason the SPAN_KEY is not being set in the context of the generator, so we re-set it
190
- context_api.attach(trace.set_span_in_context(span))
191
- yield from res
192
-
193
- span.end()
194
-
195
- # Note: we don't detach the context here as this fails in some situations
196
- # https://github.com/open-telemetry/opentelemetry-python/issues/2606
197
- # This is not a problem since the context will be detached automatically during garbage collection
198
-
199
-
200
- async def _ahandle_generator(span, ctx_token, res):
201
- async for part in res:
202
- yield part
203
-
204
- span.end()
205
- context_api.detach(ctx_token)
206
-
207
-
208
- def _process_exception(span: Span, e: Exception):
209
- # Note that this `escaped` is sent as a StringValue("True"), not a boolean.
210
- span.record_exception(e, escaped=True)
@@ -1,42 +0,0 @@
1
- from enum import Enum
2
-
3
-
4
- class Instruments(Enum):
5
- # The list of libraries which will be autoinstrumented
6
- # if no specific instruments are provided to initialize()
7
- ALEPHALPHA = "alephalpha"
8
- ANTHROPIC = "anthropic"
9
- BEDROCK = "bedrock"
10
- BROWSER_USE = "browser_use"
11
- CHROMA = "chroma"
12
- COHERE = "cohere"
13
- GOOGLE_GENERATIVEAI = "google_generativeai"
14
- GOOGLE_GENAI = "google_genai"
15
- GROQ = "groq"
16
- HAYSTACK = "haystack"
17
- LANCEDB = "lancedb"
18
- LANGCHAIN = "langchain"
19
- LLAMA_INDEX = "llama_index"
20
- MARQO = "marqo"
21
- MILVUS = "milvus"
22
- MISTRAL = "mistral"
23
- OLLAMA = "ollama"
24
- OPENAI = "openai"
25
- PINECONE = "pinecone"
26
- PLAYWRIGHT = "playwright"
27
- PATCHRIGHT = "patchright"
28
- QDRANT = "qdrant"
29
- REPLICATE = "replicate"
30
- SAGEMAKER = "sagemaker"
31
- TOGETHER = "together"
32
- TRANSFORMERS = "transformers"
33
- VERTEXAI = "vertexai"
34
- WATSONX = "watsonx"
35
- WEAVIATE = "weaviate"
36
-
37
- # The following libraries will not be autoinstrumented unless
38
- # specified explicitly in the initialize() call.
39
- REDIS = "redis"
40
- REQUESTS = "requests"
41
- URLLIB3 = "urllib3"
42
- PYMYSQL = "pymysql"
@@ -1,24 +0,0 @@
1
- # Manages list of associated properties for which content tracing
2
- # (prompts, vector embeddings, etc.) is allowed.
3
- class ContentAllowList:
4
- def __new__(cls) -> "ContentAllowList":
5
- if not hasattr(cls, "instance"):
6
- obj = cls.instance = super(ContentAllowList, cls).__new__(cls)
7
- obj._allow_list: list[dict] = []
8
-
9
- return cls.instance
10
-
11
- def is_allowed(self, association_properties: dict) -> bool:
12
- for allow_list_item in self._allow_list:
13
- if all(
14
- [
15
- association_properties.get(key) == value
16
- for key, value in allow_list_item.items()
17
- ]
18
- ):
19
- return True
20
-
21
- return False
22
-
23
- def load(self, response_json: dict):
24
- self._allow_list = response_json["associationPropertyAllowList"]