lmnr 0.4.47__py3-none-any.whl → 0.4.49__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
@@ -1,40 +1,74 @@
1
1
  from argparse import ArgumentParser
2
2
  import asyncio
3
3
  import importlib.util
4
+ import logging
4
5
  import os
6
+ import re
5
7
  import sys
6
8
 
7
9
  from .sdk.eval_control import PREPARE_ONLY, EVALUATION_INSTANCE
10
+ from .sdk.log import ColorfulFormatter
11
+
12
+ LOG = logging.getLogger(__name__)
13
+ console_log_handler = logging.StreamHandler()
14
+ console_log_handler.setFormatter(ColorfulFormatter())
15
+ LOG.addHandler(console_log_handler)
16
+
17
+
18
+ EVAL_DIR = "evals"
8
19
 
9
20
 
10
21
  async def run_evaluation(args):
11
22
  sys.path.append(os.getcwd())
12
23
 
13
- prep_token = PREPARE_ONLY.set(True)
14
- try:
15
- file = os.path.abspath(args.file)
16
- name = "user_module"
24
+ if args.file is None:
25
+ files = [
26
+ os.path.join(EVAL_DIR, f)
27
+ for f in os.listdir(EVAL_DIR)
28
+ if re.match(r".*_eval\.py$", f) or re.match(r"eval_.*\.py$", f)
29
+ ]
30
+ if len(files) == 0:
31
+ LOG.error("No evaluation files found in evals directory")
32
+ return
33
+ files.sort()
34
+ LOG.info(f"Located {len(files)} evaluation files in {EVAL_DIR}")
35
+
36
+ else:
37
+ files = [args.file]
17
38
 
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
39
+ for file in files:
40
+ prep_token = PREPARE_ONLY.set(True)
41
+ LOG.info(f"Running evaluation from {file}")
42
+ try:
43
+ file = os.path.abspath(file)
44
+ name = "user_module" + file
23
45
 
24
- spec.loader.exec_module(mod)
25
- evaluation = EVALUATION_INSTANCE.get()
26
- if evaluation is None:
27
- raise RuntimeError("Evaluation instance not found")
46
+ spec = importlib.util.spec_from_file_location(name, file)
47
+ if spec is None or spec.loader is None:
48
+ LOG.error(f"Could not load module specification from {file}")
49
+ if args.fail_on_error:
50
+ return
51
+ continue
52
+ mod = importlib.util.module_from_spec(spec)
53
+ sys.modules[name] = mod
28
54
 
29
- await evaluation.run()
30
- finally:
31
- PREPARE_ONLY.reset(prep_token)
55
+ spec.loader.exec_module(mod)
56
+ evaluation = EVALUATION_INSTANCE.get()
57
+ if evaluation is None:
58
+ LOG.warning("Evaluation instance not found")
59
+ if args.fail_on_error:
60
+ return
61
+ continue
62
+
63
+ await evaluation.run()
64
+ finally:
65
+ PREPARE_ONLY.reset(prep_token)
32
66
 
33
67
 
34
68
  def cli():
35
69
  parser = ArgumentParser(
36
70
  prog="lmnr",
37
- description="CLI for Laminar",
71
+ description="CLI for Laminar. Call `lmnr [subcommand] --help` for more information on each subcommand.",
38
72
  )
39
73
 
40
74
  subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")
@@ -44,7 +78,21 @@ def cli():
44
78
  description="Run an evaluation",
45
79
  help="Run an evaluation",
46
80
  )
47
- parser_eval.add_argument("file", help="A file containing the evaluation to run")
81
+ parser_eval.add_argument(
82
+ "file",
83
+ nargs="?",
84
+ help="A file containing the evaluation to run."
85
+ + "If no file name is provided, all evaluation files in the `evals` directory are run as long"
86
+ + "as they match *_eval.py or eval_*.py",
87
+ default=None,
88
+ )
89
+
90
+ parser_eval.add_argument(
91
+ "--fail-on-error",
92
+ action="store_true",
93
+ default=False,
94
+ help="Fail on error",
95
+ )
48
96
 
49
97
  parsed = parser.parse_args()
50
98
  if parsed.subcommand == "eval":
lmnr/sdk/evaluations.py CHANGED
@@ -29,8 +29,17 @@ from .utils import is_async
29
29
  DEFAULT_BATCH_SIZE = 5
30
30
 
31
31
 
