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 +16 -3
- lmnr/opentelemetry_lib/__init__.py +2 -0
- lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +4 -2
- lmnr/opentelemetry_lib/tracing/__init__.py +85 -80
- lmnr/sdk/client/asynchronous/async_client.py +4 -2
- lmnr/sdk/client/asynchronous/resources/evals.py +94 -0
- lmnr/sdk/client/synchronous/resources/evals.py +91 -0
- lmnr/sdk/client/synchronous/sync_client.py +3 -1
- lmnr/sdk/datasets.py +1 -1
- lmnr/sdk/evaluations.py +13 -18
- lmnr/sdk/laminar.py +7 -0
- lmnr/version.py +1 -1
- {lmnr-0.6.9.dist-info → lmnr-0.6.11.dist-info}/METADATA +1 -1
- {lmnr-0.6.9.dist-info → lmnr-0.6.11.dist-info}/RECORD +17 -17
- {lmnr-0.6.9.dist-info → lmnr-0.6.11.dist-info}/LICENSE +0 -0
- {lmnr-0.6.9.dist-info → lmnr-0.6.11.dist-info}/WHEEL +0 -0
- {lmnr-0.6.9.dist-info → lmnr-0.6.11.dist-info}/entry_points.txt +0 -0
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
|
104
|
-
|
105
|
-
|
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",
|
@@ -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
|
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
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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.
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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.
|
119
|
-
self.
|
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.
|
122
|
+
self._logger = logging.getLogger(__name__)
|
124
123
|
console_log_handler = logging.StreamHandler()
|
125
124
|
console_log_handler.setFormatter(VerboseColorfulFormatter())
|
126
|
-
self.
|
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
|
-
|
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.
|
144
|
-
cls.instance.
|
145
|
+
if isinstance(cls.instance._span_processor, LaminarSpanProcessor):
|
146
|
+
cls.instance._span_processor.clear()
|
145
147
|
|
146
148
|
def shutdown(self):
|
147
|
-
if self.
|
149
|
+
if self._tracer_provider is None:
|
148
150
|
return
|
149
|
-
self.
|
151
|
+
self._tracer_provider.shutdown()
|
150
152
|
|
151
153
|
def flush(self):
|
152
|
-
|
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.
|
160
|
+
if self._tracer_provider is None:
|
156
161
|
return trace.get_tracer_provider().get_tracer(TRACER_NAME)
|
157
|
-
return self.
|
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
|
92
|
+
def evals(self) -> AsyncEvals:
|
93
93
|
"""Get the Evals resource.
|
94
94
|
|
95
95
|
Returns:
|
96
|
-
|
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
|
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.
|
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
|
205
|
-
"
|
206
|
-
"
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
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.
|
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
|
-
|
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.
|
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.
|
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
@@ -1,14 +1,14 @@
|
|
1
1
|
lmnr/__init__.py,sha256=eJ-gIHEk8KV-BeaU8c9spQww_T2G5_OMu4F8JEzngvA,1281
|
2
|
-
lmnr/cli.py,sha256=
|
2
|
+
lmnr/cli.py,sha256=uHgLUfN_6eINtUlcQdOtODf2tI9AiwmlhojQF4UMB5Y,6047
|
3
3
|
lmnr/opentelemetry_lib/.flake8,sha256=bCxuDlGx3YQ55QHKPiGJkncHanh9qGjQJUujcFa3lAU,150
|
4
|
-
lmnr/opentelemetry_lib/__init__.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
45
|
-
lmnr/sdk/datasets.py,sha256=
|
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=
|
49
|
-
lmnr/sdk/laminar.py,sha256=
|
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=
|
54
|
-
lmnr-0.6.
|
55
|
-
lmnr-0.6.
|
56
|
-
lmnr-0.6.
|
57
|
-
lmnr-0.6.
|
58
|
-
lmnr-0.6.
|
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
|
File without changes
|