lmnr 0.4.53.dev0__py3-none-any.whl → 0.7.26__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.
Files changed (133) hide show
  1. lmnr/__init__.py +32 -11
  2. lmnr/cli/__init__.py +270 -0
  3. lmnr/cli/datasets.py +371 -0
  4. lmnr/cli/evals.py +111 -0
  5. lmnr/cli/rules.py +42 -0
  6. lmnr/opentelemetry_lib/__init__.py +70 -0
  7. lmnr/opentelemetry_lib/decorators/__init__.py +337 -0
  8. lmnr/opentelemetry_lib/litellm/__init__.py +685 -0
  9. lmnr/opentelemetry_lib/litellm/utils.py +100 -0
  10. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/__init__.py +849 -0
  11. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/config.py +13 -0
  12. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_emitter.py +211 -0
  13. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/event_models.py +41 -0
  14. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/span_utils.py +401 -0
  15. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/streaming.py +425 -0
  16. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/utils.py +332 -0
  17. lmnr/opentelemetry_lib/opentelemetry/instrumentation/anthropic/version.py +1 -0
  18. lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/__init__.py +451 -0
  19. lmnr/opentelemetry_lib/opentelemetry/instrumentation/claude_agent/proxy.py +144 -0
  20. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_agent/__init__.py +100 -0
  21. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/__init__.py +476 -0
  22. lmnr/opentelemetry_lib/opentelemetry/instrumentation/cua_computer/utils.py +12 -0
  23. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/__init__.py +599 -0
  24. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/config.py +9 -0
  25. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/schema_utils.py +26 -0
  26. lmnr/opentelemetry_lib/opentelemetry/instrumentation/google_genai/utils.py +330 -0
  27. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/__init__.py +488 -0
  28. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/config.py +8 -0
  29. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_emitter.py +143 -0
  30. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/event_models.py +41 -0
  31. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/span_utils.py +229 -0
  32. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/utils.py +92 -0
  33. lmnr/opentelemetry_lib/opentelemetry/instrumentation/groq/version.py +1 -0
  34. lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/__init__.py +381 -0
  35. lmnr/opentelemetry_lib/opentelemetry/instrumentation/kernel/utils.py +36 -0
  36. lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/__init__.py +121 -0
  37. lmnr/opentelemetry_lib/opentelemetry/instrumentation/langgraph/utils.py +60 -0
  38. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/__init__.py +61 -0
  39. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/__init__.py +472 -0
  40. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/chat_wrappers.py +1185 -0
  41. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/completion_wrappers.py +305 -0
  42. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/config.py +16 -0
  43. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/embeddings_wrappers.py +312 -0
  44. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_emitter.py +100 -0
  45. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/event_models.py +41 -0
  46. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/shared/image_gen_wrappers.py +68 -0
  47. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/utils.py +197 -0
  48. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v0/__init__.py +176 -0
  49. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/__init__.py +368 -0
  50. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/assistant_wrappers.py +325 -0
  51. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/event_handler_wrapper.py +135 -0
  52. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/v1/responses_wrappers.py +786 -0
  53. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openai/version.py +1 -0
  54. lmnr/opentelemetry_lib/opentelemetry/instrumentation/openhands_ai/__init__.py +388 -0
  55. lmnr/opentelemetry_lib/opentelemetry/instrumentation/opentelemetry/__init__.py +69 -0
  56. lmnr/opentelemetry_lib/opentelemetry/instrumentation/skyvern/__init__.py +191 -0
  57. lmnr/opentelemetry_lib/opentelemetry/instrumentation/threading/__init__.py +197 -0
  58. lmnr/opentelemetry_lib/tracing/__init__.py +263 -0
  59. lmnr/opentelemetry_lib/tracing/_instrument_initializers.py +516 -0
  60. lmnr/{openllmetry_sdk → opentelemetry_lib}/tracing/attributes.py +21 -8
  61. lmnr/opentelemetry_lib/tracing/context.py +200 -0
  62. lmnr/opentelemetry_lib/tracing/exporter.py +153 -0
  63. lmnr/opentelemetry_lib/tracing/instruments.py +140 -0
  64. lmnr/opentelemetry_lib/tracing/processor.py +193 -0
  65. lmnr/opentelemetry_lib/tracing/span.py +398 -0
  66. lmnr/opentelemetry_lib/tracing/tracer.py +57 -0
  67. lmnr/opentelemetry_lib/tracing/utils.py +62 -0
  68. lmnr/opentelemetry_lib/utils/package_check.py +18 -0
  69. lmnr/opentelemetry_lib/utils/wrappers.py +11 -0
  70. lmnr/sdk/browser/__init__.py +0 -0
  71. lmnr/sdk/browser/background_send_events.py +158 -0
  72. lmnr/sdk/browser/browser_use_cdp_otel.py +100 -0
  73. lmnr/sdk/browser/browser_use_otel.py +142 -0
  74. lmnr/sdk/browser/bubus_otel.py +71 -0
  75. lmnr/sdk/browser/cdp_utils.py +518 -0
  76. lmnr/sdk/browser/inject_script.js +514 -0
  77. lmnr/sdk/browser/patchright_otel.py +151 -0
  78. lmnr/sdk/browser/playwright_otel.py +322 -0
  79. lmnr/sdk/browser/pw_utils.py +363 -0
  80. lmnr/sdk/browser/recorder/record.umd.min.cjs +84 -0
  81. lmnr/sdk/browser/utils.py +70 -0
  82. lmnr/sdk/client/asynchronous/async_client.py +180 -0
  83. lmnr/sdk/client/asynchronous/resources/__init__.py +6 -0
  84. lmnr/sdk/client/asynchronous/resources/base.py +32 -0
  85. lmnr/sdk/client/asynchronous/resources/browser_events.py +41 -0
  86. lmnr/sdk/client/asynchronous/resources/datasets.py +131 -0
  87. lmnr/sdk/client/asynchronous/resources/evals.py +266 -0
  88. lmnr/sdk/client/asynchronous/resources/evaluators.py +85 -0
  89. lmnr/sdk/client/asynchronous/resources/tags.py +83 -0
  90. lmnr/sdk/client/synchronous/resources/__init__.py +6 -0
  91. lmnr/sdk/client/synchronous/resources/base.py +32 -0
  92. lmnr/sdk/client/synchronous/resources/browser_events.py +40 -0
  93. lmnr/sdk/client/synchronous/resources/datasets.py +131 -0
  94. lmnr/sdk/client/synchronous/resources/evals.py +263 -0
  95. lmnr/sdk/client/synchronous/resources/evaluators.py +85 -0
  96. lmnr/sdk/client/synchronous/resources/tags.py +83 -0
  97. lmnr/sdk/client/synchronous/sync_client.py +191 -0
  98. lmnr/sdk/datasets/__init__.py +94 -0
  99. lmnr/sdk/datasets/file_utils.py +91 -0
  100. lmnr/sdk/decorators.py +163 -26
  101. lmnr/sdk/eval_control.py +3 -2
  102. lmnr/sdk/evaluations.py +403 -191
  103. lmnr/sdk/laminar.py +1080 -549
  104. lmnr/sdk/log.py +7 -2
  105. lmnr/sdk/types.py +246 -134
  106. lmnr/sdk/utils.py +151 -7
  107. lmnr/version.py +46 -0
  108. {lmnr-0.4.53.dev0.dist-info → lmnr-0.7.26.dist-info}/METADATA +152 -106
  109. lmnr-0.7.26.dist-info/RECORD +116 -0
  110. lmnr-0.7.26.dist-info/WHEEL +4 -0
  111. lmnr-0.7.26.dist-info/entry_points.txt +3 -0
  112. lmnr/cli.py +0 -101
  113. lmnr/openllmetry_sdk/.python-version +0 -1
  114. lmnr/openllmetry_sdk/__init__.py +0 -72
  115. lmnr/openllmetry_sdk/config/__init__.py +0 -9
  116. lmnr/openllmetry_sdk/decorators/base.py +0 -185
  117. lmnr/openllmetry_sdk/instruments.py +0 -38
  118. lmnr/openllmetry_sdk/tracing/__init__.py +0 -1
  119. lmnr/openllmetry_sdk/tracing/content_allow_list.py +0 -24
  120. lmnr/openllmetry_sdk/tracing/context_manager.py +0 -13
  121. lmnr/openllmetry_sdk/tracing/tracing.py +0 -884
  122. lmnr/openllmetry_sdk/utils/in_memory_span_exporter.py +0 -61
  123. lmnr/openllmetry_sdk/utils/package_check.py +0 -7
  124. lmnr/openllmetry_sdk/version.py +0 -1
  125. lmnr/sdk/datasets.py +0 -55
  126. lmnr-0.4.53.dev0.dist-info/LICENSE +0 -75
  127. lmnr-0.4.53.dev0.dist-info/RECORD +0 -33
  128. lmnr-0.4.53.dev0.dist-info/WHEEL +0 -4
  129. lmnr-0.4.53.dev0.dist-info/entry_points.txt +0 -3
  130. /lmnr/{openllmetry_sdk → opentelemetry_lib}/.flake8 +0 -0
  131. /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/__init__.py +0 -0
  132. /lmnr/{openllmetry_sdk → opentelemetry_lib}/utils/json_encoder.py +0 -0
  133. /lmnr/{openllmetry_sdk/decorators/__init__.py → py.typed} +0 -0