32
- def get_evaluation_url(project_id: str, evaluation_id: str):
33
- return f"https://www.lmnr.ai/project/{project_id}/evaluations/{evaluation_id}"
32
+ def get_evaluation_url(
33
+ project_id: str, evaluation_id: str, base_url: str = "https://www.lmnr.ai"
34
+ ):
35
+ url = base_url
36
+ if url.endswith("/"):
37
+ url = url[:-1]
38
+ if url.endswith("localhost") or url.endswith("127.0.0.1"):
39
+ # We best effort assume that the frontend is running on port 3000
40
+ # TODO: expose the frontend port?
41
+ url = url + ":3000"
42
+ return f"{url}/project/{project_id}/evaluations/{evaluation_id}"
34
43
 
35
44
 
36
45
  def get_average_scores(results: list[EvaluationResultDatapoint]) -> dict[str, Numeric]:
@@ -49,8 +58,8 @@ def get_average_scores(results: list[EvaluationResultDatapoint]) -> dict[str, Nu
49
58
 
50
59
 
51
60
  class EvaluationReporter:
52
- def __init__(self):
53
- pass
61
+ def __init__(self, base_url: str = "https://www.lmnr.ai"):
62
+ self.base_url = base_url
54
63
 
55
64
  def start(self, length: int):
56
65
  self.cli_progress = tqdm(
@@ -71,7 +80,7 @@ class EvaluationReporter:
71
80
  ):
72
81
  self.cli_progress.close()
73
82
  print(
74
- f"\nCheck the results at {get_evaluation_url(project_id, evaluation_id)}\n"
83
+ f"\nCheck the results at {get_evaluation_url(project_id, evaluation_id, self.base_url)}\n"
75
84
  )
76
85
  print("Average scores:")
77
86
  for name, score in average_scores.items():
@@ -160,7 +169,7 @@ class Evaluation:
160
169
  )
161
170
 
162
171
  self.is_finished = False
163
- self.reporter = EvaluationReporter()
172
+ self.reporter = EvaluationReporter(base_url)
164
173
  if isinstance(data, list):
165
174
  self.data = [
166
175
  (Datapoint.model_validate(point) if isinstance(point, dict) else point)
lmnr/sdk/laminar.py CHANGED
@@ -12,6 +12,7 @@ from lmnr.openllmetry_sdk.tracing.attributes import (
12
12
  from lmnr.openllmetry_sdk.decorators.base import json_dumps
13
13
  from opentelemetry import context, trace
14
14
  from opentelemetry.context import attach, detach, set_value
15
+ from opentelemetry.sdk.trace import SpanProcessor
15
16
  from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
16
17
  from opentelemetry.util.types import AttributeValue
17
18
 
@@ -79,6 +80,7 @@ class Laminar:
79
80
  http_port: Optional[int] = None,
80
81
  grpc_port: Optional[int] = None,
81
82
  instruments: Optional[Set[Instruments]] = None,
83
+ _processor: Optional[SpanProcessor] = None,
82
84
  ):
83
85
  """Initialize Laminar context across the application.
84
86
  This method must be called before using any other Laminar methods or
@@ -133,6 +135,7 @@ class Laminar:
133
135
  cls.__env = env
134
136
  cls.__initialized = True
135
137
  cls._initialize_logger()
138
+
136
139
  Traceloop.init(
137
140
  exporter=OTLPSpanExporter(
138
141
  endpoint=cls.__base_grpc_url,
@@ -268,13 +271,9 @@ class Laminar:
268
271
  value: Optional[AttributeValue] = None,
269
272
  timestamp: Optional[Union[datetime.datetime, int]] = None,
270
273
  ):
271
- """Associate an event with the current span. If event with such
272
- name never existed, Laminar will create a new event and infer its type
273
- from the value. If the event already exists, Laminar will append the
274
- value to the event if and only if the value is of a matching type.
275
- Otherwise, the event won't be recorded.
276
- Supported types are string, numeric, and boolean. If the value
277
- is `None`, event is considered a boolean tag with the value of `True`.
274
+ """Associate an event with the current span. If using manual\
275
+ instrumentation, use raw OpenTelemetry `span.add_event()` instead.\
276
+ `value` will be saved as a `lmnr.event.value` attribute.
278
277
 
279
278
  Args:
280
279
  name (str): event name
@@ -400,7 +399,7 @@ class Laminar:
400
399
  )
401
400
  yield span
402
401
 
403
- # # TODO: Figure out if this is necessary
402
+ # TODO: Figure out if this is necessary
404
403
  try:
405
404
  detach(ctx_token)
406
405
  except Exception:
@@ -672,9 +671,9 @@ class Laminar:
672
671
  def clear_metadata(cls):
673
672
  """Clear the metadata from the context"""
674
673
  props: dict = copy.copy(context.get_value("association_properties"))
675
- for k in props.keys():
676
- if k.startswith("metadata."):
677
- props.pop(k)
674
+ metadata_keys = [k for k in props.keys() if k.startswith("metadata.")]
675
+ for k in metadata_keys:
676
+ props.pop(k)
678
677
  set_association_properties(props)
679
678
 
680
679
  @classmethod
lmnr/sdk/log.py CHANGED
@@ -24,6 +24,29 @@ class CustomFormatter(logging.Formatter):
24
24
  return formatter.format(record)
25
25
 
26
26
 
27
+ class ColorfulFormatter(logging.Formatter):
28
+ grey = "\x1b[38;20m"
29
+ green = "\x1b[32;20m"
30
+ yellow = "\x1b[33;20m"
31
+ red = "\x1b[31;20m"
32
+ bold_red = "\x1b[31;1m"
33
+ reset = "\x1b[0m"
34
+ fmt = "Laminar %(levelname)s: %(message)s"
35
+
36
+ FORMATS = {
37
+ logging.DEBUG: grey + fmt + reset,
38
+ logging.INFO: green + fmt + reset,
39
+ logging.WARNING: yellow + fmt + reset,
40
+ logging.ERROR: red + fmt + reset,
41
+ logging.CRITICAL: bold_red + fmt + reset,
42
+ }
43
+
44
+ def format(self, record: logging.LogRecord):
45
+ log_fmt = self.FORMATS.get(record.levelno)
46
+ formatter = logging.Formatter(log_fmt)
47
+ return formatter.format(record)
48
+
49
+
27
50
  # For StreamHandlers / console
28
51
  class VerboseColorfulFormatter(CustomFormatter):
29
52
  def format(self, record):
@@ -32,7 +55,7 @@ class VerboseColorfulFormatter(CustomFormatter):
32
55
 
33
56
  # For Verbose FileHandlers / files
34
57
  class VerboseFormatter(CustomFormatter):
35
- fmt = "%(asctime)s::%(name)s::%(levelname)s| %(message)s (%(filename)s:%(lineno)d)"
58
+ fmt = "%(asctime)s::%(name)s::%(levelname)s: %(message)s (%(filename)s:%(lineno)d)"
36
59
 
37
60
  def format(self, record):
38
61
  formatter = logging.Formatter(self.fmt)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lmnr
3
- Version: 0.4.47
3
+ Version: 0.4.49
4
4
  Summary: Python SDK for Laminar AI
5
5
  License: Apache-2.0
6
6
  Author: lmnr.ai
@@ -44,35 +44,35 @@ Requires-Dist: deprecated (>=1.0)
44
44
  Requires-Dist: opentelemetry-api (>=1.28.0)
45
45
  Requires-Dist: opentelemetry-exporter-otlp-proto-grpc (>=1.28.0)
46
46
  Requires-Dist: opentelemetry-exporter-otlp-proto-http (>=1.28.0)
47
- Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.33.12) ; extra == "all" or extra == "alephalpha"
48
- Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.33.12) ; extra == "all" or extra == "anthropic"
49
- Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.33.12) ; extra == "all" or extra == "bedrock"
50
- Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.33.12) ; extra == "all" or extra == "chromadb"
51
- Requires-Dist: opentelemetry-instrumentation-cohere (>=0.33.12) ; extra == "all" or extra == "cohere"
52
- Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.33.12) ; extra == "all" or extra == "google-generativeai"
53
- Requires-Dist: opentelemetry-instrumentation-groq (>=0.33.12) ; extra == "all" or extra == "groq"
54
- Requires-Dist: opentelemetry-instrumentation-haystack (>=0.33.12) ; extra == "all" or extra == "haystack"
55
- Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.33.12) ; extra == "all" or extra == "lancedb"
56
- Requires-Dist: opentelemetry-instrumentation-langchain (>=0.33.12) ; extra == "all" or extra == "langchain"
57
- Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.33.12) ; extra == "all" or extra == "llamaindex"
58
- Requires-Dist: opentelemetry-instrumentation-marqo (>=0.33.12) ; extra == "all" or extra == "marqo"
59
- Requires-Dist: opentelemetry-instrumentation-milvus (>=0.33.12) ; extra == "all" or extra == "milvus"
60
- Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.33.12) ; extra == "all" or extra == "mistralai"
61
- Requires-Dist: opentelemetry-instrumentation-ollama (>=0.33.12) ; extra == "all" or extra == "ollama"
47
+ Requires-Dist: opentelemetry-instrumentation-alephalpha (>=0.34.0) ; extra == "all" or extra == "alephalpha"
48
+ Requires-Dist: opentelemetry-instrumentation-anthropic (>=0.34.0) ; extra == "all" or extra == "anthropic"
49
+ Requires-Dist: opentelemetry-instrumentation-bedrock (>=0.34.0) ; extra == "all" or extra == "bedrock"
50
+ Requires-Dist: opentelemetry-instrumentation-chromadb (>=0.34.0) ; extra == "all" or extra == "chromadb"
51
+ Requires-Dist: opentelemetry-instrumentation-cohere (>=0.34.0) ; extra == "all" or extra == "cohere"
52
+ Requires-Dist: opentelemetry-instrumentation-google-generativeai (>=0.34.0) ; extra == "all" or extra == "google-generativeai"
53
+ Requires-Dist: opentelemetry-instrumentation-groq (>=0.34.0) ; extra == "all" or extra == "groq"
54
+ Requires-Dist: opentelemetry-instrumentation-haystack (>=0.34.0) ; extra == "all" or extra == "haystack"
55
+ Requires-Dist: opentelemetry-instrumentation-lancedb (>=0.34.0) ; extra == "all" or extra == "lancedb"
56
+ Requires-Dist: opentelemetry-instrumentation-langchain (>=0.34.0) ; extra == "all" or extra == "langchain"
57
+ Requires-Dist: opentelemetry-instrumentation-llamaindex (>=0.34.0) ; extra == "all" or extra == "llamaindex"
58
+ Requires-Dist: opentelemetry-instrumentation-marqo (>=0.34.0) ; extra == "all" or extra == "marqo"
59
+ Requires-Dist: opentelemetry-instrumentation-milvus (>=0.34.0) ; extra == "all" or extra == "milvus"
60
+ Requires-Dist: opentelemetry-instrumentation-mistralai (>=0.34.0) ; extra == "all" or extra == "mistralai"
61
+ Requires-Dist: opentelemetry-instrumentation-ollama (>=0.34.0) ; extra == "all" or extra == "ollama"
62
62
  Requires-Dist: opentelemetry-instrumentation-openai (>=0.33.12) ; extra == "all" or extra == "openai"
63
- Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.33.12) ; extra == "all" or extra == "pinecone"
64
- Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.33.12) ; extra == "all" or extra == "qdrant"
65
- Requires-Dist: opentelemetry-instrumentation-replicate (>=0.33.12) ; extra == "all" or extra == "replicate"
66
- Requires-Dist: opentelemetry-instrumentation-requests (>=0.49b0,<0.50)
67
- Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.33.12) ; extra == "all" or extra == "sagemaker"
68
- Requires-Dist: opentelemetry-instrumentation-sqlalchemy (>=0.49b0,<0.50)
69
- Requires-Dist: opentelemetry-instrumentation-threading (>=0.49b0,<0.50)
70
- Requires-Dist: opentelemetry-instrumentation-together (>=0.33.12) ; extra == "all" or extra == "together"
71
- Requires-Dist: opentelemetry-instrumentation-transformers (>=0.33.12) ; extra == "all" or extra == "transformers"
72
- Requires-Dist: opentelemetry-instrumentation-urllib3 (>=0.49b0,<0.50)
73
- Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.33.12) ; extra == "all" or extra == "vertexai"
74
- Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.33.12) ; extra == "all" or extra == "watsonx"
75
- Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.33.12) ; extra == "all" or extra == "weaviate"
63
+ Requires-Dist: opentelemetry-instrumentation-pinecone (>=0.34.0) ; extra == "all" or extra == "pinecone"
64
+ Requires-Dist: opentelemetry-instrumentation-qdrant (>=0.34.0) ; extra == "all" or extra == "qdrant"
65
+ Requires-Dist: opentelemetry-instrumentation-replicate (>=0.34.0) ; extra == "all" or extra == "replicate"
66
+ Requires-Dist: opentelemetry-instrumentation-requests (>=0.50b0)
67
+ Requires-Dist: opentelemetry-instrumentation-sagemaker (>=0.34.0) ; extra == "all" or extra == "sagemaker"
68
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy (>=0.50b0)
69
+ Requires-Dist: opentelemetry-instrumentation-threading (>=0.50b0)
70
+ Requires-Dist: opentelemetry-instrumentation-together (>=0.34.0) ; extra == "all" or extra == "together"
71
+ Requires-Dist: opentelemetry-instrumentation-transformers (>=0.34.0) ; extra == "all" or extra == "transformers"
72
+ Requires-Dist: opentelemetry-instrumentation-urllib3 (>=0.50b0)
73
+ Requires-Dist: opentelemetry-instrumentation-vertexai (>=0.34.0) ; extra == "all" or extra == "vertexai"
74
+ Requires-Dist: opentelemetry-instrumentation-watsonx (>=0.34.0) ; extra == "all" or extra == "watsonx"
75
+ Requires-Dist: opentelemetry-instrumentation-weaviate (>=0.34.0) ; extra == "all" or extra == "weaviate"
76
76
  Requires-Dist: opentelemetry-sdk (>=1.28.0)
77
77
  Requires-Dist: opentelemetry-semantic-conventions-ai (==0.4.2)
78
78
  Requires-Dist: pydantic (>=2.7)
@@ -1,5 +1,5 @@
1
1
  lmnr/__init__.py,sha256=Bqxs-8Mh4h69pOHURgBCgo9EW1GwChebxP6wUX2-bsU,452
2
- lmnr/cli.py,sha256=W5zn5DBJiFaVARFy2D-8tJI8ZlGNzjJqk3qiXAG2woo,1456
2
+ lmnr/cli.py,sha256=4J2RZQhHM3jJcjFvBC4PChQTS-ukxykVvI0X6lTkK-o,2918
3
3
  lmnr/openllmetry_sdk/.flake8,sha256=bCxuDlGx3YQ55QHKPiGJkncHanh9qGjQJUujcFa3lAU,150
4
4
  lmnr/openllmetry_sdk/.python-version,sha256=9OLQBQVbD4zE4cJsPePhnAfV_snrPSoqEQw-PXgPMOs,6
5
5
  lmnr/openllmetry_sdk/__init__.py,sha256=vVSGTAwUnJvdulHtslkGAd8QCBuv78WUK3bgfBpH6Do,2390
@@ -21,13 +21,13 @@ lmnr/sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  lmnr/sdk/datasets.py,sha256=KNMp_v3z1ocIltIw7kTgj8o-l9R8N8Tgj0sw1ajQ9C8,1582
22
22
  lmnr/sdk/decorators.py,sha256=ja2EUWUWvFOp28ER0k78PRuxNahwCVyH0TdM3U-xY7U,1856
23
23
  lmnr/sdk/eval_control.py,sha256=G6Fg3Xx_KWv72iBaWlNMdyRTF2bZFQnwJ68sJNSpIcY,177
24
- lmnr/sdk/evaluations.py,sha256=gLImD_uB9uXgw07QiJ_OYRTFDGxiPtFCO1c8HyOq2s0,15935
25
- lmnr/sdk/laminar.py,sha256=4Saelm9m6pB9GWquCdHWY-1VhAB8Q2tWlq6hnmorzHU,31250
26
- lmnr/sdk/log.py,sha256=cZBeUoSK39LMEV-X4-eEhTWOciULRfHaKfRK8YqIM8I,1532
24
+ lmnr/sdk/evaluations.py,sha256=lSXSvvYNB5JsSGli_wV-W34LVzJkQOiK8DnvHv4eU5A,16323
25
+ lmnr/sdk/laminar.py,sha256=sUq04bSGfrNuSHOEMTbR01Me0ploX8SE5GIWKqUAVyY,31094
26
+ lmnr/sdk/log.py,sha256=nt_YMmPw1IRbGy0b7q4rTtP4Yo3pQfNxqJPXK3nDSNQ,2213
27
27
  lmnr/sdk/types.py,sha256=FCNoFoa0ingOvpXGfbiETVsakYyq9Zpoc56MXJ1YDzQ,6390
28
28
  lmnr/sdk/utils.py,sha256=Uk8y15x-sd5tP2ERONahElLDJVEy_3dA_1_5g9A6auY,3358
29
- lmnr-0.4.47.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
30
- lmnr-0.4.47.dist-info/METADATA,sha256=dqgLT_aaBn35yjuzN0KKfI2BKVPLDIdfibN7PW5HWcw,12244
31
- lmnr-0.4.47.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
32
- lmnr-0.4.47.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
33
- lmnr-0.4.47.dist-info/RECORD,,
29
+ lmnr-0.4.49.dist-info/LICENSE,sha256=67b_wJHVV1CBaWkrKFWU1wyqTPSdzH77Ls-59631COg,10411
30
+ lmnr-0.4.49.dist-info/METADATA,sha256=5VVoG_p8k1guIZWZXliEaa8qRCTQCU6pSLoiXuDo1TY,12196
31
+ lmnr-0.4.49.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
32
+ lmnr-0.4.49.dist-info/entry_points.txt,sha256=K1jE20ww4jzHNZLnsfWBvU3YKDGBgbOiYG5Y7ivQcq4,37
33
+ lmnr-0.4.49.dist-info/RECORD,,
File without changes
File without changes