lmnr 0.4.40__tar.gz → 0.4.42__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.
- {lmnr-0.4.40 → lmnr-0.4.42}/PKG-INFO +14 -1
- {lmnr-0.4.40 → lmnr-0.4.42}/README.md +13 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/pyproject.toml +4 -3
- lmnr-0.4.42/src/lmnr/cli.py +53 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/tracing/tracing.py +1 -5
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/datasets.py +2 -4
- lmnr-0.4.42/src/lmnr/sdk/eval_control.py +4 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/evaluations.py +11 -28
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/laminar.py +121 -46
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/types.py +31 -2
- lmnr-0.4.40/src/lmnr/cli.py +0 -39
- {lmnr-0.4.40 → lmnr-0.4.42}/LICENSE +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/__init__.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/.flake8 +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/.python-version +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/__init__.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/config/__init__.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/decorators/__init__.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/decorators/base.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/instruments.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/tracing/__init__.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/tracing/attributes.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/tracing/content_allow_list.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/tracing/context_manager.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/utils/__init__.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/utils/in_memory_span_exporter.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/utils/json_encoder.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/utils/package_check.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/openllmetry_sdk/version.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/__init__.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/decorators.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/log.py +0 -0
- {lmnr-0.4.40 → lmnr-0.4.42}/src/lmnr/sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: lmnr
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.42
|
4
4
|
Summary: Python SDK for Laminar AI
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: lmnr.ai
|
@@ -38,6 +38,7 @@ Provides-Extra: transformers
|
|
38
38
|
Provides-Extra: vertexai
|
39
39
|
Provides-Extra: watsonx
|
40
40
|
Provides-Extra: weaviate
|
41
|
+
Requires-Dist: aiohttp (>=3.0,<4.0)
|
41
42
|
Requires-Dist: argparse (>=1.0,<2.0)
|
42
43
|
Requires-Dist: backoff (>=2.0,<3.0)
|
43
44
|
Requires-Dist: deprecated (>=1.0,<2.0)
|
@@ -290,3 +291,15 @@ PipelineRunResponse(
|
|
290
291
|
)
|
291
292
|
```
|
292
293
|
|
294
|
+
## Semantic search
|
295
|
+
|
296
|
+
You can perform a semantic search on a dataset in Laminar by calling `Laminar.semantic_search`.
|
297
|
+
|
298
|
+
```python
|
299
|
+
response = Laminar.semantic_search(
|
300
|
+
query="Greatest Chinese architectural wonders",
|
301
|
+
dataset_id=uuid.UUID("413f8404-724c-4aa4-af16-714d84fd7958"),
|
302
|
+
)
|
303
|
+
```
|
304
|
+
|
305
|
+
[Read more](https://docs.lmnr.ai/datasets/indexing) about indexing and semantic search.
|
@@ -204,3 +204,16 @@ PipelineRunResponse(
|
|
204
204
|
run_id='53b012d5-5759-48a6-a9c5-0011610e3669'
|
205
205
|
)
|
206
206
|
```
|
207
|
+
|
208
|
+
## Semantic search
|
209
|
+
|
210
|
+
You can perform a semantic search on a dataset in Laminar by calling `Laminar.semantic_search`.
|
211
|
+
|
212
|
+
```python
|
213
|
+
response = Laminar.semantic_search(
|
214
|
+
query="Greatest Chinese architectural wonders",
|
215
|
+
dataset_id=uuid.UUID("413f8404-724c-4aa4-af16-714d84fd7958"),
|
216
|
+
)
|
217
|
+
```
|
218
|
+
|
219
|
+
[Read more](https://docs.lmnr.ai/datasets/indexing) about indexing and semantic search.
|
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
[project]
|
8
8
|
name = "lmnr"
|
9
|
-
version = "0.4.
|
9
|
+
version = "0.4.42"
|
10
10
|
description = "Python SDK for Laminar AI"
|
11
11
|
authors = [
|
12
12
|
{ name = "lmnr.ai", email = "founders@lmnr.ai" }
|
@@ -17,7 +17,7 @@ license = "Apache-2.0"
|
|
17
17
|
|
18
18
|
[tool.poetry]
|
19
19
|
name = "lmnr"
|
20
|
-
version = "0.4.
|
20
|
+
version = "0.4.42"
|
21
21
|
description = "Python SDK for Laminar AI"
|
22
22
|
authors = ["lmnr.ai"]
|
23
23
|
readme = "README.md"
|
@@ -43,6 +43,7 @@ jinja2 = "~=3.0"
|
|
43
43
|
deprecated = "~=1.0"
|
44
44
|
tqdm = "~=4.0"
|
45
45
|
argparse = "~=1.0"
|
46
|
+
aiohttp = "~=3.0"
|
46
47
|
opentelemetry-instrumentation-alephalpha = {version = ">=0.33.12", optional = true}
|
47
48
|
opentelemetry-instrumentation-anthropic = {version = ">=0.33.12", optional = true}
|
48
49
|
opentelemetry-instrumentation-bedrock = {version = ">=0.33.12", optional = true}
|
@@ -69,6 +70,7 @@ opentelemetry-instrumentation-vertexai = {version = ">=0.33.12", optional = true
|
|
69
70
|
opentelemetry-instrumentation-watsonx = {version = ">=0.33.12", optional = true}
|
70
71
|
opentelemetry-instrumentation-weaviate = {version = ">=0.33.12", optional = true}
|
71
72
|
|
73
|
+
[tool.poetry.extras]
|
72
74
|
# List of all possible extras. You can specify one or more of these extras
|
73
75
|
# when installing the package, using any of the following:
|
74
76
|
# `pip install 'lmnr[anthropic,openai]'`
|
@@ -78,7 +80,6 @@ opentelemetry-instrumentation-weaviate = {version = ">=0.33.12", optional = true
|
|
78
80
|
|
79
81
|
# `all` is the group added for convenience, if you want to install all
|
80
82
|
# the instrumentations.
|
81
|
-
[tool.poetry.extras]
|
82
83
|
all = [
|
83
84
|
"opentelemetry-instrumentation-alephalpha",
|
84
85
|
"opentelemetry-instrumentation-anthropic",
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from argparse import ArgumentParser
|
2
|
+
import asyncio
|
3
|
+
import importlib.util
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
|
7
|
+
from .sdk.eval_control import PREPARE_ONLY, EVALUATION_INSTANCE
|
8
|
+
|
9
|
+
|
10
|
+
async def run_evaluation(args):
|
11
|
+
sys.path.append(os.getcwd())
|
12
|
+
|
13
|
+
prep_token = PREPARE_ONLY.set(True)
|
14
|
+
try:
|
15
|
+
file = os.path.abspath(args.file)
|
16
|
+
name = "user_module"
|
17
|
+
|
18
|
+
spec = importlib.util.spec_from_file_location(name, file)
|
19
|
+
if spec is None or spec.loader is None:
|
20
|
+
raise ImportError(f"Could not load module specification from {file}")
|
21
|
+
mod = importlib.util.module_from_spec(spec)
|
22
|
+
sys.modules[name] = mod
|
23
|
+
|
24
|
+
spec.loader.exec_module(mod)
|
25
|
+
evaluation = EVALUATION_INSTANCE.get()
|
26
|
+
if evaluation is None:
|
27
|
+
raise RuntimeError("Evaluation instance not found")
|
28
|
+
|
29
|
+
await evaluation.run()
|
30
|
+
finally:
|
31
|
+
PREPARE_ONLY.reset(prep_token)
|
32
|
+
|
33
|
+
|
34
|
+
def cli():
|
35
|
+
parser = ArgumentParser(
|
36
|
+
prog="lmnr",
|
37
|
+
description="CLI for Laminar",
|
38
|
+
)
|
39
|
+
|
40
|
+
subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
|
41
|
+
|
42
|
+
parser_eval = subparsers.add_parser(
|
43
|
+
"eval",
|
44
|
+
description="Run an evaluation",
|
45
|
+
help="Run an evaluation",
|
46
|
+
)
|
47
|
+
parser_eval.add_argument("file", help="A file containing the evaluation to run")
|
48
|
+
|
49
|
+
parsed = parser.parse_args()
|
50
|
+
if parsed.subcommand == "eval":
|
51
|
+
asyncio.run(run_evaluation(parsed))
|
52
|
+
else:
|
53
|
+
parser.print_help()
|
@@ -183,11 +183,7 @@ class TracerWrapper(object):
|
|
183
183
|
|
184
184
|
@classmethod
|
185
185
|
def verify_initialized(cls) -> bool:
|
186
|
-
|
187
|
-
return True
|
188
|
-
|
189
|
-
cls.__logger.warning("Laminar not initialized, make sure to initialize")
|
190
|
-
return False
|
186
|
+
return hasattr(cls, "instance")
|
191
187
|
|
192
188
|
def flush(self):
|
193
189
|
self.__spans_processor.force_flush()
|
@@ -3,7 +3,6 @@ import re
|
|
3
3
|
import sys
|
4
4
|
import uuid
|
5
5
|
|
6
|
-
from contextlib import contextmanager
|
7
6
|
from tqdm import tqdm
|
8
7
|
from typing import Any, Awaitable, Optional, Set, Union
|
9
8
|
|
@@ -11,6 +10,7 @@ from ..openllmetry_sdk.instruments import Instruments
|
|
11
10
|
from ..openllmetry_sdk.tracing.attributes import SPAN_TYPE
|
12
11
|
|
13
12
|
from .datasets import EvaluationDataset
|
13
|
+
from .eval_control import EVALUATION_INSTANCE, PREPARE_ONLY
|
14
14
|
from .laminar import Laminar as L
|
15
15
|
from .log import get_default_logger
|
16
16
|
from .types import (
|
@@ -28,21 +28,6 @@ from .utils import is_async
|
|
28
28
|
|
29
29
|
DEFAULT_BATCH_SIZE = 5
|
30
30
|
|
31
|
-
_evaluation = None
|
32
|
-
_set_global_evaluation = False
|
33
|
-
|
34
|
-
|
35
|
-
@contextmanager
|
36
|
-
def set_global_evaluation(set_global_evaluation: bool):
|
37
|
-
global _set_global_evaluation
|
38
|
-
original = _set_global_evaluation
|
39
|
-
try:
|
40
|
-
_set_global_evaluation = set_global_evaluation
|
41
|
-
yield
|
42
|
-
finally:
|
43
|
-
_set_global_evaluation = original
|
44
|
-
pass
|
45
|
-
|
46
31
|
|
47
32
|
def get_evaluation_url(project_id: str, evaluation_id: str):
|
48
33
|
return f"https://www.lmnr.ai/project/{project_id}/evaluations/{evaluation_id}"
|
@@ -198,15 +183,10 @@ class Evaluation:
|
|
198
183
|
instruments=instruments,
|
199
184
|
)
|
200
185
|
|
201
|
-
def run(self) ->
|
186
|
+
async def run(self) -> Awaitable[None]:
|
202
187
|
if self.is_finished:
|
203
188
|
raise Exception("Evaluation is already finished")
|
204
|
-
|
205
|
-
loop = asyncio.get_event_loop()
|
206
|
-
if loop.is_running():
|
207
|
-
return loop.create_task(self._run())
|
208
|
-
else:
|
209
|
-
return loop.run_until_complete(self._run())
|
189
|
+
return await self._run()
|
210
190
|
|
211
191
|
async def _run(self) -> None:
|
212
192
|
self.reporter.start(len(self.data))
|
@@ -224,7 +204,7 @@ class Evaluation:
|
|
224
204
|
for result_datapoint in result_datapoints:
|
225
205
|
result_datapoint.human_evaluators = self.human_evaluators or {}
|
226
206
|
|
227
|
-
evaluation = L.create_evaluation(
|
207
|
+
evaluation = await L.create_evaluation(
|
228
208
|
data=result_datapoints, group_id=self.group_id, name=self.name
|
229
209
|
)
|
230
210
|
average_scores = get_average_scores(result_datapoints)
|
@@ -389,8 +369,11 @@ def evaluate(
|
|
389
369
|
instruments=instruments,
|
390
370
|
)
|
391
371
|
|
392
|
-
|
393
|
-
|
394
|
-
_evaluation = evaluation
|
372
|
+
if PREPARE_ONLY.get():
|
373
|
+
EVALUATION_INSTANCE.set(evaluation)
|
395
374
|
else:
|
396
|
-
|
375
|
+
loop = asyncio.get_event_loop()
|
376
|
+
if loop.is_running():
|
377
|
+
return loop.run_until_complete(evaluation.run())
|
378
|
+
else:
|
379
|
+
return asyncio.run(evaluation.run())
|
@@ -16,8 +16,10 @@ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExport
|
|
16
16
|
from opentelemetry.util.types import AttributeValue
|
17
17
|
|
18
18
|
from pydantic.alias_generators import to_snake
|
19
|
-
from typing import Any, Literal, Optional, Set, Union
|
19
|
+
from typing import Any, Awaitable, Literal, Optional, Set, Union
|
20
20
|
|
21
|
+
import aiohttp
|
22
|
+
import asyncio
|
21
23
|
import copy
|
22
24
|
import datetime
|
23
25
|
import dotenv
|
@@ -25,8 +27,8 @@ import json
|
|
25
27
|
import logging
|
26
28
|
import os
|
27
29
|
import random
|
28
|
-
import re
|
29
30
|
import requests
|
31
|
+
import re
|
30
32
|
import urllib.parse
|
31
33
|
import uuid
|
32
34
|
|
@@ -54,6 +56,8 @@ from .types import (
|
|
54
56
|
PipelineRunResponse,
|
55
57
|
NodeInput,
|
56
58
|
PipelineRunRequest,
|
59
|
+
SemanticSearchRequest,
|
60
|
+
SemanticSearchResponse,
|
57
61
|
TraceType,
|
58
62
|
)
|
59
63
|
|
@@ -64,7 +68,6 @@ class Laminar:
|
|
64
68
|
__project_api_key: Optional[str] = None
|
65
69
|
__env: dict[str, str] = {}
|
66
70
|
__initialized: bool = False
|
67
|
-
__http_session: Optional[requests.Session] = None
|
68
71
|
|
69
72
|
@classmethod
|
70
73
|
def initialize(
|
@@ -129,7 +132,6 @@ class Laminar:
|
|
129
132
|
cls.__env = env
|
130
133
|
cls.__initialized = True
|
131
134
|
cls._initialize_logger()
|
132
|
-
cls.__http_session = requests.Session()
|
133
135
|
Traceloop.init(
|
134
136
|
exporter=OTLPSpanExporter(
|
135
137
|
endpoint=cls.__base_grpc_url,
|
@@ -164,8 +166,9 @@ class Laminar:
|
|
164
166
|
metadata: dict[str, str] = {},
|
165
167
|
parent_span_id: Optional[uuid.UUID] = None,
|
166
168
|
trace_id: Optional[uuid.UUID] = None,
|
167
|
-
) -> PipelineRunResponse:
|
168
|
-
"""Runs the pipeline with the given inputs
|
169
|
+
) -> Union[PipelineRunResponse, Awaitable[PipelineRunResponse]]:
|
170
|
+
"""Runs the pipeline with the given inputs. If called from an async
|
171
|
+
function, must be awaited.
|
169
172
|
|
170
173
|
Args:
|
171
174
|
pipeline (str): name of the Laminar pipeline.\
|
@@ -215,34 +218,47 @@ class Laminar:
|
|
215
218
|
parent_span_id=parent_span_id,
|
216
219
|
trace_id=trace_id,
|
217
220
|
)
|
221
|
+
loop = asyncio.get_event_loop()
|
222
|
+
if loop.is_running():
|
223
|
+
return cls.__run(request)
|
224
|
+
else:
|
225
|
+
return asyncio.run(cls.__run(request))
|
218
226
|
except Exception as e:
|
219
227
|
raise ValueError(f"Invalid request: {e}")
|
220
228
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
229
|
+
@classmethod
|
230
|
+
def semantic_search(
|
231
|
+
cls,
|
232
|
+
query: str,
|
233
|
+
dataset_id: uuid.UUID,
|
234
|
+
limit: Optional[int] = None,
|
235
|
+
threshold: Optional[float] = None,
|
236
|
+
) -> SemanticSearchResponse:
|
237
|
+
"""Perform a semantic search on a dataset. If called from an async
|
238
|
+
function, must be awaited.
|
239
|
+
|
240
|
+
Args:
|
241
|
+
query (str): query string to search by
|
242
|
+
dataset_id (uuid.UUID): id of the dataset to search in
|
243
|
+
limit (Optional[int], optional): maximum number of results to\
|
244
|
+
return. Defaults to None.
|
245
|
+
threshold (Optional[float], optional): minimum score for a result\
|
246
|
+
to be returned. Defaults to None.
|
247
|
+
|
248
|
+
Returns:
|
249
|
+
SemanticSearchResponse: response object containing the search results sorted by score in descending order
|
250
|
+
"""
|
251
|
+
request = SemanticSearchRequest(
|
252
|
+
query=query,
|
253
|
+
dataset_id=dataset_id,
|
254
|
+
limit=limit,
|
255
|
+
threshold=threshold,
|
233
256
|
)
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
for key in keys:
|
240
|
-
value = resp_json[key]
|
241
|
-
del resp_json[key]
|
242
|
-
resp_json[to_snake(key)] = value
|
243
|
-
return PipelineRunResponse(**resp_json)
|
244
|
-
except Exception:
|
245
|
-
raise PipelineRunError(response)
|
257
|
+
loop = asyncio.get_event_loop()
|
258
|
+
if loop.is_running():
|
259
|
+
return cls.__semantic_search(request)
|
260
|
+
else:
|
261
|
+
return asyncio.run(cls.__semantic_search(request))
|
246
262
|
|
247
263
|
@classmethod
|
248
264
|
def event(
|
@@ -329,6 +345,9 @@ class Laminar:
|
|
329
345
|
span. Defaults to None.
|
330
346
|
"""
|
331
347
|
|
348
|
+
if not cls.is_initialized():
|
349
|
+
yield
|
350
|
+
|
332
351
|
with get_tracer() as tracer:
|
333
352
|
span_path = get_span_path(name)
|
334
353
|
ctx = set_value("span_path", span_path, context)
|
@@ -646,30 +665,33 @@ class Laminar:
|
|
646
665
|
set_association_properties(props)
|
647
666
|
|
648
667
|
@classmethod
|
649
|
-
def create_evaluation(
|
668
|
+
async def create_evaluation(
|
650
669
|
cls,
|
651
670
|
data: list[EvaluationResultDatapoint],
|
652
671
|
group_id: Optional[str] = None,
|
653
672
|
name: Optional[str] = None,
|
654
673
|
) -> CreateEvaluationResponse:
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
{
|
674
|
+
async with aiohttp.ClientSession() as session:
|
675
|
+
async with session.post(
|
676
|
+
cls.__base_http_url + "/v1/evaluations",
|
677
|
+
json={
|
659
678
|
"groupId": group_id,
|
660
679
|
"name": name,
|
661
680
|
"points": [datapoint.to_dict() for datapoint in data],
|
662
|
-
}
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
681
|
+
},
|
682
|
+
headers=cls._headers(),
|
683
|
+
) as response:
|
684
|
+
if response.status != 200:
|
685
|
+
try:
|
686
|
+
resp_json = await response.json()
|
687
|
+
raise ValueError(
|
688
|
+
f"Error creating evaluation {json.dumps(resp_json)}"
|
689
|
+
)
|
690
|
+
except aiohttp.ClientError:
|
691
|
+
text = await response.text()
|
692
|
+
raise ValueError(f"Error creating evaluation {text}")
|
693
|
+
resp_json = await response.json()
|
694
|
+
return CreateEvaluationResponse.model_validate(resp_json)
|
673
695
|
|
674
696
|
@classmethod
|
675
697
|
def get_datapoints(
|
@@ -678,6 +700,10 @@ class Laminar:
|
|
678
700
|
offset: int,
|
679
701
|
limit: int,
|
680
702
|
) -> GetDatapointsResponse:
|
703
|
+
# TODO: Use aiohttp. Currently, this function is called from within
|
704
|
+
# `LaminarDataset.__len__`, which is sync, but can be called from
|
705
|
+
# both sync and async. Python does not make it easy to mix things this
|
706
|
+
# way, so we should probably refactor `LaminarDataset`.
|
681
707
|
params = {"name": dataset_name, "offset": offset, "limit": limit}
|
682
708
|
url = (
|
683
709
|
cls.__base_http_url
|
@@ -704,3 +730,52 @@ class Laminar:
|
|
704
730
|
"Authorization": "Bearer " + cls.__project_api_key,
|
705
731
|
"Content-Type": "application/json",
|
706
732
|
}
|
733
|
+
|
734
|
+
@classmethod
|
735
|
+
async def __run(
|
736
|
+
cls,
|
737
|
+
request: PipelineRunRequest,
|
738
|
+
) -> PipelineRunResponse:
|
739
|
+
async with aiohttp.ClientSession() as session:
|
740
|
+
async with session.post(
|
741
|
+
cls.__base_http_url + "/v1/pipeline/run",
|
742
|
+
data=json.dumps(request.to_dict()),
|
743
|
+
headers=cls._headers(),
|
744
|
+
) as response:
|
745
|
+
if response.status != 200:
|
746
|
+
raise PipelineRunError(response)
|
747
|
+
try:
|
748
|
+
resp_json = await response.json()
|
749
|
+
keys = list(resp_json.keys())
|
750
|
+
for key in keys:
|
751
|
+
value = resp_json[key]
|
752
|
+
del resp_json[key]
|
753
|
+
resp_json[to_snake(key)] = value
|
754
|
+
return PipelineRunResponse(**resp_json)
|
755
|
+
except Exception:
|
756
|
+
raise PipelineRunError(response)
|
757
|
+
|
758
|
+
@classmethod
|
759
|
+
async def __semantic_search(
|
760
|
+
cls,
|
761
|
+
request: SemanticSearchRequest,
|
762
|
+
) -> SemanticSearchResponse:
|
763
|
+
async with aiohttp.ClientSession() as session:
|
764
|
+
async with session.post(
|
765
|
+
cls.__base_http_url + "/v1/semantic-search",
|
766
|
+
data=json.dumps(request.to_dict()),
|
767
|
+
headers=cls._headers(),
|
768
|
+
) as response:
|
769
|
+
if response.status != 200:
|
770
|
+
raise ValueError(
|
771
|
+
f"Error performing semantic search: [{response.status}] {response.text}"
|
772
|
+
)
|
773
|
+
try:
|
774
|
+
resp_json = await response.json()
|
775
|
+
for result in resp_json["results"]:
|
776
|
+
result["dataset_id"] = uuid.UUID(result["datasetId"])
|
777
|
+
return SemanticSearchResponse(**resp_json)
|
778
|
+
except Exception as e:
|
779
|
+
raise ValueError(
|
780
|
+
f"Error parsing semantic search response: status={response.status} error={e}"
|
781
|
+
)
|
@@ -1,7 +1,7 @@
|
|
1
|
+
import aiohttp
|
1
2
|
import datetime
|
2
3
|
from enum import Enum
|
3
4
|
import pydantic
|
4
|
-
import requests
|
5
5
|
from typing import Any, Awaitable, Callable, Optional, Union
|
6
6
|
import uuid
|
7
7
|
|
@@ -55,11 +55,40 @@ class PipelineRunResponse(pydantic.BaseModel):
|
|
55
55
|
run_id: str
|
56
56
|
|
57
57
|
|
58
|
+
class SemanticSearchRequest(pydantic.BaseModel):
|
59
|
+
query: str
|
60
|
+
dataset_id: uuid.UUID
|
61
|
+
limit: Optional[int] = pydantic.Field(default=None)
|
62
|
+
threshold: Optional[float] = pydantic.Field(default=None, ge=0.0, le=1.0)
|
63
|
+
|
64
|
+
def to_dict(self):
|
65
|
+
res = {
|
66
|
+
"query": self.query,
|
67
|
+
"datasetId": str(self.dataset_id),
|
68
|
+
}
|
69
|
+
if self.limit is not None:
|
70
|
+
res["limit"] = self.limit
|
71
|
+
if self.threshold is not None:
|
72
|
+
res["threshold"] = self.threshold
|
73
|
+
return res
|
74
|
+
|
75
|
+
|
76
|
+
class SemanticSearchResult(pydantic.BaseModel):
|
77
|
+
dataset_id: uuid.UUID
|
78
|
+
score: float
|
79
|
+
data: dict[str, Any]
|
80
|
+
content: str
|
81
|
+
|
82
|
+
|
83
|
+
class SemanticSearchResponse(pydantic.BaseModel):
|
84
|
+
results: list[SemanticSearchResult]
|
85
|
+
|
86
|
+
|
58
87
|
class PipelineRunError(Exception):
|
59
88
|
error_code: str
|
60
89
|
error_message: str
|
61
90
|
|
62
|
-
def __init__(self, response:
|
91
|
+
def __init__(self, response: aiohttp.ClientResponse):
|
63
92
|
try:
|
64
93
|
resp_json = response.json()
|
65
94
|
self.error_code = resp_json["error_code"]
|
lmnr-0.4.40/src/lmnr/cli.py
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
from argparse import ArgumentParser
|
2
|
-
import asyncio
|
3
|
-
import importlib
|
4
|
-
import os
|
5
|
-
import sys
|
6
|
-
|
7
|
-
from lmnr.sdk.evaluations import set_global_evaluation
|
8
|
-
|
9
|
-
|
10
|
-
# TODO: Refactor this code
|
11
|
-
async def run_evaluation(args):
|
12
|
-
sys.path.insert(0, os.getcwd())
|
13
|
-
|
14
|
-
with set_global_evaluation(True):
|
15
|
-
file = os.path.abspath(args.file)
|
16
|
-
|
17
|
-
spec = importlib.util.spec_from_file_location("run_eval", file)
|
18
|
-
mod = importlib.util.module_from_spec(spec)
|
19
|
-
spec.loader.exec_module(mod)
|
20
|
-
|
21
|
-
from lmnr.sdk.evaluations import _evaluation
|
22
|
-
evaluation = _evaluation
|
23
|
-
await evaluation.run()
|
24
|
-
|
25
|
-
|
26
|
-
def cli():
|
27
|
-
parser = ArgumentParser(
|
28
|
-
prog="lmnr",
|
29
|
-
description="CLI for Laminar",
|
30
|
-
)
|
31
|
-
|
32
|
-
subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
|
33
|
-
|
34
|
-
parser_eval = subparsers.add_parser("eval", description="Run an evaluation")
|
35
|
-
parser_eval.add_argument("file", help="A file containing the evaluation to run")
|
36
|
-
parser_eval.set_defaults(func=run_evaluation)
|
37
|
-
|
38
|
-
parsed = parser.parse_args()
|
39
|
-
asyncio.run(parsed.func(parsed))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|