@@ -0,0 +1,94 @@
1
+ from abc import ABC, abstractmethod
2
+ from pathlib import Path
3
+
4
+ import uuid
5
+
6
+ from lmnr.sdk.client.synchronous.sync_client import LaminarClient
7
+ from lmnr.sdk.datasets.file_utils import load_from_paths
8
+ from lmnr.sdk.log import get_default_logger
9
+ from lmnr.sdk.types import Datapoint
10
+
11
+ DEFAULT_FETCH_SIZE = 25
12
+ LOG = get_default_logger(__name__, verbose=False)
13
+
14
+
15
+ class EvaluationDataset(ABC):
16
+ @abstractmethod
17
+ def __init__(self, *args, **kwargs):
18
+ pass
19
+
20
+ @abstractmethod
21
+ def __len__(self) -> int:
22
+ pass
23
+
24
+ @abstractmethod
25
+ def __getitem__(self, idx) -> Datapoint:
26
+ pass
27
+
28
+ def slice(self, start: int, end: int):
29
+ return [self[i] for i in range(max(start, 0), min(end, len(self)))]
30
+
31
+
32
+ class LaminarDataset(EvaluationDataset):
33
+ client: LaminarClient
34
+ id: uuid.UUID | None = None
35
+
36
+ def __init__(
37
+ self,
38
+ name: str | None = None,
39
+ id: uuid.UUID | None = None,
40
+ fetch_size: int = DEFAULT_FETCH_SIZE,
41
+ ):
42
+ self.name = name
43
+ self.id = id
44
+ if name is None and id is None:
45
+ raise ValueError("Either name or id must be provided")
46
+ if name is not None and id is not None:
47
+ raise ValueError("Only one of name or id must be provided")
48
+ self._len = None
49
+ self._fetched_items = []
50
+ self._offset = 0
51
+ self._fetch_size = fetch_size
52
+ self._logger = get_default_logger(self.__class__.__name__)
53
+
54
+ def _fetch_batch(self):
55
+ self._logger.debug(
56
+ f"dataset name: {self.name}, id: {self.id}. Fetching batch from {self._offset} to "
57
+ + f"{self._offset + self._fetch_size}"
58
+ )
59
+ identifier = {"id": self.id} if self.id is not None else {"name": self.name}
60
+ resp = self.client.datasets.pull(
61
+ **identifier,
62
+ offset=self._offset,
63
+ limit=self._fetch_size,
64
+ )
65
+ self._fetched_items += resp.items
66
+ self._offset = len(self._fetched_items)
67
+ if self._len is None:
68
+ self._len = resp.total_count
69
+
70
+ def __len__(self) -> int:
71
+ if self._len is None:
72
+ self._fetch_batch()
73
+ return self._len
74
+
75
+ def __getitem__(self, idx) -> Datapoint:
76
+ if idx >= len(self._fetched_items):
77
+ self._fetch_batch()
78
+ return self._fetched_items[idx]
79
+
80
+ def set_client(self, client: LaminarClient):
81
+ self.client = client
82
+
83
+ def push(self, paths: str | list[str], recursive: bool = False):
84
+ paths = [paths] if isinstance(paths, str) else paths
85
+ paths = [Path(path) for path in paths]
86
+ data = load_from_paths(paths, recursive)
87
+ if len(data) == 0:
88
+ LOG.warning("No data to push. Skipping")
89
+ return
90
+ identifier = {"id": self.id} if self.id is not None else {"name": self.name}
91
+ self.client.datasets.push(data, **identifier)
92
+ LOG.info(
93
+ f"Successfully pushed {len(data)} datapoints to dataset [{identifier}]"
94
+ )
@@ -0,0 +1,91 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+ import csv
4
+ import orjson
5
+
6
+ from lmnr.sdk.log import get_default_logger
7
+
8
+ LOG = get_default_logger(__name__, verbose=False)
9
+
10
+
11
+ def _is_supported_file(file: Path) -> bool:
12
+ """Check if a file is supported."""
13
+ return file.suffix in [".json", ".csv", ".jsonl"]
14
+
15
+
16
+ def _collect_files(paths: list[Path], recursive: bool = False) -> list[Path]:
17
+ """
18
+ Collect all supported files from the given paths.
19
+
20
+ Handles both files and directories. If a path is a directory,
21
+ collects all supported files within it (recursively if specified).
22
+ """
23
+ collected_files = []
24
+
25
+ for path in paths:
26
+ if path.is_file():
27
+ if _is_supported_file(path):
28
+ collected_files.append(path)
29
+ else:
30
+ LOG.warning(f"Skipping unsupported file type: {path}")
31
+ elif path.is_dir():
32
+ for item in path.iterdir():
33
+ if item.is_file() and _is_supported_file(item):
34
+ collected_files.append(item)
35
+ elif recursive and item.is_dir():
36
+ # Recursively collect files from subdirectories
37
+ collected_files.extend(_collect_files([item], recursive=True))
38
+ else:
39
+ LOG.warning(f"Path does not exist or is not accessible: {path}")
40
+
41
+ return collected_files
42
+
43
+
44
+ def _read_file(file: Path) -> list[dict[str, Any]]:
45
+ """Read data from a single file and return as a list of dictionaries."""
46
+ if file.suffix == ".json":
47
+ result = orjson.loads(file.read_bytes())
48
+ if isinstance(result, list):
49
+ return result
50
+ else:
51
+ return [result]
52
+ elif file.suffix == ".csv":
53
+ return [dict(row) for row in csv.DictReader(file.read_text().splitlines())]
54
+ elif file.suffix == ".jsonl":
55
+ return [
56
+ orjson.loads(line) for line in file.read_text().splitlines() if line.strip()
57
+ ]
58
+ else:
59
+ raise ValueError(f"Unsupported file type: {file.suffix}")
60
+
61
+
62
+ def load_from_paths(paths: list[Path], recursive: bool = False) -> list[dict[str, Any]]:
63
+ """
64
+ Load data from all files in the specified paths.
65
+
66
+ First collects all file paths, then reads each file's data.
67
+ """
68
+ files = _collect_files(paths, recursive)
69
+
70
+ if not files:
71
+ LOG.warning("No supported files found in the specified paths")
72
+ return []
73
+
74
+ LOG.info(f"Found {len(files)} file(s) to read")
75
+
76
+ result = []
77
+ for file in files:
78
+ try:
79
+ data = _read_file(file)
80
+ result.extend(data)
81
+ LOG.info(f"Read {len(data)} record(s) from {file}")
82
+ except Exception as e:
83
+ LOG.error(f"Error reading file {file}: {e}")
84
+ raise
85
+
86
+ return result
87
+
88
+
89
+ def parse_paths(paths: list[str]) -> list[Path]:
90
+ """Parse paths."""
91
+ return [Path(path) for path in paths]
lmnr/sdk/decorators.py CHANGED
@@ -1,46 +1,132 @@
1
- from lmnr.openllmetry_sdk.decorators.base import (
2
- entity_method,
3
- aentity_method,
1
+ from lmnr.opentelemetry_lib.decorators import (
2
+ observe_base,
3
+ async_observe_base,
4
4
  )
5
5
  from opentelemetry.trace import INVALID_SPAN, get_current_span
6
6
 
7
- from typing import Callable, Optional, TypeVar, cast
7
+ from typing import Any, Callable, Coroutine, Literal, TypeVar, overload
8
8
  from typing_extensions import ParamSpec
9
9
 
10
- from lmnr.openllmetry_sdk.tracing.attributes import SESSION_ID
11
- from lmnr.openllmetry_sdk.tracing.tracing import update_association_properties
10
+ from lmnr.opentelemetry_lib.tracing.attributes import SESSION_ID
11
+ from lmnr.sdk.log import get_default_logger
12
+ from lmnr.sdk.types import TraceType
12
13
 
13
14
  from .utils import is_async
14
15
 
16
+ logger = get_default_logger(__name__)
15
17
 
16
18
  P = ParamSpec("P")
17
19
  R = TypeVar("R")
18
20
 
19
21
 
22
+ # Overload for synchronous functions
23
+ @overload
20
24
  def observe(
21
25
  *,
22
- name: Optional[str] = None,
23
- session_id: Optional[str] = None,
24
- ) -> Callable[[Callable[P, R]], Callable[P, R]]:
26
+ name: str | None = None,
27
+ session_id: str | None = None,
28
+ user_id: str | None = None,
29
+ ignore_input: bool = False,
30
+ ignore_output: bool = False,
31
+ span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
32
+ ignore_inputs: list[str] | None = None,
33
+ input_formatter: Callable[..., str] | None = None,
34
+ output_formatter: Callable[..., str] | None = None,
35
+ metadata: dict[str, Any] | None = None,
36
+ tags: list[str] | None = None,
37
+ preserve_global_context: bool = False,
38
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
39
+
40
+
41
+ # Overload for asynchronous functions
42
+ @overload
43
+ def observe(
44
+ *,
45
+ name: str | None = None,
46
+ session_id: str | None = None,
47
+ user_id: str | None = None,
48
+ ignore_input: bool = False,
49
+ ignore_output: bool = False,
50
+ span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
51
+ ignore_inputs: list[str] | None = None,
52
+ input_formatter: Callable[..., str] | None = None,
53
+ output_formatter: Callable[..., str] | None = None,
54
+ metadata: dict[str, Any] | None = None,
55
+ tags: list[str] | None = None,
56
+ preserve_global_context: bool = False,
57
+ ) -> Callable[
58
+ [Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]
59
+ ]: ...
60
+
61
+
62
+ # Implementation
63
+ def observe(
64
+ *,
65
+ name: str | None = None,
66
+ session_id: str | None = None,
67
+ user_id: str | None = None,
68
+ ignore_input: bool = False,
69
+ ignore_output: bool = False,
70
+ span_type: Literal["DEFAULT", "LLM", "TOOL"] = "DEFAULT",
71
+ ignore_inputs: list[str] | None = None,
72
+ input_formatter: Callable[..., str] | None = None,
73
+ output_formatter: Callable[..., str] | None = None,
74
+ metadata: dict[str, Any] | None = None,
75
+ tags: list[str] | None = None,
76
+ preserve_global_context: bool = False,
77
+ ):
78
+ # Return type is determined by overloads above
25
79
  """The main decorator entrypoint for Laminar. This is used to wrap
26
80
  functions and methods to create spans.
27
81
 
28
82
  Args:
29
- name (Optional[str], optional): Name of the span. Function
30
- name is used if not specified.
31
- Defaults to None.
32
- session_id (Optional[str], optional): Session ID to associate with the
33
- span and the following context. Defaults to None.
34
-
83
+ name (str | None, optional): Name of the span. Function name is used if\
84
+ not specified. Defaults to None.
85
+ session_id (str | None, optional): Session ID to associate with the\
86
+ span and the following context. Defaults to None.
87
+ user_id (str | None, optional): User ID to associate with the span and\
88
+ the following context. This is different from ID of a Laminar user.
89
+ Defaults to None.
90
+ ignore_input (bool, optional): Whether to ignore ALL input of the\
91
+ wrapped function. Defaults to False.
92
+ ignore_output (bool, optional): Whether to ignore ALL output of the\
93
+ wrapped function. Defaults to False.
94
+ span_type (Literal["DEFAULT", "LLM", "TOOL"], optional): Type of the span.
95
+ Defaults to "DEFAULT".
96
+ ignore_inputs (list[str] | None, optional): List of input keys to\
97
+ ignore. For example, if the wrapped function takes three arguments\
98
+ def foo(a, b, `sensitive_data`), and you want to ignore the\
99
+ `sensitive_data` argument, you can pass ["sensitive_data"] to\
100
+ this argument. Defaults to None.
101
+ input_formatter (Callable[P, str] | None, optional): A custom function\
102
+ to format the input of the wrapped function. This function should\
103
+ accept the same parameters as the wrapped function and return a string.\
104
+ All function arguments are passed to this function. Ignored if\
105
+ `ignore_input` is True. Does not respect `ignore_inputs` argument.
106
+ Defaults to None.
107
+ output_formatter (Callable[[R], str] | None, optional): A custom function\
108
+ to format the output of the wrapped function. This function should\
109
+ accept a single parameter (the return value of the wrapped function)\
110
+ and return a string. Ignored if `ignore_output` is True.\
111
+ Defaults to None.
112
+ metadata (dict[str, Any] | None, optional): Metadata to associate with\
113
+ the trace. Must be JSON serializable. Defaults to None.
114
+ tags (list[str] | None, optional): Tags to associate with the trace.
115
+ Defaults to None.
116
+ preserve_global_context (bool, optional): Whether to preserve the global\
117
+ OpenTelemetry context. If set to True, Laminar spans will continue\
118
+ traces started in the global context. Defaults to False.
35
119
  Raises:
36
- Exception: re-raises the exception if the wrapped function raises
37
- an exception
120
+ Exception: re-raises the exception if the wrapped function raises an\
121
+ exception
38
122
 
39
123
  Returns:
40
124
  R: Returns the result of the wrapped function
41
125
  """
42
126
 
43
- def decorator(func: Callable) -> Callable:
127
+ def decorator(
128
+ func: Callable[P, R] | Callable[P, Coroutine[Any, Any, R]],
129
+ ) -> Callable[P, R] | Callable[P, Coroutine[Any, Any, R]]:
44
130
  current_span = get_current_span()
45
131
  if current_span != INVALID_SPAN:
46
132
  if session_id is not None:
@@ -48,11 +134,62 @@ def observe(
48
134
  association_properties = {}
49
135
  if session_id is not None:
50
136
  association_properties["session_id"] = session_id
51
- update_association_properties(association_properties)
52
- return (
53
- aentity_method(name=name)(func)
54
- if is_async(func)
55
- else entity_method(name=name)(func)
56
- )
57
-
58
- return cast(Callable, decorator)
137
+ if user_id is not None:
138
+ association_properties["user_id"] = user_id
139
+ if span_type in ["EVALUATION", "EXECUTOR", "EVALUATOR"]:
140
+ association_properties["trace_type"] = TraceType.EVALUATION.value
141
+ if tags is not None:
142
+ if not isinstance(tags, list) or not all(
143
+ isinstance(tag, str) for tag in tags
144
+ ):
145
+ logger.warning("Tags must be a list of strings. Tags will be ignored.")
146
+ else:
147
+ # list(set(tags)) to deduplicate tags
148
+ association_properties["tags"] = list(set(tags))
149
+ if input_formatter is not None and ignore_input:
150
+ logger.warning(
151
+ f"observe, function {func.__name__}: Input formatter"
152
+ " is ignored because `ignore_input` is True. Specify only one of"
153
+ " `ignore_input` or `input_formatter`."
154
+ )
155
+ if input_formatter is not None and ignore_inputs is not None:
156
+ logger.warning(
157
+ f"observe, function {func.__name__}: Both input formatter and"
158
+ " `ignore_inputs` are specified. Input formatter"
159
+ " will pass all arguments to the formatter regardless of"
160
+ " `ignore_inputs`."
161
+ )
162
+ if output_formatter is not None and ignore_output:
163
+ logger.warning(
164
+ f"observe, function {func.__name__}: Output formatter"
165
+ " is ignored because `ignore_output` is True. Specify only one of"
166
+ " `ignore_output` or `output_formatter`."
167
+ )
168
+ if is_async(func):
169
+ return async_observe_base(
170
+ name=name,
171
+ ignore_input=ignore_input,
172
+ ignore_output=ignore_output,
173
+ span_type=span_type,
174
+ metadata=metadata,
175
+ ignore_inputs=ignore_inputs,
176
+ input_formatter=input_formatter,
177
+ output_formatter=output_formatter,
178
+ association_properties=association_properties,
179
+ preserve_global_context=preserve_global_context,
180
+ )(func)
181
+ else:
182
+ return observe_base(
183
+ name=name,
184
+ ignore_input=ignore_input,
185
+ ignore_output=ignore_output,
186
+ span_type=span_type,
187
+ metadata=metadata,
188
+ ignore_inputs=ignore_inputs,
189
+ input_formatter=input_formatter,
190
+ output_formatter=output_formatter,
191
+ association_properties=association_properties,
192
+ preserve_global_context=preserve_global_context,
193
+ )(func)
194
+
195
+ return decorator
lmnr/sdk/eval_control.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from contextvars import ContextVar
2
2
 
3
- PREPARE_ONLY = ContextVar("__lmnr_prepare_only", default=False)
4
- EVALUATION_INSTANCE = ContextVar("__lmnr_evaluation_instance", default=None)
3
+
4
+ PREPARE_ONLY: ContextVar[bool] = ContextVar("__lmnr_prepare_only", default=False)
5
+ EVALUATION_INSTANCES = ContextVar("__lmnr_evaluation_instances")