lmnr 0.4.2__tar.gz → 0.4.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lmnr
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Python SDK for Laminar AI
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lmnr"
3
- version = "0.4.2"
3
+ version = "0.4.4"
4
4
  description = "Python SDK for Laminar AI"
5
5
  authors = [
6
6
  { name = "lmnr.ai", email = "founders@lmnr.ai" }
@@ -11,7 +11,7 @@ license = "Apache-2.0"
11
11
 
12
12
  [tool.poetry]
13
13
  name = "lmnr"
14
- version = "0.4.2"
14
+ version = "0.4.4"
15
15
  description = "Python SDK for Laminar AI"
16
16
  authors = ["lmnr.ai"]
17
17
  readme = "README.md"
@@ -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
- async def run(self):
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,61 +103,76 @@ 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
- batch_promises = []
109
124
 
125
+ # Process batches sequentially
110
126
  for i in range(0, len(self.data), self.batch_size):
111
127
  batch = (
112
128
  self.data[i : i + self.batch_size]
113
129
  if isinstance(self.data, list)
114
130
  else self.data.slice(i, i + self.batch_size)
115
131
  )
116
- batch_promises.append(self._evaluate_batch(batch))
132
+ try:
133
+ await self._evaluate_batch(batch)
134
+ except Exception as e:
135
+ print(f"Error evaluating batch: {e}")
117
136
 
118
137
  try:
119
- await asyncio.gather(*batch_promises)
120
138
  L.update_evaluation_status(response.name, "Finished")
121
139
  print(f"Evaluation {response.id} complete")
122
140
  except Exception as e:
123
- print(f"Error evaluating batch: {e}")
141
+ print(f"Error updating evaluation status: {e}")
124
142
 
125
143
  async def _evaluate_batch(self, batch: list[EvaluationDatapoint]):
126
- results = []
127
- for datapoint in batch:
128
- output = (
129
- await self.executor(datapoint.data)
130
- if is_async(self.executor)
131
- else self.executor(datapoint.data)
132
- )
133
- target = datapoint.target
134
-
135
- # iterate in order of evaluators
136
- scores = {}
137
- for evaluator_name in self.evaluator_names:
138
- evaluator = self.evaluators[evaluator_name]
139
- value = (
140
- await evaluator(output, target)
141
- if is_async(evaluator)
142
- else evaluator(output, target)
143
- )
144
+ batch_promises = [self._evaluate_datapoint(datapoint) for datapoint in batch]
145
+ results = await asyncio.gather(*batch_promises)
146
+
147
+ return L.post_evaluation_results(self.name, results)
144
148
 
145
- # if the evaluator returns a single number,
146
- # use the evaluator name as the key
147
- if isinstance(value, Numeric):
148
- scores[evaluator_name] = value
149
- else:
150
- # if the evaluator returns an object,
151
- # use the object keys as the keys
152
- scores.update(value)
153
-
154
- results.append(
155
- {
156
- "executorOutput": output,
157
- "data": datapoint.data,
158
- "target": target,
159
- "scores": scores,
160
- }
149
+ async def _evaluate_datapoint(self, datapoint):
150
+ output = (
151
+ await self.executor(datapoint.data)
152
+ if is_async(self.executor)
153
+ else self.executor(datapoint.data)
154
+ )
155
+ target = datapoint.target
156
+
157
+ # Iterate over evaluators
158
+ scores = {}
159
+ for evaluator_name in self.evaluator_names:
160
+ evaluator = self.evaluators[evaluator_name]
161
+ value = (
162
+ await evaluator(output, target)
163
+ if is_async(evaluator)
164
+ else evaluator(output, target)
161
165
  )
162
166
 
163
- return L.post_evaluation_results(self.name, results)
167
+ # If evaluator returns a single number, use evaluator name as key
168
+ if isinstance(value, (int, float)):
169
+ scores[evaluator_name] = value
170
+ else:
171
+ scores.update(value)
172
+
173
+ return {
174
+ "executorOutput": output,
175
+ "data": datapoint.data,
176
+ "target": target,
177
+ "scores": scores,
178
+ }
@@ -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
- or a sequence of values of the same primitive type
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:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes