lmnr 0.6.9__py3-none-any.whl → 0.6.11__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/cli.py CHANGED
@@ -100,9 +100,14 @@ async def run_evaluation(args):
100
100
  sys.modules[name] = mod
101
101
 
102
102
  spec.loader.exec_module(mod)
103
- evaluations: list[Evaluation] | None = EVALUATION_INSTANCES.get()
104
- if evaluations is None:
105
- LOG.warning("Evaluation instance not found")
103
+ evaluations = []
104
+ try:
105
+ evaluations: list[Evaluation] | None = EVALUATION_INSTANCES.get()
106
+ if evaluations is None:
107
+ raise LookupError()
108
+ # may be raised by `get()` or manually by us above
109
+ except LookupError:
110
+ log_evaluation_instance_not_found()
106
111
  if args.continue_on_error:
107
112
  continue
108
113
  return
@@ -130,6 +135,14 @@ async def run_evaluation(args):
130
135
  PREPARE_ONLY.reset(prep_token)
131
136
 
132
137
 
138
+ def log_evaluation_instance_not_found():
139
+ LOG.warning(
140
+ "Evaluation instance not found. "
141
+ "`evaluate` must be called at the top level of the file, "
142
+ "not inside a function when running evaluations from the CLI."
143
+ )
144
+
145
+
133
146
  def cli():
