lmnr 0.3.7__py3-none-any.whl → 0.4.0__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/__init__.py +2 -6
- lmnr/sdk/decorators.py +55 -267
- lmnr/sdk/laminar.py +380 -0
- lmnr/sdk/log.py +39 -0
- lmnr/sdk/utils.py +10 -11
- lmnr-0.4.0.dist-info/METADATA +151 -0
- lmnr-0.4.0.dist-info/RECORD +12 -0
- lmnr/sdk/client.py +0 -161
- lmnr/sdk/collector.py +0 -177
- lmnr/sdk/constants.py +0 -1
- lmnr/sdk/context.py +0 -483
- lmnr/sdk/interface.py +0 -316
- lmnr/sdk/providers/__init__.py +0 -2
- lmnr/sdk/providers/base.py +0 -28
- lmnr/sdk/providers/fallback.py +0 -154
- lmnr/sdk/providers/openai.py +0 -159
- lmnr/sdk/providers/utils.py +0 -33
- lmnr/sdk/tracing_types.py +0 -210
- lmnr/semantic_conventions/__init__.py +0 -0
- lmnr/semantic_conventions/gen_ai_spans.py +0 -48
- lmnr-0.3.7.dist-info/METADATA +0 -266
- lmnr-0.3.7.dist-info/RECORD +0 -23
- {lmnr-0.3.7.dist-info → lmnr-0.4.0.dist-info}/LICENSE +0 -0
- {lmnr-0.3.7.dist-info → lmnr-0.4.0.dist-info}/WHEEL +0 -0
- {lmnr-0.3.7.dist-info → lmnr-0.4.0.dist-info}/entry_points.txt +0 -0
lmnr/__init__.py
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
from .sdk.
|
2
|
-
from .sdk.decorators import observe, lmnr_context, wrap_llm_call
|
3
|
-
from .sdk.interface import trace, TraceContext, SpanContext, initialize
|
4
|
-
from .sdk.tracing_types import EvaluateEvent
|
1
|
+
from .sdk.laminar import Laminar
|
5
2
|
from .sdk.types import ChatMessage, PipelineRunError, PipelineRunResponse, NodeInput
|
6
|
-
|
7
|
-
from .semantic_conventions import *
|
3
|
+
from .sdk.decorators import observe
|
lmnr/sdk/decorators.py
CHANGED
@@ -1,284 +1,72 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
from .context import LaminarSingleton
|
7
|
-
from .providers.fallback import FallbackProvider
|
8
|
-
from ..semantic_conventions.gen_ai_spans import PROVIDER
|
9
|
-
from .types import NodeInput, PipelineRunResponse
|
10
|
-
from .utils import (
|
11
|
-
PROVIDER_NAME_TO_OBJECT,
|
12
|
-
get_input_from_func_args,
|
13
|
-
is_async,
|
14
|
-
is_method,
|
1
|
+
from traceloop.sdk.decorators.base import (
|
2
|
+
entity_method,
|
3
|
+
aentity_method,
|
15
4
|
)
|
5
|
+
from opentelemetry.trace import INVALID_SPAN, get_current_span
|
6
|
+
from traceloop.sdk import Traceloop
|
16
7
|
|
8
|
+
from typing import Callable, Optional, ParamSpec, TypeVar, cast
|
17
9
|
|
18
|
-
|
19
|
-
|
20
|
-
self,
|
21
|
-
*,
|
22
|
-
name: Optional[str] = None,
|
23
|
-
span_type: Optional[Literal["DEFAULT", "LLM"]] = "DEFAULT",
|
24
|
-
capture_input: bool = True,
|
25
|
-
capture_output: bool = True,
|
26
|
-
release: Optional[str] = None,
|
27
|
-
user_id: Optional[str] = None,
|
28
|
-
session_id: Optional[str] = None,
|
29
|
-
):
|
30
|
-
"""The main decorator entrypoint for Laminar. This is used to wrap functions and methods to create spans.
|
31
|
-
|
32
|
-
Args:
|
33
|
-
name (Optional[str], optional): Name of the span. Function name is used if not specified. Defaults to None.
|
34
|
-
span_type (Literal["DEFAULT", "LLM"], optional): Type of this span. Prefer `wrap_llm_call` instead of specifying
|
35
|
-
this as "LLM" . Defaults to "DEFAULT".
|
36
|
-
capture_input (bool, optional): Whether to capture input parameters to the function. Defaults to True.
|
37
|
-
capture_output (bool, optional): Whether to capture returned type from the function. Defaults to True.
|
38
|
-
release (Optional[str], optional): Release version of your app. Useful for further grouping and analytics. Defaults to None.
|
39
|
-
user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
|
40
|
-
session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
|
41
|
-
Defaults to None.
|
42
|
-
|
43
|
-
Raises:
|
44
|
-
Exception: re-raises the exception if the wrapped function raises an exception
|
45
|
-
|
46
|
-
Returns:
|
47
|
-
Any: Returns the result of the wrapped function
|
48
|
-
"""
|
49
|
-
context_manager = LaminarSingleton().get()
|
50
|
-
|
51
|
-
def decorator(func: Callable):
|
52
|
-
@functools.wraps(func)
|
53
|
-
def wrapper(*args, **kwargs):
|
54
|
-
span = context_manager.observe_start(
|
55
|
-
name=name or func.__name__,
|
56
|
-
span_type=span_type,
|
57
|
-
input=(
|
58
|
-
get_input_from_func_args(func, is_method(func), args, kwargs)
|
59
|
-
if capture_input
|
60
|
-
else None
|
61
|
-
),
|
62
|
-
user_id=user_id,
|
63
|
-
session_id=session_id,
|
64
|
-
release=release,
|
65
|
-
)
|
66
|
-
try:
|
67
|
-
result = func(*args, **kwargs)
|
68
|
-
except Exception as e:
|
69
|
-
context_manager.observe_end(result=None, span=span, error=e)
|
70
|
-
raise e
|
71
|
-
context_manager.observe_end(
|
72
|
-
result=result if capture_output else None, span=span
|
73
|
-
)
|
74
|
-
return result
|
75
|
-
|
76
|
-
@functools.wraps(func)
|
77
|
-
async def async_wrapper(*args, **kwargs):
|
78
|
-
span = context_manager.observe_start(
|
79
|
-
name=name or func.__name__,
|
80
|
-
span_type=span_type,
|
81
|
-
input=(
|
82
|
-
get_input_from_func_args(func, is_method(func), args, kwargs)
|
83
|
-
if capture_input
|
84
|
-
else None
|
85
|
-
),
|
86
|
-
user_id=user_id,
|
87
|
-
session_id=session_id,
|
88
|
-
release=release,
|
89
|
-
)
|
90
|
-
try:
|
91
|
-
result = await func(*args, **kwargs)
|
92
|
-
except Exception as e:
|
93
|
-
context_manager.observe_end(result=None, span=span, error=e)
|
94
|
-
raise e
|
95
|
-
context_manager.observe_end(
|
96
|
-
result=result if capture_output else None, span=span
|
97
|
-
)
|
98
|
-
return result
|
99
|
-
|
100
|
-
return async_wrapper if is_async(func) else wrapper
|
101
|
-
|
102
|
-
return decorator
|
103
|
-
|
104
|
-
def update_current_span(
|
105
|
-
self,
|
106
|
-
metadata: Optional[dict[str, Any]] = None,
|
107
|
-
attributes: Optional[dict[str, Any]] = None,
|
108
|
-
override: bool = False,
|
109
|
-
):
|
110
|
-
"""Update the current span with any optional metadata.
|
111
|
-
|
112
|
-
Args:
|
113
|
-
metadata (Optional[dict[str, Any]], optional): metadata to the span. Defaults to None.
|
114
|
-
override (bool, optional): Whether to override the existing metadata. If False, metadata is merged with the existing metadata. Defaults to False.
|
115
|
-
"""
|
116
|
-
laminar = LaminarSingleton().get()
|
117
|
-
laminar.update_current_span(
|
118
|
-
metadata=metadata, attributes=attributes, override=override
|
119
|
-
)
|
120
|
-
|
121
|
-
def update_current_trace(
|
122
|
-
self,
|
123
|
-
user_id: Optional[str] = None,
|
124
|
-
session_id: Optional[str] = None,
|
125
|
-
release: Optional[str] = None,
|
126
|
-
metadata: Optional[dict[str, Any]] = None,
|
127
|
-
):
|
128
|
-
"""Update the current trace with any optional metadata.
|
129
|
-
|
130
|
-
Args:
|
131
|
-
user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
|
132
|
-
session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
|
133
|
-
Defaults to None.
|
134
|
-
release (Optional[str], optional): Release version of your app. Useful for further grouping and analytics. Defaults to None.
|
135
|
-
metadata (Optional[dict[str, Any]], optional): metadata to the trace. Defaults to None.
|
136
|
-
"""
|
137
|
-
laminar = LaminarSingleton().get()
|
138
|
-
laminar.update_current_trace(
|
139
|
-
user_id=user_id, session_id=session_id, release=release, metadata=metadata
|
140
|
-
)
|
141
|
-
|
142
|
-
def event(
|
143
|
-
self,
|
144
|
-
name: str,
|
145
|
-
value: Optional[Union[str, int, float, bool]] = None,
|
146
|
-
timestamp: Optional[datetime.datetime] = None,
|
147
|
-
):
|
148
|
-
"""Associate an event with the current span
|
149
|
-
|
150
|
-
Args:
|
151
|
-
name (str): name of the event. Must be predefined in the Laminar events page.
|
152
|
-
value (Optional[Union[str, int, float, bool]], optional): value of the event. Must match range definition in Laminar events page. Defaults to None.
|
153
|
-
timestamp (Optional[datetime.datetime], optional): If you need custom timestamp. If not specified, current time is used. Defaults to None.
|
154
|
-
"""
|
155
|
-
laminar = LaminarSingleton().get()
|
156
|
-
laminar.event(name, value=value, timestamp=timestamp)
|
157
|
-
|
158
|
-
def evaluate_event(self, name: str, evaluator: str, data: dict):
|
159
|
-
"""Evaluate an event with the given name by evaluator based on the given data.
|
160
|
-
Evaluator is the Laminar pipeline name.
|
161
|
-
Data is passed as an input to the the evaluator pipeline, so you need to specify which data you want to evaluate. The prompt
|
162
|
-
of the evaluator will be templated with the keys of the data dictionary.
|
163
|
-
|
164
|
-
Usually, you would want to pass the output of LLM generation, users' messages, and some other surrounding data to 'data'.
|
165
|
-
|
166
|
-
Args:
|
167
|
-
name (str): Name of the event.
|
168
|
-
evaluator (str): Name of the evaluator pipeline.
|
169
|
-
data (dict): Data to be used when evaluating the event.
|
170
|
-
"""
|
171
|
-
laminar = LaminarSingleton().get()
|
172
|
-
laminar.evaluate_event(name, evaluator=evaluator, data=data)
|
10
|
+
from .laminar import Laminar as L
|
11
|
+
from .utils import is_async
|
173
12
|
|
174
|
-
|
175
|
-
|
176
|
-
pipeline: str,
|
177
|
-
inputs: dict[str, NodeInput],
|
178
|
-
env: dict[str, str] = {},
|
179
|
-
metadata: dict[str, str] = {},
|
180
|
-
) -> PipelineRunResponse:
|
181
|
-
"""Run the laminar pipeline with the given inputs. Pipeline must be defined in the Laminar UI and have a target version.
|
13
|
+
P = ParamSpec("P")
|
14
|
+
R = TypeVar("R")
|
182
15
|
|
183
|
-
Args:
|
184
|
-
pipeline (str): pipeline name
|
185
|
-
inputs (dict[str, NodeInput]): Map from input node name to input value
|
186
|
-
env (dict[str, str], optional): Environment variables for the pipeline executions. Typically contains API keys. Defaults to None.
|
187
|
-
metadata (dict[str, str], optional): Any additional data to associate with the resulting span. Defaults to None.
|
188
16
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
"""Wrap an LLM call with Laminar observability. This is a convenience function that does the same as `@observe()`, plus
|
198
|
-
a few utilities around LLM-specific things, such as counting tokens and recording model params.
|
199
|
-
|
200
|
-
Example usage:
|
201
|
-
```python
|
202
|
-
wrap_llm_call(client.chat.completions.create)(
|
203
|
-
model="gpt-4o-mini",
|
204
|
-
messages=[
|
205
|
-
{"role": "system", "content": "You are a helpful assistant."},
|
206
|
-
{"role": "user", "content": "Hello"},
|
207
|
-
],
|
208
|
-
stream=True,
|
209
|
-
)
|
210
|
-
```
|
17
|
+
def observe(
|
18
|
+
*,
|
19
|
+
name: Optional[str] = None,
|
20
|
+
user_id: Optional[str] = None,
|
21
|
+
session_id: Optional[str] = None,
|
22
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
23
|
+
"""The main decorator entrypoint for Laminar. This is used to wrap
|
24
|
+
functions and methods to create spans.
|
211
25
|
|
212
26
|
Args:
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
27
|
+
name (Optional[str], optional): Name of the span. Function
|
28
|
+
name is used if not specified.
|
29
|
+
Defaults to None.
|
30
|
+
user_id (Optional[str], optional): User ID to associate
|
31
|
+
with the span and the following context.
|
32
|
+
Defaults to None.
|
33
|
+
session_id (Optional[str], optional): Session ID to associate with the
|
34
|
+
span and the following context. Defaults to None.
|
218
35
|
|
219
36
|
Raises:
|
220
|
-
|
37
|
+
Exception: re-raises the exception if the wrapped function raises
|
38
|
+
an exception
|
221
39
|
|
222
40
|
Returns:
|
223
|
-
|
41
|
+
R: Returns the result of the wrapped function
|
224
42
|
"""
|
225
|
-
laminar = LaminarSingleton().get()
|
226
|
-
# Simple heuristic to determine the package from where the LLM call is imported.
|
227
|
-
# This works for major providers, but will likely make no sense for custom providers.
|
228
|
-
provider_name = (
|
229
|
-
provider.lower().strip() if provider else func.__module__.split(".")[0]
|
230
|
-
)
|
231
|
-
provider_module = PROVIDER_NAME_TO_OBJECT.get(provider_name, FallbackProvider())
|
232
|
-
name = name or f"{provider_module.display_name()} completion"
|
233
43
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
if provider_module
|
240
|
-
else {}
|
241
|
-
)
|
242
|
-
attributes[PROVIDER] = provider_name
|
243
|
-
span = laminar.observe_start(
|
244
|
-
name=name, span_type="LLM", input=inp, attributes=attributes
|
245
|
-
)
|
246
|
-
try:
|
247
|
-
result = func(*args, **kwargs)
|
248
|
-
except Exception as e:
|
249
|
-
laminar.observe_end(
|
250
|
-
result=None, span=span, error=e, provider_name=provider_name
|
44
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
45
|
+
if not L.is_initialized():
|
46
|
+
raise Exception(
|
47
|
+
"Laminar is not initialized. Please "
|
48
|
+
+ "call Laminar.initialize() first."
|
251
49
|
)
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
laminar.observe_end(
|
273
|
-
result=None, span=span, error=e, provider_name=provider_name
|
274
|
-
)
|
275
|
-
raise e
|
276
|
-
return laminar.observe_end(
|
277
|
-
result=result, span=span, provider_name=provider_name
|
50
|
+
current_span = get_current_span()
|
51
|
+
if current_span != INVALID_SPAN:
|
52
|
+
if session_id is not None:
|
53
|
+
current_span.set_attribute(
|
54
|
+
"traceloop.association.properties.session_id", session_id
|
55
|
+
)
|
56
|
+
if user_id is not None:
|
57
|
+
current_span.set_attribute(
|
58
|
+
"traceloop.association.properties.user_id", user_id
|
59
|
+
)
|
60
|
+
association_properties = {}
|
61
|
+
if session_id is not None:
|
62
|
+
association_properties["session_id"] = session_id
|
63
|
+
if user_id is not None:
|
64
|
+
association_properties["user_id"] = user_id
|
65
|
+
Traceloop.set_association_properties(association_properties)
|
66
|
+
return (
|
67
|
+
aentity_method(name=name)(func)
|
68
|
+
if is_async(func)
|
69
|
+
else entity_method(name=name)(func)
|
278
70
|
)
|
279
71
|
|
280
|
-
return
|
281
|
-
|
282
|
-
|
283
|
-
lmnr_context = LaminarDecorator()
|
284
|
-
observe = lmnr_context.observe
|
72
|
+
return cast(Callable[P, R], decorator)
|