lmnr 0.2.14__py3-none-any.whl → 0.3.0b1__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 (43) hide show
  1. lmnr/__init__.py +4 -4
  2. lmnr/sdk/client.py +156 -0
  3. lmnr/sdk/collector.py +177 -0
  4. lmnr/sdk/constants.py +1 -0
  5. lmnr/sdk/context.py +456 -0
  6. lmnr/sdk/decorators.py +277 -0
  7. lmnr/sdk/interface.py +339 -0
  8. lmnr/sdk/providers/__init__.py +2 -0
  9. lmnr/sdk/providers/base.py +28 -0
  10. lmnr/sdk/providers/fallback.py +131 -0
  11. lmnr/sdk/providers/openai.py +140 -0
  12. lmnr/sdk/providers/utils.py +33 -0
  13. lmnr/sdk/tracing_types.py +197 -0
  14. lmnr/sdk/types.py +69 -0
  15. lmnr/sdk/utils.py +102 -0
  16. lmnr-0.3.0b1.dist-info/METADATA +186 -0
  17. lmnr-0.3.0b1.dist-info/RECORD +21 -0
  18. lmnr/cli/__init__.py +0 -0
  19. lmnr/cli/__main__.py +0 -4
  20. lmnr/cli/cli.py +0 -232
  21. lmnr/cli/parser/__init__.py +0 -0
  22. lmnr/cli/parser/nodes/__init__.py +0 -45
  23. lmnr/cli/parser/nodes/code.py +0 -36
  24. lmnr/cli/parser/nodes/condition.py +0 -30
  25. lmnr/cli/parser/nodes/input.py +0 -25
  26. lmnr/cli/parser/nodes/json_extractor.py +0 -29
  27. lmnr/cli/parser/nodes/llm.py +0 -56
  28. lmnr/cli/parser/nodes/output.py +0 -27
  29. lmnr/cli/parser/nodes/router.py +0 -37
  30. lmnr/cli/parser/nodes/semantic_search.py +0 -53
  31. lmnr/cli/parser/nodes/types.py +0 -153
  32. lmnr/cli/parser/parser.py +0 -62
  33. lmnr/cli/parser/utils.py +0 -49
  34. lmnr/cli/zip.py +0 -16
  35. lmnr/sdk/endpoint.py +0 -186
  36. lmnr/sdk/registry.py +0 -29
  37. lmnr/sdk/remote_debugger.py +0 -148
  38. lmnr/types.py +0 -101
  39. lmnr-0.2.14.dist-info/METADATA +0 -187
  40. lmnr-0.2.14.dist-info/RECORD +0 -28
  41. {lmnr-0.2.14.dist-info → lmnr-0.3.0b1.dist-info}/LICENSE +0 -0
  42. {lmnr-0.2.14.dist-info → lmnr-0.3.0b1.dist-info}/WHEEL +0 -0
  43. {lmnr-0.2.14.dist-info → lmnr-0.3.0b1.dist-info}/entry_points.txt +0 -0