134
147
  parser = ArgumentParser(
135
148
  prog="lmnr",
@@ -54,6 +54,8 @@ class TracerManager:
54
54
 
55
55
  @staticmethod
56
56
  def flush() -> bool:
57
+ if not hasattr(TracerManager, "_TracerManager__tracer_wrapper"):
58
+ return False
57
59
  return TracerManager.__tracer_wrapper.flush()
58
60
 
59
61
  @staticmethod
@@ -11,7 +11,7 @@ from .utils import (
11
11
  from langchain_core.runnables.graph import Graph
12
12
  from opentelemetry.trace import Tracer
13
13
  from wrapt import wrap_function_wrapper
14
- from opentelemetry.trace import get_tracer, get_current_span
14
+ from opentelemetry.trace import get_tracer
15
15
 
16
16
  from lmnr.opentelemetry_lib.tracing.context_properties import (
17
17
  update_association_properties,
@@ -81,7 +81,9 @@ async def async_wrap_pregel_stream(
81
81
  "langgraph.nodes": json.dumps(nodes),
82
82
  },
83
83
  )
84
- return await wrapped(*args, **kwargs)
84
+
85
+ async for item in wrapped(*args, **kwargs):
86
+ yield item
85
87
 
86
88
 
87
89
  class LanggraphInstrumentor(BaseInstrumentor):
@@ -1,5 +1,6 @@
1
1
  import atexit
2
2
  import logging
3
+ import threading
3
4
 
4
5
  from lmnr.opentelemetry_lib.tracing.processor import LaminarSpanProcessor
5
6
  from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
@@ -16,12 +17,6 @@ from opentelemetry.sdk.resources import Resource
16
17
  from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
17
18
  from opentelemetry.sdk.trace.export import SpanExporter
18
19
 
19
- module_logger = logging.getLogger(__name__)
20
- console_log_handler = logging.StreamHandler()
21
- console_log_handler.setFormatter(VerboseColorfulFormatter())
22
- module_logger.addHandler(console_log_handler)
23
-
24
-
25
20
  TRACER_NAME = "lmnr.tracer"
26
21
 
27
22
  MAX_EVENTS_OR_ATTRIBUTES_PER_SPAN = 5000
@@ -30,12 +25,13 @@ MAX_EVENTS_OR_ATTRIBUTES_PER_SPAN = 5000
30
25
  class TracerWrapper(object):
31
26
  resource_attributes: dict = {}
32
27
  enable_content_tracing: bool = True
33
- __tracer_provider: TracerProvider | None = None
34
- __logger: logging.Logger
35
- __client: LaminarClient
36
- __async_client: AsyncLaminarClient
37
- __resource: Resource
38
- __span_processor: SpanProcessor
28
+ _lock = threading.Lock()
29
+ _tracer_provider: TracerProvider | None = None
30
+ _logger: logging.Logger
31
+ _client: LaminarClient
32
+ _async_client: AsyncLaminarClient
33
+ _resource: Resource
34
+ _span_processor: SpanProcessor
39
35
 
40
36
  def __new__(
41
37
  cls,
@@ -57,73 +53,76 @@ class TracerWrapper(object):
57
53
  logging.getLogger("opentelemetry.trace").setLevel(otel_logger_level)
58
54
 
59
55
  base_http_url = f"{base_url}:{http_port}"
60
- cls._initialize_logger(cls)
61
- if not hasattr(cls, "instance"):
62
- obj = cls.instance = super(TracerWrapper, cls).__new__(cls)
63
-
64
- obj.__client = LaminarClient(
65
- base_url=base_http_url,
66
- project_api_key=project_api_key,
67
- )
68
- obj.__async_client = AsyncLaminarClient(
69
- base_url=base_http_url,
70
- project_api_key=project_api_key,
71
- )
72
-
73
- obj.__resource = Resource(attributes=TracerWrapper.resource_attributes)
74
-
75
- obj.__span_processor = LaminarSpanProcessor(
76
- base_url=base_url,
77
- api_key=project_api_key,
78
- port=http_port if force_http else port,
79
- exporter=exporter,
80
- max_export_batch_size=max_export_batch_size,
81
- timeout_seconds=timeout_seconds,
82
- force_http=force_http,
83
- disable_batch=disable_batch,
84
- )
85
-
86
- lmnr_provider = TracerProvider(resource=obj.__resource)
87
- global_provider = trace.get_tracer_provider()
88
- if set_global_tracer_provider and isinstance(
89
- global_provider, trace.ProxyTracerProvider
90
- ):
91
- trace.set_tracer_provider(lmnr_provider)
92
-
93
- obj.__tracer_provider = lmnr_provider
94
-
95
- obj.__tracer_provider.add_span_processor(obj.__span_processor)
96
-
97
- # This is not a real instrumentation and does not generate telemetry
98
- # data, but it is required to ensure that OpenTelemetry context
99
- # propagation is enabled.
100
- # See the README at:
101
- # https://pypi.org/project/opentelemetry-instrumentation-threading/
102
- ThreadingInstrumentor().instrument()
103
-
104
- init_instrumentations(
105
- tracer_provider=obj.__tracer_provider,
106
- instruments=instruments,
107
- block_instruments=block_instruments,
108
- client=obj.__client,
109
- async_client=obj.__async_client,
110
- )
111
-
112
- # Force flushes for debug environments (e.g. local development)
113
- atexit.register(obj.exit_handler)
114
-
115
- return cls.instance
56
+ with cls._lock:
57
+ if not hasattr(cls, "instance"):
58
+ cls._initialize_logger(cls)
59
+ obj = super(TracerWrapper, cls).__new__(cls)
60
+
61
+ obj._client = LaminarClient(
62
+ base_url=base_http_url,
63
+ project_api_key=project_api_key,
64
+ )
65
+ obj._async_client = AsyncLaminarClient(
66
+ base_url=base_http_url,
67
+ project_api_key=project_api_key,
68
+ )
69
+
70
+ obj._resource = Resource(attributes=TracerWrapper.resource_attributes)
71
+
72
+ obj._span_processor = LaminarSpanProcessor(
73
+ base_url=base_url,
74
+ api_key=project_api_key,
75
+ port=http_port if force_http else port,
76
+ exporter=exporter,
77
+ max_export_batch_size=max_export_batch_size,
78
+ timeout_seconds=timeout_seconds,
79
+ force_http=force_http,
80
+ disable_batch=disable_batch,
81
+ )
82
+
83
+ lmnr_provider = TracerProvider(resource=obj._resource)
84
+ global_provider = trace.get_tracer_provider()
85
+ if set_global_tracer_provider and isinstance(
86
+ global_provider, trace.ProxyTracerProvider
87
+ ):
88
+ trace.set_tracer_provider(lmnr_provider)
89
+
90
+ obj._tracer_provider = lmnr_provider
91
+
92
+ obj._tracer_provider.add_span_processor(obj._span_processor)
93
+
94
+ # This is not a real instrumentation and does not generate telemetry
95
+ # data, but it is required to ensure that OpenTelemetry context
96
+ # propagation is enabled.
97
+ # See the README at:
98
+ # https://pypi.org/project/opentelemetry-instrumentation-threading/
99
+ ThreadingInstrumentor().instrument()
100
+
101
+ init_instrumentations(
102
+ tracer_provider=obj._tracer_provider,
103
+ instruments=instruments,
104
+ block_instruments=block_instruments,
105
+ client=obj._client,
106
+ async_client=obj._async_client,
107
+ )
108
+
109
+ cls.instance = obj
110
+
111
+ # Force flushes for debug environments (e.g. local development)
112
+ atexit.register(obj.exit_handler)
113
+
114
+ return cls.instance
116
115
 
117
116
  def exit_handler(self):
118
- if isinstance(self.__span_processor, LaminarSpanProcessor):
119
- self.__span_processor.clear()
117
+ if isinstance(self._span_processor, LaminarSpanProcessor):
118
+ self._span_processor.clear()
120
119
  self.flush()
121
120
 
122
121
  def _initialize_logger(self):
123
- self.__logger = logging.getLogger(__name__)
122
+ self._logger = logging.getLogger(__name__)
124
123
  console_log_handler = logging.StreamHandler()
125
124
  console_log_handler.setFormatter(VerboseColorfulFormatter())
126
- self.__logger.addHandler(console_log_handler)
125
+ self._logger.addHandler(console_log_handler)
127
126
 
128
127
  @staticmethod
129
128
  def set_static_params(
@@ -135,23 +134,29 @@ class TracerWrapper(object):
135
134
 
136
135
  @classmethod
137
136
  def verify_initialized(cls) -> bool:
138
- return hasattr(cls, "instance")
137
+ with cls._lock:
138
+ return hasattr(cls, "instance") and hasattr(cls.instance, "_span_processor")
139
139
 
140
140
  @classmethod
141
141
  def clear(cls):
142
+ if not cls.verify_initialized():
143
+ return
142
144
  # Any state cleanup. Now used in between tests
143
- if isinstance(cls.instance.__span_processor, LaminarSpanProcessor):
144
- cls.instance.__span_processor.clear()
145
+ if isinstance(cls.instance._span_processor, LaminarSpanProcessor):
146
+ cls.instance._span_processor.clear()
145
147
 
146
148
  def shutdown(self):
147
- if self.__tracer_provider is None:
149
+ if self._tracer_provider is None:
148
150
  return
149
- self.__tracer_provider.shutdown()
151
+ self._tracer_provider.shutdown()
150
152
 
151
153
  def flush(self):
152
- return self.__span_processor.force_flush()
154
+ if not hasattr(self, "_span_processor"):
155
+ self._logger.warning("TracerWrapper not fully initialized, cannot flush")
156
+ return False
157
+ return self._span_processor.force_flush()
153
158
 
154
159
  def get_tracer(self):
155
- if self.__tracer_provider is None:
160
+ if self._tracer_provider is None:
156
161
  return trace.get_tracer_provider().get_tracer(TRACER_NAME)
157
- return self.__tracer_provider.get_tracer(TRACER_NAME)
162
+ return self._tracer_provider.get_tracer(TRACER_NAME)
@@ -89,11 +89,11 @@ class AsyncLaminarClient:
89
89
  return self.__agent
90
90
 
91
91
  @property
92
- def _evals(self) -> AsyncEvals:
92
+ def evals(self) -> AsyncEvals:
93
93
  """Get the Evals resource.
94
94
 
95
95
  Returns:
96
- Evals: The Evals resource instance.
96
+ AsyncEvals: The Evals resource instance.
97
97
  """
98
98
  return self.__evals
99
99
 
@@ -144,3 +144,5 @@ class AsyncLaminarClient:
144
144
  "Content-Type": "application/json",
145
145
  "Accept": "application/json",
146
146
  }
147
+
148
+
@@ -1,5 +1,6 @@
1
1
  """Evals resource for interacting with Laminar evaluations API."""
2
2
 
3
+ from typing import Any
3
4
  import uuid
4
5
 
5
6
  from lmnr.sdk.client.asynchronous.resources.base import BaseAsyncResource
@@ -33,9 +34,71 @@ class AsyncEvals(BaseAsyncResource):
33
34
  },
34
35
  headers=self._headers(),
35
36
  )
37
+ if response.status_code != 200:
38
+ if response.status_code == 401:
39
+ raise ValueError("Unauthorized. Please check your project API key.")
40
+ raise ValueError(f"Error initializing evaluation: {response.text}")
36
41
  resp_json = response.json()
37
42
  return InitEvaluationResponse.model_validate(resp_json)
38
43
 
44
+ async def create_evaluation(
45
+ self,
46
+ name: str | None = None,
47
+ group_name: str | None = None,
48
+ ) -> uuid.UUID:
49
+ """
50
+ Create a new evaluation and return its ID.
51
+
52
+ Parameters:
53
+ name (str | None, optional): Optional name of the evaluation.
54
+ group_name (str | None, optional): An identifier to group evaluations.
55
+
56
+ Returns:
57
+ uuid.UUID: The evaluation ID.
58
+ """
59
+ evaluation = await self.init(name=name, group_name=group_name)
60
+ return evaluation.id
61
+
62
+ async def create_datapoint(
63
+ self,
64
+ eval_id: uuid.UUID,
65
+ data: Any,
66
+ target: Any = None,
67
+ metadata: dict[str, Any] | None = None,
68
+ index: int | None = None,
69
+ trace_id: uuid.UUID | None = None,
70
+ ) -> uuid.UUID:
71
+ """
72
+ Create a datapoint for an evaluation.
73
+
74
+ Parameters:
75
+ eval_id (uuid.UUID): The evaluation ID.
76
+ data: The input data for the executor.
77
+ target: The target/expected output for evaluators.
78
+ metadata (dict[str, Any] | None, optional): Optional metadata.
79
+ index (int | None, optional): Optional index of the datapoint.
80
+ trace_id (uuid.UUID | None, optional): Optional trace ID.
81
+
82
+ Returns:
83
+ uuid.UUID: The datapoint ID.
84
+ """
85
+
86
+ datapoint_id = uuid.uuid4()
87
+
88
+ # Create a minimal datapoint first
89
+ partial_datapoint = PartialEvaluationDatapoint(
90
+ id=datapoint_id,
91
+ data=data,
92
+ target=target,
93
+ index=index or 0,
94
+ trace_id=trace_id or uuid.uuid4(),
95
+ executor_span_id=uuid.uuid4(), # Will be updated when executor runs
96
+ metadata=metadata,
97
+ )
98
+
99
+ await self.save_datapoints(eval_id, [partial_datapoint])
100
+ return datapoint_id
101
+
39
102
  async def save_datapoints(
40
103
  self,
41
104
  eval_id: uuid.UUID,
@@ -62,3 +125,34 @@ class AsyncEvals(BaseAsyncResource):
62
125
  )
63
126
  if response.status_code != 200:
64
127
  raise ValueError(f"Error saving evaluation datapoints: {response.text}")
128
+
129
+
130
+ async def update_datapoint(
131
+ self,
132
+ eval_id: uuid.UUID,
133
+ datapoint_id: uuid.UUID,
134
+ scores: dict[str, float | int],
135
+ executor_output: Any | None = None,
136
+ ) -> None:
137
+ """Update a datapoint with evaluation results.
138
+
139
+ Args:
140
+ eval_id (uuid.UUID): The evaluation ID.
141
+ datapoint_id (uuid.UUID): The datapoint ID.
142
+ executor_output (Any): The executor output.
143
+ scores (dict[str, float | int] | None, optional): The scores. Defaults to None.
144
+ """
145
+
146
+ response = await self._client.post(
147
+ self._base_url + f"/v1/evals/{eval_id}/datapoints/{datapoint_id}",
148
+ json={
149
+ "executorOutput": executor_output,
150
+ "scores": scores,
151
+ },
152
+ headers=self._headers(),
153
+ )
154
+
155
+ if response.status_code != 200:
156
+ raise ValueError(f"Error updating evaluation datapoint: {response.text}")
157
+
158
+
@@ -2,6 +2,7 @@
2
2
 
3
3
  import uuid
4
4
  import urllib.parse
5
+ from typing import Any
5
6
 
6
7
  from lmnr.sdk.client.synchronous.resources.base import BaseResource
7
8
  from lmnr.sdk.types import (
@@ -35,9 +36,71 @@ class Evals(BaseResource):
35
36
  },
36
37
  headers=self._headers(),
37
38
  )
