lmnr 0.4.3__py3-none-any.whl → 0.4.5__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/sdk/evaluations.py +20 -4
- lmnr/sdk/laminar.py +32 -24
- {lmnr-0.4.3.dist-info → lmnr-0.4.5.dist-info}/METADATA +39 -1
- {lmnr-0.4.3.dist-info → lmnr-0.4.5.dist-info}/RECORD +7 -7
- {lmnr-0.4.3.dist-info → lmnr-0.4.5.dist-info}/LICENSE +0 -0
- {lmnr-0.4.3.dist-info → lmnr-0.4.5.dist-info}/WHEEL +0 -0
- {lmnr-0.4.3.dist-info → lmnr-0.4.5.dist-info}/entry_points.txt +0 -0
lmnr/sdk/evaluations.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from typing import Union
|
2
2
|
|
3
|
+
from .types import EvaluatorFunction, ExecutorFunction, EvaluationDatapoint
|
3
4
|
from .utils import is_async
|
4
|
-
from .types import EvaluatorFunction, ExecutorFunction, EvaluationDatapoint, Numeric
|
5
5
|
from .laminar import Laminar as L
|
6
6
|
import asyncio
|
7
7
|
|
@@ -95,7 +95,7 @@ class Evaluation:
|
|
95
95
|
self.batch_size = batch_size
|
96
96
|
L.initialize(project_api_key=project_api_key, base_url=base_url)
|
97
97
|
|
98
|
-
|
98
|
+
def run(self):
|
99
99
|
"""Runs the evaluation.
|
100
100
|
|
101
101
|
Creates a new evaluation if no evaluation with such name exists, or
|
@@ -103,7 +103,23 @@ class Evaluation:
|
|
103
103
|
batches of `self.batch_size`. The executor
|
104
104
|
function is called on each data point to get the output,
|
105
105
|
and then evaluate it by each evaluator function.
|
106
|
+
|
107
|
+
Usage:
|
108
|
+
```python
|
109
|
+
# in a synchronous context:
|
110
|
+
e.run()
|
111
|
+
# in an asynchronous context:
|
112
|
+
await e.run()
|
113
|
+
```
|
114
|
+
|
106
115
|
"""
|
116
|
+
loop = asyncio.get_event_loop()
|
117
|
+
if loop.is_running():
|
118
|
+
return loop.create_task(self._run())
|
119
|
+
else:
|
120
|
+
return loop.run_until_complete(self._run())
|
121
|
+
|
122
|
+
async def _run(self):
|
107
123
|
response = L.create_evaluation(self.name)
|
108
124
|
|
109
125
|
# Process batches sequentially
|
@@ -133,7 +149,7 @@ class Evaluation:
|
|
133
149
|
async def _evaluate_datapoint(self, datapoint):
|
134
150
|
output = (
|
135
151
|
await self.executor(datapoint.data)
|
136
|
-
if
|
152
|
+
if is_async(self.executor)
|
137
153
|
else self.executor(datapoint.data)
|
138
154
|
)
|
139
155
|
target = datapoint.target
|
@@ -144,7 +160,7 @@ class Evaluation:
|
|
144
160
|
evaluator = self.evaluators[evaluator_name]
|
145
161
|
value = (
|
146
162
|
await evaluator(output, target)
|
147
|
-
if
|
163
|
+
if is_async(evaluator)
|
148
164
|
else evaluator(output, target)
|
149
165
|
)
|
150
166
|
|
lmnr/sdk/laminar.py
CHANGED
@@ -11,7 +11,7 @@ from traceloop.sdk import Traceloop
|
|
11
11
|
from traceloop.sdk.tracing import get_tracer
|
12
12
|
|
13
13
|
from pydantic.alias_generators import to_snake
|
14
|
-
from typing import Any, Optional,
|
14
|
+
from typing import Any, Optional, Union
|
15
15
|
|
16
16
|
import copy
|
17
17
|
import datetime
|
@@ -201,15 +201,21 @@ class Laminar:
|
|
201
201
|
def event(
|
202
202
|
cls,
|
203
203
|
name: str,
|
204
|
-
value: AttributeValue,
|
204
|
+
value: Optional[AttributeValue] = None,
|
205
205
|
timestamp: Optional[Union[datetime.datetime, int]] = None,
|
206
206
|
):
|
207
|
-
"""Associate an event with the current span
|
207
|
+
"""Associate an event with the current span. If event with such name never
|
208
|
+
existed, Laminar will create a new event and infer its type from the value.
|
209
|
+
If the event already exists, Laminar will append the value to the event
|
210
|
+
if and only if the value is of a matching type. Otherwise, the event won't
|
211
|
+
be recorded Supported types are string, numeric, and boolean. If the value
|
212
|
+
is `None`, event is considered a boolean tag with the value of `True`.
|
208
213
|
|
209
214
|
Args:
|
210
215
|
name (str): event name
|
211
|
-
value (AttributeValue): event value. Must be a primitive type
|
212
|
-
|
216
|
+
value (Optional[AttributeValue]): event value. Must be a primitive type.
|
217
|
+
Boolean true is assumed in the backend if value is None.
|
218
|
+
Defaults to None.
|
213
219
|
timestamp (Optional[Union[datetime.datetime, int]], optional):
|
214
220
|
If int, must be epoch nanoseconds. If not
|
215
221
|
specified, relies on the underlying OpenTelemetry
|
@@ -220,8 +226,9 @@ class Laminar:
|
|
220
226
|
|
221
227
|
event = {
|
222
228
|
"lmnr.event.type": "default",
|
223
|
-
"lmnr.event.value": value,
|
224
229
|
}
|
230
|
+
if value is not None:
|
231
|
+
event["lmnr.event.value"] = value
|
225
232
|
|
226
233
|
current_span = get_current_span()
|
227
234
|
if current_span == INVALID_SPAN:
|
@@ -282,7 +289,7 @@ class Laminar:
|
|
282
289
|
cls,
|
283
290
|
name: str,
|
284
291
|
input: Any = None,
|
285
|
-
) ->
|
292
|
+
) -> Span:
|
286
293
|
"""Start a new span with the given name. Useful for manual
|
287
294
|
instrumentation.
|
288
295
|
|
@@ -297,33 +304,34 @@ class Laminar:
|
|
297
304
|
that must be passed to `end_span` to end the span.
|
298
305
|
|
299
306
|
"""
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
307
|
+
tracer = get_tracer().__enter__()
|
308
|
+
span = tracer.start_span(name)
|
309
|
+
# apparently, detaching from this context is not mandatory.
|
310
|
+
# According to traceloop, and the github issue in opentelemetry,
|
311
|
+
# the context is collected by the garbage collector.
|
312
|
+
# https://github.com/open-telemetry/opentelemetry-python/issues/2606#issuecomment-2106320379
|
313
|
+
context.attach(set_span_in_context(span))
|
314
|
+
|
315
|
+
if input is not None:
|
316
|
+
span.set_attribute(
|
317
|
+
SpanAttributes.TRACELOOP_ENTITY_INPUT, json.dumps({"input": input})
|
318
|
+
)
|
319
|
+
|
320
|
+
return span
|
310
321
|
|
311
322
|
@classmethod
|
312
|
-
def
|
313
|
-
"""
|
323
|
+
def set_span_output(cls, span: Span, output: Any = None):
|
324
|
+
"""Set the output of the span. Useful for manual instrumentation.
|
314
325
|
|
315
326
|
Args:
|
316
|
-
span (Span): span
|
317
|
-
token (object): context token returned by `start_span`
|
327
|
+
span (Span): the span to set the output for
|
318
328
|
output (Any, optional): output of the span. Will be sent as an
|
319
329
|
attribute, so must be json serializable. Defaults to None.
|
320
330
|
"""
|
321
331
|
if output is not None:
|
322
332
|
span.set_attribute(
|
323
|
-
SpanAttributes.TRACELOOP_ENTITY_OUTPUT, json.dumps({
|
333
|
+
SpanAttributes.TRACELOOP_ENTITY_OUTPUT, json.dumps({output})
|
324
334
|
)
|
325
|
-
span.end()
|
326
|
-
context.detach(token)
|
327
335
|
|
328
336
|
@classmethod
|
329
337
|
def set_session(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: lmnr
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.5
|
4
4
|
Summary: Python SDK for Laminar AI
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: lmnr.ai
|
@@ -89,6 +89,44 @@ def poem_writer(topic="turbulence"):
|
|
89
89
|
print(poem_writer(topic="laminar flow"))
|
90
90
|
```
|
91
91
|
|
92
|
+
### Manual instrumentation
|
93
|
+
|
94
|
+
Our manual instrumentation is a very thin wrapper around OpenTelemetry's
|
95
|
+
`trace.start_span`. Our wrapper sets the span into the active context.
|
96
|
+
You don't have to explicitly pass the spans around, it is enough to
|
97
|
+
just call `L.start_span`, and OpenTelemetry will handle the context management
|
98
|
+
|
99
|
+
```python
|
100
|
+
from lmnr import observe, Laminar as L
|
101
|
+
L.initialize(project_api_key="<LMNR_PROJECT_API_KEY>")
|
102
|
+
|
103
|
+
def poem_writer(topic="turbulence"):
|
104
|
+
|
105
|
+
span = L.start_span("poem_writer", topic) # start a span
|
106
|
+
|
107
|
+
prompt = f"write a poem about {topic}"
|
108
|
+
|
109
|
+
# OpenAI calls are still automatically instrumented with OpenLLMetry
|
110
|
+
response = client.chat.completions.create(
|
111
|
+
model="gpt-4o",
|
112
|
+
messages=[
|
113
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
114
|
+
{"role": "user", "content": prompt},
|
115
|
+
],
|
116
|
+
)
|
117
|
+
poem = response.choices[0].message.content
|
118
|
+
# while within the span, you can attach laminar events to it
|
119
|
+
L.event("event_name", "event_value")
|
120
|
+
|
121
|
+
L.set_span_output(span, poem) # set an output
|
122
|
+
|
123
|
+
# IMPORTANT: don't forget to end all the spans (usually in `finally` blocks)
|
124
|
+
# Otherwise, the trace may not be sent/displayed correctly
|
125
|
+
span.end()
|
126
|
+
|
127
|
+
return poem
|
128
|
+
```
|
129
|
+
|
92
130
|
|
93
131
|
## Sending events
|
94
132
|
|
@@ -1,13 +1,13 @@
|
|
1
1
|
lmnr/__init__.py,sha256=wQwnHl662Xcz7GdSofFsEjmAK0nxioYA2Yq6Q78m4ps,194
|
2
2
|
lmnr/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
lmnr/sdk/decorators.py,sha256=Xs6n0TGX9LZ9i1hE_UZz4LEyd_ZAfpVGfNQh_rKwOuA,2493
|
4
|
-
lmnr/sdk/evaluations.py,sha256=
|
5
|
-
lmnr/sdk/laminar.py,sha256=
|
4
|
+
lmnr/sdk/evaluations.py,sha256=Z0j2HyXgrwlGyiT_Ql7W3e_ZWjOlNlIj9RWAKjEgkkE,6366
|
5
|
+
lmnr/sdk/laminar.py,sha256=LOttLBrQoAlduqLYL6sODYHzqRrt44mDNgnnE2it5CE,17114
|
6
6
|
lmnr/sdk/log.py,sha256=EgAMY77Zn1bv1imCqrmflD3imoAJ2yveOkIcrIP3e98,1170
|
7
7
|
lmnr/sdk/types.py,sha256=gDwRSWR9A1__FGtQhVaFc6PUYQuIhubo5tpfYAajTQQ,4055
|
8
8
|
lmnr/sdk/utils.py,sha256=ZsGJ86tq8lIbvOhSb1gJWH5K3GylO_lgX68FN6rG2nM,3358
|
9
|
-
lmnr-0.4.
|
10
|
-
lmnr-0.4.
|
11
|
-
lmnr-0.4.
|
12
|
-
lmnr-0.4.
|
13
|
-
lmnr-0.4.
|
9
|
+
lmnr-0.4.5.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
|
10
|
+
lmnr-0.4.5.dist-info/METADATA,sha256=tmHMhn3OT6gvymqbZk1aDp5_LH-dHiOMc2m_Lpt8ptU,8292
|
11
|
+
lmnr-0.4.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
12
|
+
lmnr-0.4.5.dist-info/entry_points.txt,sha256=Qg7ZRax4k-rcQsZ26XRYQ8YFSBiyY2PNxYfq4a6PYXI,41
|
13
|
+
lmnr-0.4.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|