lmnr/sdk/decorators.py ADDED
@@ -0,0 +1,277 @@
1
+ import datetime
2
+ import functools
3
+ from typing import Any, Callable, Literal, Optional, Union
4
+
5
+
6
+ from .context import LaminarSingleton
7
+ from .providers.fallback import FallbackProvider
8
+ from .types import NodeInput, PipelineRunResponse
9
+ from .utils import (
10
+ PROVIDER_NAME_TO_OBJECT,
11
+ get_input_from_func_args,
12
+ is_async,
13
+ is_method,
14
+ )
15
+
16
+
17
+ class LaminarDecorator:
18
+ def observe(
19
+ self,
20
+ *,
21
+ name: Optional[str] = None,
22
+ span_type: Optional[Literal["DEFAULT", "LLM"]] = "DEFAULT",
23
+ capture_input: bool = True,
24
+ capture_output: bool = True,
25
+ release: Optional[str] = None,
26
+ user_id: Optional[str] = None,
27
+ session_id: Optional[str] = None,
28
+ ):
29
+ """The main decorator entrypoint for Laminar. This is used to wrap functions and methods to create spans.
30
+
31
+ Args:
32
+ name (Optional[str], optional): Name of the span. Function name is used if not specified. Defaults to None.
33
+ span_type (Literal["DEFAULT", "LLM"], optional): Type of this span. Prefer `wrap_llm_call` instead of specifying
34
+ this as "LLM" . Defaults to "DEFAULT".
35
+ capture_input (bool, optional): Whether to capture input parameters to the function. Defaults to True.
36
+ capture_output (bool, optional): Whether to capture returned type from the function. Defaults to True.
37
+ release (Optional[str], optional): Release version of your app. Useful for further grouping and analytics. Defaults to None.
38
+ user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
39
+ session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
40
+ Defaults to None.
41
+
42
+ Raises:
43
+ Exception: re-raises the exception if the wrapped function raises an exception
44
+
45
+ Returns:
46
+ Any: Returns the result of the wrapped function
47
+ """
48
+ context_manager = LaminarSingleton().get()
49
+
50
+ def decorator(func: Callable):
51
+ @functools.wraps(func)
52
+ def wrapper(*args, **kwargs):
53
+ span = context_manager.observe_start(
54
+ name=name or func.__name__,
55
+ span_type=span_type,
56
+ input=(
57
+ get_input_from_func_args(func, is_method(func), args, kwargs)
58
+ if capture_input
59
+ else None
60
+ ),
61
+ user_id=user_id,
62
+ session_id=session_id,
63
+ release=release,
64
+ )
65
+ try:
66
+ result = func(*args, **kwargs)
67
+ except Exception as e:
68
+ context_manager.observe_end(result=None, span=span, error=e)
69
+ raise e
70
+ context_manager.observe_end(
71
+ result=result if capture_output else None, span=span
72
+ )
73
+ return result
74
+
75
+ @functools.wraps(func)
76
+ async def async_wrapper(*args, **kwargs):
77
+ span = context_manager.observe_start(
78
+ name=name or func.__name__,
79
+ span_type=span_type,
80
+ input=(
81
+ get_input_from_func_args(func, is_method(func), args, kwargs)
82
+ if capture_input
83
+ else None
84
+ ),
85
+ user_id=user_id,
86
+ session_id=session_id,
87
+ release=release,
88
+ )
89
+ try:
90
+ result = await func(*args, **kwargs)
91
+ except Exception as e:
92
+ context_manager.observe_end(result=None, span=span, error=e)
93
+ raise e
94
+ context_manager.observe_end(
95
+ result=result if capture_output else None, span=span
96
+ )
97
+ return result
98
+
99
+ return async_wrapper if is_async(func) else wrapper
100
+
101
+ return decorator
102
+
103
+ def update_current_span(
104
+ self,
105
+ metadata: Optional[dict[str, Any]] = None,
106
+ override: bool = False,
107
+ ):
108
+ """Update the current span with any optional metadata.
109
+
110
+ Args:
111
+ metadata (Optional[dict[str, Any]], optional): metadata to the span. Defaults to None.
112
+ override (bool, optional): Whether to override the existing metadata. If False, metadata is merged with the existing metadata. Defaults to False.
113
+ """
114
+ laminar = LaminarSingleton().get()
115
+ laminar.update_current_span(metadata=metadata, override=override)
116
+
117
+ def update_current_trace(
118
+ self,
119
+ user_id: Optional[str] = None,
120
+ session_id: Optional[str] = None,
121
+ release: Optional[str] = None,
122
+ metadata: Optional[dict[str, Any]] = None,
123
+ ):
124
+ """Update the current trace with any optional metadata.
125
+
126
+ Args:
127
+ user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
128
+ session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
129
+ Defaults to None.
130
+ release (Optional[str], optional): Release version of your app. Useful for further grouping and analytics. Defaults to None.
131
+ metadata (Optional[dict[str, Any]], optional): metadata to the trace. Defaults to None.
132
+ """
133
+ laminar = LaminarSingleton().get()
134
+ laminar.update_current_trace(
135
+ user_id=user_id, session_id=session_id, release=release, metadata=metadata
136
+ )
137
+
138
+ def event(
139
+ self,
140
+ name: str,
141
+ value: Optional[Union[str, int]] = None,
142
+ timestamp: Optional[datetime.datetime] = None,
143
+ ):
144
+ """Associate an event with the current span
145
+
146
+ Args:
147
+ name (str): name of the event. Must be predefined in the Laminar events page.
148
+ value (Optional[Union[str, int]], optional): value of the event. Must match range definition in Laminar events page. Defaults to None.
149
+ timestamp (Optional[datetime.datetime], optional): If you need custom timestamp. If not specified, current time is used. Defaults to None.
150
+ """
151
+ laminar = LaminarSingleton().get()
152
+ laminar.event(name, value=value, timestamp=timestamp)
153
+
154
+ def evaluate_event(self, name: str, data: str):
155
+ """Evaluate an event with the given name and data. The event value will be assessed by the Laminar evaluation engine.
156
+ Data is passed as an input to the agent, so you need to specify which data you want to evaluate. Most of the times,
157
+ this is an output of the LLM generation, but sometimes, you may want to evaluate the input or both. In the latter case,
158
+ concatenate the input and output annotating with natural language.
159
+
160
+ Args:
161
+ name (str): Name of the event. Must be predefined in the Laminar events page.
162
+ data (str): Data to be evaluated. Typically the output of the LLM generation.
163
+ """
164
+ laminar = LaminarSingleton().get()
165
+ laminar.evaluate_event(name, data)
166
+
167
+ def run_pipeline(
168
+ self,
169
+ pipeline: str,
170
+ inputs: dict[str, NodeInput],
171
+ env: dict[str, str] = None,
172
+ metadata: dict[str, str] = None,
173
+ ) -> PipelineRunResponse:
174
+ """Run the laminar pipeline with the given inputs. Pipeline must be defined in the Laminar UI and have a target version.
175
+
176
+ Args:
177
+ pipeline (str): pipeline name
178
+ inputs (dict[str, NodeInput]): Map from input node name to input value
179
+ env (dict[str, str], optional): Environment variables for the pipeline executions. Typically contains API keys. Defaults to None.
180
+ metadata (dict[str, str], optional): Any additional data to associate with the resulting span. Defaults to None.
181
+
182
+ Returns:
183
+ PipelineRunResponse: Response from the pipeline execution
184
+ """
185
+ laminar = LaminarSingleton().get()
186
+ return laminar.run_pipeline(pipeline, inputs, env, metadata)
187
+
188
+
189
+ def wrap_llm_call(func: Callable, name: str = None, provider: str = None) -> Callable:
190
+ """Wrap an LLM call with Laminar observability. This is a convenience function that does the same as `@observe()`, plus
191
+ a few utilities around LLM-specific things, such as counting tokens and recording model params.
192
+
193
+ Example usage:
194
+ ```python
195
+ wrap_llm_call(client.chat.completions.create)(
196
+ model="gpt-4o-mini",
197
+ messages=[
198
+ {"role": "system", "content": "You are a helpful assistant."},
199
+ {"role": "user", "content": "Hello"},
200
+ ],
201
+ stream=True,
202
+ )
203
+ ```
204
+
205
+ Args:
206
+ func (Callable): The function to wrap
207
+ name (str, optional): Name of the resulting span. Default "{provider name} completion" if not specified. Defaults to None.
208
+ provider (str, optional): LLM model provider, e.g. openai, anthropic. This is needed to help us correctly parse
209
+ things like token usage. If not specified, we infer it from the name of the package,
210
+ where the function is imported from. Defaults to None.
211
+
212
+ Raises:
213
+ Exctption: re-raises the exception if the wrapped function raises an exception
214
+
215
+ Returns:
216
+ Callable: the wrapped function
217
+ """
218
+ laminar = LaminarSingleton().get()
219
+ # Simple heuristic to determine the package from where the LLM call is imported.
220
+ # This works for major providers, but will likely make no sense for custom providers.
221
+ provider_name = (
222
+ provider.lower().strip() if provider else func.__module__.split(".")[0]
223
+ )
224
+ provider_module = PROVIDER_NAME_TO_OBJECT.get(provider_name, FallbackProvider())
225
+ name = name or f"{provider_module.display_name()} completion"
226
+
227
+ @functools.wraps(func)
228
+ def wrapper(*args, **kwargs):
229
+ inp = kwargs.get("messages")
230
+ attributes = (
231
+ provider_module.extract_llm_attributes_from_args(args, kwargs)
232
+ if provider_module
233
+ else {}
234
+ )
235
+ attributes["provider"] = provider_name
236
+ span = laminar.observe_start(
237
+ name=name, span_type="LLM", input=inp, attributes=attributes
238
+ )
239
+ try:
240
+ result = func(*args, **kwargs)
241
+ except Exception as e:
242
+ laminar.observe_end(
243
+ result=None, span=span, error=e, provider_name=provider_name
244
+ )
245
+ raise e
246
+ return laminar.observe_end(
247
+ result=result, span=span, provider_name=provider_name
248
+ )
249
+
250
+ @functools.wraps(func)
251
+ async def async_wrapper(*args, **kwargs):
252
+ inp = kwargs.get("messages")
253
+ attributes = (
254
+ provider_module.extract_llm_attributes_from_args(args, kwargs)
255
+ if provider_module
256
+ else {}
257
+ )
258
+ attributes["provider"] = provider_name
259
+ span = laminar.observe_start(
260
+ name=name, span_type="LLM", input=inp, attributes=attributes
261
+ )
262
+ try:
263
+ result = await func(*args, **kwargs)
264
+ except Exception as e:
265
+ laminar.observe_end(
266
+ result=None, span=span, error=e, provider_name=provider_name
267
+ )
268
+ raise e
269
+ return laminar.observe_end(
270
+ result=result, span=span, provider_name=provider_name
271
+ )
272
+
273
+ return async_wrapper if is_async(func) else wrapper
274
+
275
+
276
+ lmnr_context = LaminarDecorator()
277
+ observe = lmnr_context.observe
lmnr/sdk/interface.py ADDED
@@ -0,0 +1,339 @@
1
+ from .context import LaminarSingleton
2
+ from .tracing_types import EvaluateEvent, Span, Trace, Event
3
+
4
+ from typing import Any, Literal, Optional, Union
5
+ import datetime
6
+ import logging
7
+ import uuid
8
+
9
+
10
+ laminar = LaminarSingleton().get()
11
+
12
+
13
+ class ObservationContext:
14
+ observation: Union[Span, Trace] = None
15
+ _parent: "ObservationContext" = None
16
+ _children: dict[uuid.UUID, "ObservationContext"] = {}
17
+ _log = logging.getLogger("laminar.observation_context")
18
+
19
+ def __init__(self, observation: Union[Span, Trace], parent: "ObservationContext"):
20
+ self.observation = observation
21
+ self._parent = parent
22
+ self._children = {}
23
+
24
+ def _get_parent(self) -> "ObservationContext":
25
+ raise NotImplementedError
26
+
27
+ def end(self, *args, **kwargs):
28
+ raise NotImplementedError
29
+
30
+ def update(self, *args, **kwargs):
31
+ raise NotImplementedError
32
+
33
+ def span(
34
+ self,
35
+ name: str,
36
+ input: Optional[Any] = None,
37
+ metadata: Optional[dict[str, Any]] = None,
38
+ attributes: Optional[dict[str, Any]] = None,
39
+ span_type: Literal["DEFAULT", "LLM"] = "DEFAULT",
40
+ ) -> "SpanContext":
41
+ """Create a span within the current (trace or span) context.
42
+
43
+ Args:
44
+ name (str): Span name
45
+ input (Optional[Any], optional): Inputs to the span. Defaults to None.
46
+ metadata (Optional[dict[str, Any]], optional): Any additional metadata. Defaults to None.
47
+ attributes (Optional[dict[str, Any]], optional): Any pre-defined attributes. Must comply to the convention. Defaults to None.
48
+ span_type (Literal["DEFAULT", "LLM"], optional): Type of the span. Defaults to "DEFAULT".
49
+
50
+ Returns:
51
+ SpanContext: The new span context
52
+ """
53
+ parent = self._get_parent()
54
+ parent_span_id = (
55
+ parent.observation.id if isinstance(parent.observation, Span) else None
56
+ )
57
+ trace_id = (
58
+ parent.observation.traceId
59
+ if isinstance(parent.observation, Span)
60
+ else parent.observation.id
61
+ )
62
+ span = laminar.create_span(
63
+ name=name,
64
+ trace_id=trace_id,
65
+ input=input,
66
+ metadata=metadata,
67
+ attributes=attributes,
68
+ parent_span_id=parent_span_id,
69
+ span_type=span_type,
70
+ )
71
+ span_context = SpanContext(span, self)
72
+ self._children[span.id] = span_context
73
+ return span_context
74
+
75
+ def id(self) -> uuid.UUID:
76
+ """Get the uuid of the current observation
77
+
78
+ Returns:
79
+ uuid.UUID: UUID of the observation
80
+ """
81
+ return self.observation.id
82
+
83
+
84
+ class SpanContext(ObservationContext):
85
+ def _get_parent(self) -> ObservationContext:
86
+ return self._parent
87
+
88
+ def end(
89
+ self,
90
+ output: Optional[Any] = None,
91
+ metadata: Optional[dict[str, Any]] = None,
92
+ evaluate_events: Optional[list[EvaluateEvent]] = None,
93
+ override: bool = False,
94
+ ) -> "SpanContext":
95
+ """End the span with the given output and optional metadata and evaluate events.
96
+
97
+ Args:
98
+ output (Optional[Any], optional): output of the span. Defaults to None.
99
+ metadata (Optional[dict[str, Any]], optional): any additional metadata to the span. Defaults to None.
100
+ check_event_names (Optional[list[EvaluateEvent]], optional): List of events to evaluate for and tag. Defaults to None.
101
+ override (bool, optional): override existing metadata fully. If False, metadata is merged. Defaults to False.
102
+
103
+ Returns:
104
+ SpanContext: the finished span context
105
+ """
106
+ if self._children:
107
+ self._log.warning(
108
+ "Ending span %s, but it has children that have not been finalized. Children: %s",
109
+ self.observation.name,
110
+ [child.observation.name for child in self._children.values()],
111
+ )
112
+ self._get_parent()._children.pop(self.observation.id)
113
+ return self._update(
114
+ output=output,
115
+ metadata=metadata,
116
+ evaluate_events=evaluate_events,
117
+ override=override,
118
+ finalize=True,
119
+ )
120
+
121
+ def update(
122
+ self,
123
+ output: Optional[Any] = None,
124
+ metadata: Optional[dict[str, Any]] = None,
125
+ evaluate_events: Optional[list[EvaluateEvent]] = None,
126
+ override: bool = False,
127
+ ) -> "SpanContext":
128
+ """Update the current span with (optionally) the given output and optional metadata and evaluate events, but don't end it.
129
+
130
+ Args:
131
+ output (Optional[Any], optional): output of the span. Defaults to None.
132
+ metadata (Optional[dict[str, Any]], optional): any additional metadata to the span. Defaults to None.
133
+ check_event_names (Optional[list[EvaluateEvent]], optional): List of events to evaluate for and tag. Defaults to None.
134
+ override (bool, optional): override existing metadata fully. If False, metadata is merged. Defaults to False.
135
+
136
+ Returns:
137
+ SpanContext: the finished span context
138
+ """
139
+ return self._update(
140
+ output=output or self.observation.output,
141
+ metadata=metadata or self.observation.metadata,
142
+ evaluate_events=evaluate_events or self.observation.evaluateEvents,
143
+ override=override,
144
+ finalize=False,
145
+ )
146
+
147
+ def event(
148
+ self,
149
+ name: str,
150
+ value: Optional[Union[str, int]] = None,
151
+ timestamp: Optional[datetime.datetime] = None,
152
+ ) -> "SpanContext":
153
+ """Associate an event with the current span
154
+
155
+ Args:
156
+ name (str): name of the event. Must be predefined in the Laminar events page.
157
+ value (Optional[Union[str, int]], optional): value of the event. Must match range definition in Laminar events page. Defaults to None.
158
+ timestamp (Optional[datetime.datetime], optional): If you need custom timestamp. If not specified, current time is used. Defaults to None.
159
+
160
+ Returns:
161
+ SpanContext: the updated span context
162
+ """
163
+ event = Event(
164
+ name=name,
165
+ span_id=self.observation.id,
166
+ timestamp=timestamp,
167
+ value=value,
168
+ )
169
+ self.observation.add_event(event)
170
+ return self
171
+
172
+ def evaluate_event(self, name: str, data: str) -> "SpanContext":
173
+ """Evaluate an event with the given name and data. The event value will be assessed by the Laminar evaluation engine.
174
+ Data is passed as an input to the agent, so you need to specify which data you want to evaluate. Most of the times,
175
+ this is an output of the LLM generation, but sometimes, you may want to evaluate the input or both. In the latter case,
176
+ concatenate the input and output annotating with natural language.
177
+
178
+ Args:
179
+ name (str): Name of the event. Must be predefined in the Laminar events page.
180
+ data (str): Data to be evaluated. Typically the output of the LLM generation.
181
+
182
+ Returns:
183
+ SpanContext: the updated span context
184
+ """
185
+ existing_evaluate_events = self.observation.evaluateEvents
186
+ output = self.observation.output
187
+ self._update(
188
+ output=output,
189
+ evaluate_events=existing_evaluate_events
190
+ + [EvaluateEvent(name=name, data=data)],
191
+ override=False,
192
+ )
193
+
194
+ def _update(
195
+ self,
196
+ output: Optional[Any] = None,
197
+ metadata: Optional[dict[str, Any]] = None,
198
+ evaluate_events: Optional[list[EvaluateEvent]] = None,
199
+ override: bool = False,
200
+ finalize: bool = False,
201
+ ) -> "SpanContext":
202
+ new_metadata = (
203
+ metadata
204
+ if override
205
+ else {**(self.observation.metadata or {}), **(metadata or {})}
206
+ )
207
+ new_evaluate_events = (
208
+ evaluate_events
209
+ if override
210
+ else self.observation.evaluateEvents + (evaluate_events or [])
211
+ )
212
+ self.observation = laminar.update_span(
213
+ span=self.observation,
214
+ end_time=datetime.datetime.now(datetime.timezone.utc),
215
+ output=output,
216
+ metadata=new_metadata,
217
+ evaluate_events=new_evaluate_events,
218
+ finalize=finalize,
219
+ )
220
+ return self
221
+
222
+
223
+ class TraceContext(ObservationContext):
224
+ def _get_parent(self) -> "ObservationContext":
225
+ return self
226
+
227
+ def update(
228
+ self,
229
+ user_id: Optional[str] = None,
230
+ session_id: Optional[str] = None,
231
+ release: Optional[str] = None,
232
+ metadata: Optional[dict[str, Any]] = None,
233
+ success: bool = True,
234
+ ) -> "TraceContext":
235
+ """Update the current trace with the given metadata and success status.
236
+
237
+ Args:
238
+ user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
239
+ session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
240
+ Defaults to None.
241
+ release (Optional[str], optional): _description_. Release of your application. Useful for grouping and further analytics. Defaults to None.
242
+ metadata (Optional[dict[str, Any]], optional): any additional metadata to the trace. Defaults to None.
243
+ success (bool, optional): whether this trace ran successfully. Defaults to True.
244
+
245
+ Returns:
246
+ TraceContext: updated trace context
247
+ """
248
+ return self._update(
249
+ user_id=user_id or self.observation.userId,
250
+ session_id=session_id or self.observation.sessionId,
251
+ release=release or self.observation.release,
252
+ metadata=metadata or self.observation.metadata,
253
+ success=success if success is not None else self.observation.success,
254
+ )
255
+
256
+ def end(
257
+ self,
258
+ user_id: Optional[str] = None,
259
+ session_id: Optional[str] = None,
260
+ release: Optional[str] = None,
261
+ metadata: Optional[dict[str, Any]] = None,
262
+ success: bool = True,
263
+ ) -> "TraceContext":
264
+ """End the current trace with the given metadata and success status.
265
+
266
+ Args:
267
+ user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
268
+ session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
269
+ Defaults to None.
270
+ release (Optional[str], optional): _description_. Release of your application. Useful for grouping and further analytics. Defaults to None.
271
+ metadata (Optional[dict[str, Any]], optional): any additional metadata to the trace. Defaults to None.
272
+ success (bool, optional): whether this trace ran successfully. Defaults to True.
273
+
274
+ Returns:
275
+ TraceContext: context of the ended trace
276
+ """
277
+ if self._children:
278
+ self._log.warning(
279
+ "Ending trace id: %s, but it has children that have not been finalized. Children: %s",
280
+ self.observation.id,
281
+ [child.observation.name for child in self._children.values()],
282
+ )
283
+ return self._update(
284
+ user_id=user_id or self.observation.userId,
285
+ session_id=session_id or self.observation.sessionId,
286
+ release=release or self.observation.release,
287
+ metadata=metadata or self.observation.metadata,
288
+ success=success if success is not None else self.observation.success,
289
+ end_time=datetime.datetime.now(datetime.timezone.utc),
290
+ )
291
+
292
+ def _update(
293
+ self,
294
+ user_id: Optional[str] = None,
295
+ session_id: Optional[str] = None,
296
+ release: Optional[str] = None,
297
+ metadata: Optional[dict[str, Any]] = None,
298
+ success: bool = True,
299
+ end_time: Optional[datetime.datetime] = None,
300
+ ) -> "TraceContext":
301
+ self.observation = laminar.update_trace(
302
+ id=self.observation.id,
303
+ user_id=user_id,
304
+ start_time=self.observation.startTime,
305
+ session_id=session_id,
306
+ release=release,
307
+ metadata=metadata,
308
+ success=success,
309
+ end_time=end_time,
310
+ )
311
+ return self
312
+
313
+
314
+ def trace(
315
+ user_id: Optional[str] = None,
316
+ session_id: Optional[str] = None,
317
+ release: Optional[str] = None,
318
+ ) -> TraceContext:
319
+ """Create the initial trace context. All further spans will be created within this context.
320
+
321
+ Args:
322
+ user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
323
+ session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
324
+ Defaults to None.
325
+ release (Optional[str], optional): _description_. Release of your application. Useful for grouping and further analytics. Defaults to None.
326
+
327
+ Returns:
328
+ TraceContext: the pointer to the trace context. Use `.span()` to create a new span within this context.
329
+ """
330
+ session_id = session_id or str(uuid.uuid4())
331
+ trace_id = uuid.uuid4()
332
+ trace = laminar.update_trace(
333
+ id=trace_id,
334
+ user_id=user_id,
335
+ session_id=session_id,
336
+ release=release,
337
+ start_time=datetime.datetime.now(datetime.timezone.utc),
338
+ )
339
+ return TraceContext(trace, None)
@@ -0,0 +1,2 @@
1
+ from .base import Provider
2
+ from .openai import OpenAI
@@ -0,0 +1,28 @@
1
+ import abc
2
+ import pydantic
3
+ import typing
4
+
5
+
6
+ class Provider(abc.ABC):
7
+ def display_name(self) -> str:
8
+ raise NotImplementedError("display_name not implemented")
9
+
10
+ def stream_list_to_dict(self, response: list[typing.Any]) -> dict[str, typing.Any]:
11
+ raise NotImplementedError("stream_list_to_dict not implemented")
12
+
13
+ def extract_llm_attributes_from_response(
14
+ self, response: typing.Union[str, dict[str, typing.Any], pydantic.BaseModel]
15
+ ) -> dict[str, typing.Any]:
16
+ raise NotImplementedError(
17
+ "extract_llm_attributes_from_response not implemented"
18
+ )
19
+
20
+ def extract_llm_output(
21
+ self, response: typing.Union[str, dict[str, typing.Any], pydantic.BaseModel]
22
+ ) -> typing.Any:
23
+ raise NotImplementedError("extract_llm_output not implemented")
24
+
25
+ def extract_llm_attributes_from_args(
26
+ self, func_args: list[typing.Any], func_kwargs: dict[str, typing.Any]
27
+ ) -> dict[str, typing.Any]:
28
+ raise NotImplementedError("_extract_llm_attributes_from_args not implemented")