39
+ if response.status_code != 200:
40
+ if response.status_code == 401:
41
+ raise ValueError("Unauthorized. Please check your project API key.")
42
+ raise ValueError(f"Error initializing evaluation: {response.text}")
38
43
  resp_json = response.json()
39
44
  return InitEvaluationResponse.model_validate(resp_json)
40
45
 
46
+ def create_evaluation(
47
+ self,
48
+ name: str | None = None,
49
+ group_name: str | None = None,
50
+ ) -> uuid.UUID:
51
+ """
52
+ Create a new evaluation and return its ID.
53
+
54
+ Parameters:
55
+ name (str | None, optional): Optional name of the evaluation.
56
+ group_name (str | None, optional): An identifier to group evaluations.
57
+
58
+ Returns:
59
+ uuid.UUID: The evaluation ID.
60
+ """
61
+ evaluation = self.init(name=name, group_name=group_name)
62
+ return evaluation.id
63
+
64
+ def create_datapoint(
65
+ self,
66
+ eval_id: uuid.UUID,
67
+ data: Any,
68
+ target: Any = None,
69
+ metadata: dict[str, Any] | None = None,
70
+ index: int | None = None,
71
+ trace_id: uuid.UUID | None = None,
72
+ ) -> uuid.UUID:
73
+ """
74
+ Create a datapoint for an evaluation.
75
+
76
+ Parameters:
77
+ eval_id (uuid.UUID): The evaluation ID.
78
+ data: The input data for the executor.
79
+ target: The target/expected output for evaluators.
80
+ metadata (dict[str, Any] | None, optional): Optional metadata.
81
+ index (int | None, optional): Optional index of the datapoint.
82
+ trace_id (uuid.UUID | None, optional): Optional trace ID.
83
+
84
+ Returns:
85
+ uuid.UUID: The datapoint ID.
86
+ """
87
+
88
+ datapoint_id = uuid.uuid4()
89
+
90
+ # Create a minimal datapoint first
91
+ partial_datapoint = PartialEvaluationDatapoint(
92
+ id=datapoint_id,
93
+ data=data,
94
+ target=target,
95
+ index=index or 0,
96
+ trace_id=trace_id or uuid.uuid4(),
97
+ executor_span_id=uuid.uuid4(), # Will be updated when executor runs
98
+ metadata=metadata,
99
+ )
100
+
101
+ self.save_datapoints(eval_id, [partial_datapoint])
102
+ return datapoint_id
103
+
41
104
  def save_datapoints(
42
105
  self,
43
106
  eval_id: uuid.UUID,
@@ -65,6 +128,34 @@ class Evals(BaseResource):
65
128
  if response.status_code != 200:
66
129
  raise ValueError(f"Error saving evaluation datapoints: {response.text}")
67
130
 
131
+ def update_datapoint(
132
+ self,
133
+ eval_id: uuid.UUID,
134
+ datapoint_id: uuid.UUID,
135
+ scores: dict[str, float | int],
136
+ executor_output: Any | None = None,
137
+ ) -> None:
138
+ """Update a datapoint with evaluation results.
139
+
140
+ Args:
141
+ eval_id (uuid.UUID): The evaluation ID.
142
+ datapoint_id (uuid.UUID): The datapoint ID.
143
+ executor_output (Any): The executor output.
144
+ scores (dict[str, float | int] | None, optional): The scores. Defaults to None.
145
+ """
146
+
147
+ response = self._client.post(
148
+ self._base_url + f"/v1/evals/{eval_id}/datapoints/{datapoint_id}",
149
+ json={
150
+ "executorOutput": executor_output,
151
+ "scores": scores,
152
+ },
153
+ headers=self._headers(),
154
+ )
155
+
156
+ if response.status_code != 200:
157
+ raise ValueError(f"Error updating evaluation datapoint: {response.text}")
158
+
68
159
  def get_datapoints(
69
160
  self,
70
161
  dataset_name: str,
@@ -89,7 +89,7 @@ class LaminarClient:
89
89
  return self.__agent
90
90
 
91
91
  @property
92
- def _evals(self) -> Evals:
92
+ def evals(self) -> Evals:
93
93
  """Get the Evals resource.
94
94
 
95
95
  Returns:
@@ -155,3 +155,5 @@ class LaminarClient:
155
155
  "Content-Type": "application/json",
156
156
  "Accept": "application/json",
157
157
  }
158
+
159
+
lmnr/sdk/datasets.py CHANGED
@@ -38,7 +38,7 @@ class LaminarDataset(EvaluationDataset):
38
38
  f"dataset {self.name}. Fetching batch from {self._offset} to "
39
39
  + f"{self._offset + self._fetch_size}"
40
40
  )
41
- resp = self.client._evals.get_datapoints(
41
+ resp = self.client.evals.get_datapoints(
42
42
  self.name, self._offset, self._fetch_size
43
43
  )
44
44
  self._fetched_items += resp.items
lmnr/sdk/evaluations.py CHANGED
@@ -199,11 +199,11 @@ class Evaluation:
199
199
  self.base_http_url = f"{base_url}:{http_port or 443}"
200
200
 
201
201
  api_key = project_api_key or from_env("LMNR_PROJECT_API_KEY")
202
- if not api_key:
202
+ if not api_key and not L.is_initialized():
203
203
  raise ValueError(
204
- "Please initialize the Laminar object with"
205
- " your project API key or set the LMNR_PROJECT_API_KEY"
206
- " environment variable in your environment or .env file"
204
+ "Please pass the project API key to `evaluate`"
205
+ " or set the LMNR_PROJECT_API_KEY environment variable"
206
+ " in your environment or .env file"
207
207
  )
208
208
  self.project_api_key = api_key
209
209
 
@@ -212,17 +212,12 @@ class Evaluation:
212
212
  base_url=L.get_base_http_url(),
213
213
  project_api_key=L.get_project_api_key(),
214
214
  )
215
- if project_api_key and project_api_key != L.get_project_api_key():
216
- self._logger.warning(
217
- "Project API key is different from the one used to initialize"
218
- " Laminar. Ignoring the project API key passed to the evaluation."
219
- )
220
- return
215
+ else:
216
+ self.client = AsyncLaminarClient(
217
+ base_url=self.base_http_url,
218
+ project_api_key=self.project_api_key,
219
+ )
221
220
 
222
- self.client = AsyncLaminarClient(
223
- base_url=self.base_http_url,
224
- project_api_key=self.project_api_key,
225
- )
226
221
  L.initialize(
227
222
  project_api_key=project_api_key,
228
223
  base_url=base_url,
@@ -246,7 +241,7 @@ class Evaluation:
246
241
  )
247
242
  self.reporter.start(len(self.data))
248
243
  try:
249
- evaluation = await self.client._evals.init(
244
+ evaluation = await self.client.evals.init(
250
245
  name=self.name, group_name=self.group_name
251
246
  )
252
247
  result_datapoints = await self._evaluate_in_batches(evaluation.id)
@@ -261,7 +256,7 @@ class Evaluation:
261
256
  except Exception as e:
262
257
  self.reporter.stopWithError(e)
263
258
  await self._shutdown()
264
- return {}
259
+ raise
265
260
 
266
261
  average_scores = get_average_scores(result_datapoints)
267
262
  self.reporter.stop(average_scores, evaluation.projectId, evaluation.id)
@@ -331,7 +326,7 @@ class Evaluation:
331
326
  metadata=datapoint.metadata,
332
327
  )
333
328
  # First, create datapoint with trace_id so that we can show the dp in the UI
334
- await self.client._evals.save_datapoints(
329
+ await self.client.evals.save_datapoints(
335
330
  eval_id, [partial_datapoint], self.group_name
336
331
  )
337
332
  executor_span.set_attribute(SPAN_TYPE, SpanType.EXECUTOR.value)
@@ -389,7 +384,7 @@ class Evaluation:
389
384
 
390
385
  # Create background upload task without awaiting it
391
386
  upload_task = asyncio.create_task(
392
- self.client._evals.save_datapoints(eval_id, [datapoint], self.group_name)
387
+ self.client.evals.save_datapoints(eval_id, [datapoint], self.group_name)
393
388
  )
394
389
  self.upload_tasks.append(upload_task)
395
390
 
lmnr/sdk/laminar.py CHANGED
@@ -114,6 +114,12 @@ class Laminar:
114
114
  Raises:
115
115
  ValueError: If project API key is not set
116
116
  """
117
+ if cls.is_initialized():
118
+ cls.__logger.info(
119
+ "Laminar is already initialized. Skipping initialization."
120
+ )
121
+ return
122
+
117
123
  cls.__project_api_key = project_api_key or from_env("LMNR_PROJECT_API_KEY")
118
124
  if not cls.__project_api_key:
119
125
  raise ValueError(
@@ -691,6 +697,7 @@ class Laminar:
691
697
  def shutdown(cls):
692
698
  if cls.is_initialized():
693
699
  TracerManager.shutdown()
700
+ cls.__initialized = False
694
701
 
695
702
  @classmethod
696
703
  def set_span_tags(cls, tags: list[str]):
lmnr/version.py CHANGED
@@ -3,7 +3,7 @@ import httpx
3
3
  from packaging import version
4
4
 
5
5
 
6
- __version__ = "0.6.9"
6
+ __version__ = "0.6.11"
7
7
  PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
8
8
 
9
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lmnr
3
- Version: 0.6.9
3
+ Version: 0.6.11
4
4
  Summary: Python SDK for Laminar
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -1,14 +1,14 @@
1
1
  lmnr/__init__.py,sha256=eJ-gIHEk8KV-BeaU8c9spQww_T2G5_OMu4F8JEzngvA,1281
2
- lmnr/cli.py,sha256=X5YVOBjitRdZPfcF3qxR4SflgTMZ2wsW6s_yXgvdxkU,5621
2
+ lmnr/cli.py,sha256=uHgLUfN_6eINtUlcQdOtODf2tI9AiwmlhojQF4UMB5Y,6047
3
3
  lmnr/opentelemetry_lib/.flake8,sha256=bCxuDlGx3YQ55QHKPiGJkncHanh9qGjQJUujcFa3lAU,150
4
- lmnr/opentelemetry_lib/__init__.py,sha256=E_NwAWxh3hckZjXTA80hOzmRUL8RvnSYRdcPExwVROc,2056
4
+ lmnr/opentelemetry_lib/__init__.py,sha256=MHT91gFyPte8OHiy18rQuImy4Bee1DXFSR4CH6-B-Wc,2154
5
5
  lmnr/opentelemetry_lib/decorators/__init__.py,sha256=45HVoYnHC1Y9D_VSkioDbqD3gm4RPC5sKoztomBI5j8,8496
6
6
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py,sha256=6Fvkc_zZEX1lk8g6ZGFrADLNOL055pkMdO-hEef8qBY,18525
7
7
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py,sha256=25zevJ7g3MtJP_5gju3jBH7-wg7SbDkktysuUO29ksI,245
8
8
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py,sha256=ICQENOiICTKodjZVHhq3H5RIRY5bbuWp_KmzkDNgDRM,7471
9
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py,sha256=khLC07QIsU15Sv8_Ax0ePjeTg7yP0vwhxvJIAqhU_4A,3099
9
+ lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py,sha256=Jyv9koZdGA4-oTaB7ATB7DaX7aNOY-3YOGL4wX0c7PM,3107
10
10
  lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py,sha256=nf9sJZXnnts4gYZortEiDvwYjYqYJZTAT0zutuP_R6Y,1512
11
- lmnr/opentelemetry_lib/tracing/__init__.py,sha256=dy3zrgeiZmLX-aLXuRUW8-RGR4FaIdRRKKEVR0eItGs,5662
11
+ lmnr/opentelemetry_lib/tracing/__init__.py,sha256=27QogAe-aHyrVr6b56-DduUm0KEE24K1aV2e1nouTNg,6007
12
12
  lmnr/opentelemetry_lib/tracing/_instrument_initializers.py,sha256=RYSp4PxrF8yfG5qy0ALpx1EArLFBPDLQd6H6YeYw184,14567
13
13
  lmnr/opentelemetry_lib/tracing/attributes.py,sha256=MvowVluXfCqSIC3Cvx3tWDqB0Cpr9bpSlY91qL4Iy74,1497
14
14
  lmnr/opentelemetry_lib/tracing/context_properties.py,sha256=aWbvMdWB4Q7uqc0GGSsjcRXMTcO18aWOaIZe3QyS_aA,2314
@@ -28,31 +28,31 @@ lmnr/sdk/browser/playwright_otel.py,sha256=LFg1iJXbez-BEgEIY9eaO_2T2uR6SxyfFL46f
28
28
  lmnr/sdk/browser/pw_utils.py,sha256=nFqVujQb7owVFzT-31_g09CbfbBwUIXKxs3JxuE47ws,10835
29
29
  lmnr/sdk/browser/rrweb/rrweb.umd.min.cjs,sha256=Ly2jiwC7hTEtgiXzBpoJNSE1Vkzu0lZPZS8brjusAW0,260896
30
30
  lmnr/sdk/browser/utils.py,sha256=SmaHdtKTgQjSX7zs1hyOFxCk2j5WIw1f78pZZN0J48E,2371
31
- lmnr/sdk/client/asynchronous/async_client.py,sha256=5Hb1Z4tPdUATHgmQ8R6laCqWCxvm5MbxyU4jMmRPnO8,4579
31
+ lmnr/sdk/client/asynchronous/async_client.py,sha256=2mIezUAzUrqk5tCZlzfkID4NzIy_JuyBQtDCAT-2Mro,4585
32
32
  lmnr/sdk/client/asynchronous/resources/__init__.py,sha256=9fkjlVJS8zhnCTITjhow173phBdZlyiae-X0LcTSWqM,381
33
33
  lmnr/sdk/client/asynchronous/resources/agent.py,sha256=Ong3K2KRLN7agx1_-aZxMGcT_OGF3_ZGtFLm8aPMbYw,17788
34
34
  lmnr/sdk/client/asynchronous/resources/base.py,sha256=aJ43Q1rltg23IQaI4eeaZKckxVTgDUbCJrChhQCUEoE,986
35
35
  lmnr/sdk/client/asynchronous/resources/browser_events.py,sha256=T-DUbbAfMQ2VqiVfgVplxuTaJZuoNcC1O6RCxdfw7UQ,1163
36
- lmnr/sdk/client/asynchronous/resources/evals.py,sha256=88PsegaPhqHTuTzMmG9qBrGAgd5peBgdOc_sL9zf2G8,2231
36
+ lmnr/sdk/client/asynchronous/resources/evals.py,sha256=dYFuHmXW_FFNsmKC7_NuhxowzCJVUrRmrxeAJ_7EzOA,5420
37
37
  lmnr/sdk/client/asynchronous/resources/tags.py,sha256=VbsBMp120d_8drGFr1Obp4xSRktzPC-3kOYcblZnvKA,2565
38
38
  lmnr/sdk/client/synchronous/resources/__init__.py,sha256=hDGyNARdG3J25lLAP8JnlER7r8JL-JQuPN1xdheiCw4,318
39
39
  lmnr/sdk/client/synchronous/resources/agent.py,sha256=mnTu6toN2LbgmEhQ-mdZ0CzNAnkrGiksrys0AyMwz2A,17809
40
40
  lmnr/sdk/client/synchronous/resources/base.py,sha256=ne1ZZ10UmNkMrECVvClcEJfcFJlSGvaXOC8K6mZTPdY,971
41
41
  lmnr/sdk/client/synchronous/resources/browser_events.py,sha256=9rFYWZesXQomnFgbZ590tGFMTaNj0OAzT9RcFwD8q_Y,1135
42
- lmnr/sdk/client/synchronous/resources/evals.py,sha256=0vOVY6nNZkvhX6kgmpfeeMHWl97YoO1hUiu6CM8XOuM,3540
42
+ lmnr/sdk/client/synchronous/resources/evals.py,sha256=odN9ZfZnUXKzFZJ6AQDrIjEljqnj8aQKP1ivY188WGo,6667
43
43
  lmnr/sdk/client/synchronous/resources/tags.py,sha256=cNMEzMDhlBNpI7J4x6xkFAANiNSq-Vuu_zi5NPk2kcA,2485
44
- lmnr/sdk/client/synchronous/sync_client.py,sha256=kPS14M0e99xMtLQ_yEOJrFpQWhstqARytkGIbfCoNVc,4906
45
- lmnr/sdk/datasets.py,sha256=jl5Wj5nEI9pww4Jwn4XKF8h0gXBU4TOIrhqNjTJsHZQ,1709
44
+ lmnr/sdk/client/synchronous/sync_client.py,sha256=IIzj-mAwHHoRuUX9KkJtrzTGi5UOygbA8wiA9Aqzf2E,4907
45
+ lmnr/sdk/datasets.py,sha256=P9hRxfl7-I6qhLFFGgU-r_I7RJfLtF6sL56g5fKIbAA,1708
46
46
  lmnr/sdk/decorators.py,sha256=1uu9xxBYgblFqlhQqH17cZYq7babAmB1lEtvBgTsP0E,4468
47
47
  lmnr/sdk/eval_control.py,sha256=KROUrDhcZTrptRZ-hxvr60_o_Gt_8u045jb4cBXcuoY,184
48
- lmnr/sdk/evaluations.py,sha256=ORGfoyxGJmqBTW3yd1mslr4TK0rWlySZLT4GlC6gVuI,21313
49
- lmnr/sdk/laminar.py,sha256=Ha6pJSzUqZQBJNfDtEXwYuMcyRvmsZFj7y3A7f0-Y5I,33785
48
+ lmnr/sdk/evaluations.py,sha256=i5c9wi9ZWV-L7vYbHEnLQC2V34n3tasPRowJAnSr-qQ,21022
49
+ lmnr/sdk/laminar.py,sha256=oOVco_c9ZstT71HsquGsgbtFumXd2Ejz0rl_qpmMlTU,33996
50
50
  lmnr/sdk/log.py,sha256=nt_YMmPw1IRbGy0b7q4rTtP4Yo3pQfNxqJPXK3nDSNQ,2213
51
51
  lmnr/sdk/types.py,sha256=5tEX7yoemb9wYyXLy4aqdazudO5I8dglU5A-IegDhsQ,12653
52
52
  lmnr/sdk/utils.py,sha256=yrcHIhoADf9lWH9qJWZMmkRWYvd0DuxPSLP3mY6YFw0,4327
53
- lmnr/version.py,sha256=By46D14Qb3GmEacSJyQ3-_aLJmKB4PmhLldX9eaM1Ic,1321
54
- lmnr-0.6.9.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
55
- lmnr-0.6.9.dist-info/METADATA,sha256=SYj_iCKcXXUwR8WgW6qs72jYvwVQCKnA9EjpzaqFjRQ,15131
56
- lmnr-0.6.9.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
57
- lmnr-0.6.9.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
58
- lmnr-0.6.9.dist-info/RECORD,,
53
+ lmnr/version.py,sha256=qnwKwsPBqcM4aZx6FBz8GRvhfVyvAhaXzQqQSfXM-k0,1322
54
+ lmnr-0.6.11.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
55
+ lmnr-0.6.11.dist-info/METADATA,sha256=DpYsYjFiUQII2I71j7YJv6F10rgBqGsHCfG_lLQQfhQ,15132
56
+ lmnr-0.6.11.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
57
+ lmnr-0.6.11.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
58
+ lmnr-0.6.11.dist-info/RECORD,,
File without changes
File without changes