lmnr 0.3.1__tar.gz → 0.3.2__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.3.1 → lmnr-0.3.2}/PKG-INFO +62 -9
- {lmnr-0.3.1 → lmnr-0.3.2}/README.md +61 -8
- {lmnr-0.3.1 → lmnr-0.3.2}/pyproject.toml +2 -2
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/__init__.py +2 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/context.py +28 -20
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/decorators.py +7 -3
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/interface.py +35 -66
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/providers/fallback.py +31 -8
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/providers/openai.py +29 -10
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/tracing_types.py +3 -6
- lmnr-0.3.2/src/lmnr/semantic_conventions/__init__.py +0 -0
- lmnr-0.3.2/src/lmnr/semantic_conventions/gen_ai_spans.py +48 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/LICENSE +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/__init__.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/client.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/collector.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/constants.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/providers/__init__.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/providers/base.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/providers/utils.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/types.py +0 -0
- {lmnr-0.3.1 → lmnr-0.3.2}/src/lmnr/sdk/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: lmnr
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.2
|
4
4
|
Summary: Python SDK for Laminar AI
|
5
5
|
License: Apache-2.0
|
6
6
|
Author: lmnr.ai
|
@@ -49,6 +49,14 @@ Important notes:
|
|
49
49
|
- If event name does not match anything pre-defined in the UI, the event won't be saved.
|
50
50
|
- If event value (when sent with `.event()`) is not in the domain, the event won't be saved.
|
51
51
|
|
52
|
+
## Instrumentation
|
53
|
+
|
54
|
+
We provide two ways to instrument your python code:
|
55
|
+
- With `@observe()` decorators and `wrap_llm_call` helpers
|
56
|
+
- Manually
|
57
|
+
|
58
|
+
It is important to not mix the two styles of instrumentation, this can lead to unpredictable results.
|
59
|
+
|
52
60
|
## Decorator instrumentation example
|
53
61
|
|
54
62
|
For easy automatic instrumentation, we provide you two simple primitives:
|
@@ -109,11 +117,11 @@ For manual instrumetation you will need to import the following:
|
|
109
117
|
Both `TraceContext` and `SpanContext` expose the following interfaces:
|
110
118
|
- `span(name: str, **kwargs)` - create a child span within the current context. Returns `SpanContext`
|
111
119
|
- `update(**kwargs)` - update the current trace or span and return it. Returns `TraceContext` or `SpanContext`. Useful when some metadata becomes known later during the program execution
|
112
|
-
- `end(**kwargs)` – update the current span, and terminate it
|
113
120
|
|
114
121
|
In addition, `SpanContext` allows you to:
|
115
122
|
- `event(name: str, value: str | int)` - emit a custom event at any point
|
116
123
|
- `evaluate_event(name: str, data: str)` - register a possible event for automatic checking by Laminar.
|
124
|
+
- `end(**kwargs)` – update the current span, and terminate it
|
117
125
|
|
118
126
|
Example:
|
119
127
|
|
@@ -122,6 +130,7 @@ import os
|
|
122
130
|
from openai import OpenAI
|
123
131
|
|
124
132
|
from lmnr import trace, TraceContext, SpanContext, EvaluateEvent
|
133
|
+
from lmnr.semantic_conventions.gen_ai_spans import INPUT_TOKEN_COUNT, OUTPUT_TOKEN_COUNT, RESPONSE_MODEL, PROVIDER, STREAM
|
125
134
|
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
|
126
135
|
|
127
136
|
def poem_writer(t: TraceContext, topic = "turbulence"):
|
@@ -150,7 +159,14 @@ def poem_writer(t: TraceContext, topic = "turbulence"):
|
|
150
159
|
# not only `llm_span.evaluate_event()`
|
151
160
|
llm_span.end(
|
152
161
|
output=poem,
|
153
|
-
evaluate_events=[EvaluateEvent(name="excessive_wordines", data=poem)]
|
162
|
+
evaluate_events=[EvaluateEvent(name="excessive_wordines", data=poem)],
|
163
|
+
attributes={
|
164
|
+
INPUT_TOKEN_COUNT: response.usage.prompt_tokens,
|
165
|
+
OUTPUT_TOKEN_COUNT: response.usage.completion_tokens,
|
166
|
+
RESPONSE_MODEL: response.model,
|
167
|
+
PROVIDER: 'openai',
|
168
|
+
STREAM: False
|
169
|
+
}
|
154
170
|
)
|
155
171
|
span.end(output=poem)
|
156
172
|
return poem
|
@@ -158,14 +174,52 @@ def poem_writer(t: TraceContext, topic = "turbulence"):
|
|
158
174
|
|
159
175
|
t: TraceContext = trace(user_id="user123", session_id="session123", release="release")
|
160
176
|
main(t, topic="laminar flow")
|
161
|
-
t.end(success=True)
|
162
177
|
```
|
163
178
|
|
164
|
-
##
|
179
|
+
## Manual attributes
|
180
|
+
|
181
|
+
You can specify span attributes when creating/updating/ending spans.
|
182
|
+
|
183
|
+
If you use [decorator instrumentation](#decorator-instrumentation-example), `wrap_llm_call` handles all of this for you.
|
184
|
+
|
185
|
+
Example usage:
|
186
|
+
|
187
|
+
```python
|
188
|
+
from lmnr.semantic_conventions.gen_ai_spans import REQUEST_MODEL
|
189
|
+
|
190
|
+
# span_type = LLM is important for correct attribute semantics
|
191
|
+
llm_span = span.span(name="OpenAI completion", input=messages, span_type="LLM")
|
192
|
+
llm_span.update(
|
193
|
+
attributes={REQUEST_MODEL: "gpt-4o-mini"}
|
194
|
+
)
|
195
|
+
response = 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. What is the capital of France?"},
|
200
|
+
],
|
201
|
+
)
|
202
|
+
```
|
203
|
+
|
204
|
+
Semantics:
|
205
|
+
|
206
|
+
Check for available semantic conventions in `lmnr.semantic_conventions.gen_ai_spans`.
|
207
|
+
|
208
|
+
You can specify the cost with `COST`. Otherwise, the cost will be calculated
|
209
|
+
on the Laminar servers, given the following are specified:
|
165
210
|
|
166
|
-
-
|
167
|
-
-
|
168
|
-
-
|
211
|
+
- span_type is `"LLM"`
|
212
|
+
- Model provider: `PROVIDER`, e.g. 'openai', 'anthropic'
|
213
|
+
- Output tokens: `OUTPUT_TOKEN_COUNT`
|
214
|
+
- Input tokens: `INPUT_TOKEN_COUNT`*
|
215
|
+
- Model. We look at `RESPONSE_MODEL` first, and then, if it is not present, we take the value of `REQUEST_MODEL`
|
216
|
+
|
217
|
+
\* Also, for the case when `PROVIDER` is `"openai"`, the `STREAM` is set to `True`, and `INPUT_TOKEN_COUNT` is not set, we will calculate
|
218
|
+
the number of input tokens, and the cost on the server using [tiktoken](https://github.com/zurawiki/tiktoken-rs) and
|
219
|
+
use it in cost calculation.
|
220
|
+
This is done because OpenAI does not stream the usage back
|
221
|
+
when streaming is enabled. Output token count is (approximately) equal to the number of streaming
|
222
|
+
events sent by OpenAI, but there is no way to calculate the input token count, other than re-tokenizing.
|
169
223
|
|
170
224
|
## Making Laminar pipeline calls
|
171
225
|
|
@@ -202,4 +256,3 @@ PipelineRunResponse(
|
|
202
256
|
)
|
203
257
|
```
|
204
258
|
|
205
|
-
|
@@ -29,6 +29,14 @@ Important notes:
|
|
29
29
|
- If event name does not match anything pre-defined in the UI, the event won't be saved.
|
30
30
|
- If event value (when sent with `.event()`) is not in the domain, the event won't be saved.
|
31
31
|
|
32
|
+
## Instrumentation
|
33
|
+
|
34
|
+
We provide two ways to instrument your python code:
|
35
|
+
- With `@observe()` decorators and `wrap_llm_call` helpers
|
36
|
+
- Manually
|
37
|
+
|
38
|
+
It is important to not mix the two styles of instrumentation, this can lead to unpredictable results.
|
39
|
+
|
32
40
|
## Decorator instrumentation example
|
33
41
|
|
34
42
|
For easy automatic instrumentation, we provide you two simple primitives:
|
@@ -89,11 +97,11 @@ For manual instrumetation you will need to import the following:
|
|
89
97
|
Both `TraceContext` and `SpanContext` expose the following interfaces:
|
90
98
|
- `span(name: str, **kwargs)` - create a child span within the current context. Returns `SpanContext`
|
91
99
|
- `update(**kwargs)` - update the current trace or span and return it. Returns `TraceContext` or `SpanContext`. Useful when some metadata becomes known later during the program execution
|
92
|
-
- `end(**kwargs)` – update the current span, and terminate it
|
93
100
|
|
94
101
|
In addition, `SpanContext` allows you to:
|
95
102
|
- `event(name: str, value: str | int)` - emit a custom event at any point
|
96
103
|
- `evaluate_event(name: str, data: str)` - register a possible event for automatic checking by Laminar.
|
104
|
+
- `end(**kwargs)` – update the current span, and terminate it
|
97
105
|
|
98
106
|
Example:
|
99
107
|
|
@@ -102,6 +110,7 @@ import os
|
|
102
110
|
from openai import OpenAI
|
103
111
|
|
104
112
|
from lmnr import trace, TraceContext, SpanContext, EvaluateEvent
|
113
|
+
from lmnr.semantic_conventions.gen_ai_spans import INPUT_TOKEN_COUNT, OUTPUT_TOKEN_COUNT, RESPONSE_MODEL, PROVIDER, STREAM
|
105
114
|
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
|
106
115
|
|
107
116
|
def poem_writer(t: TraceContext, topic = "turbulence"):
|
@@ -130,7 +139,14 @@ def poem_writer(t: TraceContext, topic = "turbulence"):
|
|
130
139
|
# not only `llm_span.evaluate_event()`
|
131
140
|
llm_span.end(
|
132
141
|
output=poem,
|
133
|
-
evaluate_events=[EvaluateEvent(name="excessive_wordines", data=poem)]
|
142
|
+
evaluate_events=[EvaluateEvent(name="excessive_wordines", data=poem)],
|
143
|
+
attributes={
|
144
|
+
INPUT_TOKEN_COUNT: response.usage.prompt_tokens,
|
145
|
+
OUTPUT_TOKEN_COUNT: response.usage.completion_tokens,
|
146
|
+
RESPONSE_MODEL: response.model,
|
147
|
+
PROVIDER: 'openai',
|
148
|
+
STREAM: False
|
149
|
+
}
|
134
150
|
)
|
135
151
|
span.end(output=poem)
|
136
152
|
return poem
|
@@ -138,14 +154,52 @@ def poem_writer(t: TraceContext, topic = "turbulence"):
|
|
138
154
|
|
139
155
|
t: TraceContext = trace(user_id="user123", session_id="session123", release="release")
|
140
156
|
main(t, topic="laminar flow")
|
141
|
-
t.end(success=True)
|
142
157
|
```
|
143
158
|
|
144
|
-
##
|
159
|
+
## Manual attributes
|
160
|
+
|
161
|
+
You can specify span attributes when creating/updating/ending spans.
|
162
|
+
|
163
|
+
If you use [decorator instrumentation](#decorator-instrumentation-example), `wrap_llm_call` handles all of this for you.
|
164
|
+
|
165
|
+
Example usage:
|
166
|
+
|
167
|
+
```python
|
168
|
+
from lmnr.semantic_conventions.gen_ai_spans import REQUEST_MODEL
|
169
|
+
|
170
|
+
# span_type = LLM is important for correct attribute semantics
|
171
|
+
llm_span = span.span(name="OpenAI completion", input=messages, span_type="LLM")
|
172
|
+
llm_span.update(
|
173
|
+
attributes={REQUEST_MODEL: "gpt-4o-mini"}
|
174
|
+
)
|
175
|
+
response = client.chat.completions.create(
|
176
|
+
model="gpt-4o-mini",
|
177
|
+
messages=[
|
178
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
179
|
+
{"role": "user", "content": "Hello. What is the capital of France?"},
|
180
|
+
],
|
181
|
+
)
|
182
|
+
```
|
183
|
+
|
184
|
+
Semantics:
|
185
|
+
|
186
|
+
Check for available semantic conventions in `lmnr.semantic_conventions.gen_ai_spans`.
|
187
|
+
|
188
|
+
You can specify the cost with `COST`. Otherwise, the cost will be calculated
|
189
|
+
on the Laminar servers, given the following are specified:
|
145
190
|
|
146
|
-
-
|
147
|
-
-
|
148
|
-
-
|
191
|
+
- span_type is `"LLM"`
|
192
|
+
- Model provider: `PROVIDER`, e.g. 'openai', 'anthropic'
|
193
|
+
- Output tokens: `OUTPUT_TOKEN_COUNT`
|
194
|
+
- Input tokens: `INPUT_TOKEN_COUNT`*
|
195
|
+
- Model. We look at `RESPONSE_MODEL` first, and then, if it is not present, we take the value of `REQUEST_MODEL`
|
196
|
+
|
197
|
+
\* Also, for the case when `PROVIDER` is `"openai"`, the `STREAM` is set to `True`, and `INPUT_TOKEN_COUNT` is not set, we will calculate
|
198
|
+
the number of input tokens, and the cost on the server using [tiktoken](https://github.com/zurawiki/tiktoken-rs) and
|
199
|
+
use it in cost calculation.
|
200
|
+
This is done because OpenAI does not stream the usage back
|
201
|
+
when streaming is enabled. Output token count is (approximately) equal to the number of streaming
|
202
|
+
events sent by OpenAI, but there is no way to calculate the input token count, other than re-tokenizing.
|
149
203
|
|
150
204
|
## Making Laminar pipeline calls
|
151
205
|
|
@@ -181,4 +235,3 @@ PipelineRunResponse(
|
|
181
235
|
run_id='53b012d5-5759-48a6-a9c5-0011610e3669'
|
182
236
|
)
|
183
237
|
```
|
184
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "lmnr"
|
3
|
-
version = "0.3.
|
3
|
+
version = "0.3.2"
|
4
4
|
description = "Python SDK for Laminar AI"
|
5
5
|
authors = [
|
6
6
|
{ name = "lmnr.ai", email = "founders@lmnr.ai" }
|
@@ -11,7 +11,7 @@ license = "Apache-2.0"
|
|
11
11
|
|
12
12
|
[tool.poetry]
|
13
13
|
name = "lmnr"
|
14
|
-
version = "0.3.
|
14
|
+
version = "0.3.2"
|
15
15
|
description = "Python SDK for Laminar AI"
|
16
16
|
authors = ["lmnr.ai"]
|
17
17
|
readme = "README.md"
|
@@ -3,3 +3,5 @@ from .sdk.decorators import observe, lmnr_context, wrap_llm_call
|
|
3
3
|
from .sdk.interface import trace, TraceContext, SpanContext
|
4
4
|
from .sdk.tracing_types import EvaluateEvent
|
5
5
|
from .sdk.types import ChatMessage, PipelineRunError, PipelineRunResponse, NodeInput
|
6
|
+
|
7
|
+
from .semantic_conventions import *
|
@@ -75,7 +75,6 @@ class LaminarContextManager:
|
|
75
75
|
user_id=user_id,
|
76
76
|
session_id=session_id,
|
77
77
|
release=release,
|
78
|
-
start_time=datetime.datetime.now(datetime.timezone.utc),
|
79
78
|
)
|
80
79
|
_root_trace_id_context.set(trace.id)
|
81
80
|
_lmnr_stack_context.set([trace])
|
@@ -116,8 +115,6 @@ class LaminarContextManager:
|
|
116
115
|
trace = stack[0]
|
117
116
|
self.update_trace(
|
118
117
|
id=trace.id,
|
119
|
-
start_time=trace.startTime,
|
120
|
-
end_time=datetime.datetime.now(datetime.timezone.utc),
|
121
118
|
user_id=trace.userId,
|
122
119
|
session_id=trace.sessionId,
|
123
120
|
release=trace.release,
|
@@ -127,9 +124,7 @@ class LaminarContextManager:
|
|
127
124
|
_lmnr_stack_context.set([])
|
128
125
|
|
129
126
|
if error is not None:
|
130
|
-
self.update_current_trace(
|
131
|
-
success=False, end_time=datetime.datetime.now(datetime.timezone.utc)
|
132
|
-
)
|
127
|
+
self.update_current_trace(success=False)
|
133
128
|
|
134
129
|
if inspect.isgenerator(result) or is_iterator(result):
|
135
130
|
return self._collect_generator_result(
|
@@ -162,7 +157,8 @@ class LaminarContextManager:
|
|
162
157
|
def update_current_span(
|
163
158
|
self,
|
164
159
|
metadata: Optional[dict[str, Any]] = None,
|
165
|
-
|
160
|
+
attributes: Optional[dict[str, Any]] = None,
|
161
|
+
evaluate_events: list[EvaluateEvent] = None,
|
166
162
|
override: bool = False,
|
167
163
|
):
|
168
164
|
stack = _lmnr_stack_context.get()
|
@@ -172,15 +168,21 @@ class LaminarContextManager:
|
|
172
168
|
new_metadata = (
|
173
169
|
metadata if override else {**(span.metadata or {}), **(metadata or {})}
|
174
170
|
)
|
175
|
-
|
176
|
-
|
171
|
+
new_evaluate_events = (
|
172
|
+
evaluate_events
|
173
|
+
if override
|
174
|
+
else span.evaluateEvents + (evaluate_events or [])
|
175
|
+
)
|
176
|
+
new_attributes = (
|
177
|
+
attributes
|
177
178
|
if override
|
178
|
-
else span.
|
179
|
+
else {**(span.attributes or {}), **(attributes or {})}
|
179
180
|
)
|
180
181
|
self.update_span(
|
181
182
|
span=span,
|
182
183
|
metadata=new_metadata,
|
183
|
-
evaluate_events=
|
184
|
+
evaluate_events=new_evaluate_events,
|
185
|
+
attributes=new_attributes,
|
184
186
|
)
|
185
187
|
|
186
188
|
def update_current_trace(
|
@@ -190,7 +192,6 @@ class LaminarContextManager:
|
|
190
192
|
release: Optional[str] = None,
|
191
193
|
metadata: Optional[dict[str, Any]] = None,
|
192
194
|
success: bool = True,
|
193
|
-
end_time: Optional[datetime.datetime] = None,
|
194
195
|
):
|
195
196
|
existing_trace = (
|
196
197
|
_lmnr_stack_context.get()[0] if _lmnr_stack_context.get() else None
|
@@ -199,8 +200,6 @@ class LaminarContextManager:
|
|
199
200
|
return
|
200
201
|
self.update_trace(
|
201
202
|
id=existing_trace.id,
|
202
|
-
start_time=existing_trace.startTime,
|
203
|
-
end_time=end_time,
|
204
203
|
user_id=user_id or existing_trace.userId,
|
205
204
|
session_id=session_id or existing_trace.sessionId,
|
206
205
|
release=release or existing_trace.release,
|
@@ -211,8 +210,6 @@ class LaminarContextManager:
|
|
211
210
|
def update_trace(
|
212
211
|
self,
|
213
212
|
id: uuid.UUID,
|
214
|
-
start_time: Optional[datetime.datetime] = None,
|
215
|
-
end_time: Optional[datetime.datetime] = None,
|
216
213
|
user_id: Optional[str] = None,
|
217
214
|
session_id: Optional[str] = None,
|
218
215
|
release: Optional[str] = None,
|
@@ -220,8 +217,6 @@ class LaminarContextManager:
|
|
220
217
|
success: bool = True,
|
221
218
|
) -> Trace:
|
222
219
|
trace = Trace(
|
223
|
-
start_time=start_time,
|
224
|
-
end_time=end_time,
|
225
220
|
id=id,
|
226
221
|
user_id=user_id,
|
227
222
|
session_id=session_id,
|
@@ -245,6 +240,7 @@ class LaminarContextManager:
|
|
245
240
|
attributes: Optional[dict[str, Any]] = None,
|
246
241
|
check_event_names: list[str] = None,
|
247
242
|
) -> Span:
|
243
|
+
"""Internal method to create a span object. Use `ObservationContext.span` instead."""
|
248
244
|
span = Span(
|
249
245
|
name=name,
|
250
246
|
trace_id=trace_id,
|
@@ -263,18 +259,23 @@ class LaminarContextManager:
|
|
263
259
|
self,
|
264
260
|
span: Span,
|
265
261
|
finalize: bool = False,
|
262
|
+
input: Optional[Any] = None,
|
266
263
|
end_time: Optional[datetime.datetime] = None,
|
267
264
|
output: Optional[Any] = None,
|
268
265
|
metadata: Optional[dict[str, Any]] = None,
|
269
266
|
attributes: Optional[dict[str, Any]] = None,
|
270
267
|
evaluate_events: Optional[list[EvaluateEvent]] = None,
|
268
|
+
override: bool = False,
|
271
269
|
) -> Span:
|
270
|
+
"""Internal method to update a span object. Use `SpanContext.update()` instead."""
|
272
271
|
span.update(
|
272
|
+
input=input,
|
273
273
|
end_time=end_time,
|
274
274
|
output=output,
|
275
275
|
metadata=metadata,
|
276
276
|
attributes=attributes,
|
277
277
|
evaluate_events=evaluate_events,
|
278
|
+
override=override,
|
278
279
|
)
|
279
280
|
if finalize:
|
280
281
|
self._add_observation(span)
|
@@ -305,7 +306,13 @@ class LaminarContextManager:
|
|
305
306
|
f"No active span to add check event. Ignoring event. {name}"
|
306
307
|
)
|
307
308
|
return
|
308
|
-
stack[-1].evaluateEvents.append(
|
309
|
+
stack[-1].evaluateEvents.append(
|
310
|
+
EvaluateEvent(
|
311
|
+
name=name,
|
312
|
+
data=data,
|
313
|
+
timestamp=datetime.datetime.now(datetime.timezone.utc),
|
314
|
+
)
|
315
|
+
)
|
309
316
|
|
310
317
|
def run_pipeline(
|
311
318
|
self,
|
@@ -328,7 +335,8 @@ class LaminarContextManager:
|
|
328
335
|
)
|
329
336
|
|
330
337
|
def _force_finalize_trace(self):
|
331
|
-
|
338
|
+
# TODO: flush in progress spans as error?
|
339
|
+
pass
|
332
340
|
|
333
341
|
def _add_observation(self, observation: Union[Span, Trace]) -> bool:
|
334
342
|
return self.thread_manager.add_task(observation)
|
@@ -5,6 +5,7 @@ from typing import Any, Callable, Literal, Optional, Union
|
|
5
5
|
|
6
6
|
from .context import LaminarSingleton
|
7
7
|
from .providers.fallback import FallbackProvider
|
8
|
+
from ..semantic_conventions.gen_ai_spans import PROVIDER
|
8
9
|
from .types import NodeInput, PipelineRunResponse
|
9
10
|
from .utils import (
|
10
11
|
PROVIDER_NAME_TO_OBJECT,
|
@@ -103,6 +104,7 @@ class LaminarDecorator:
|
|
103
104
|
def update_current_span(
|
104
105
|
self,
|
105
106
|
metadata: Optional[dict[str, Any]] = None,
|
107
|
+
attributes: Optional[dict[str, Any]] = None,
|
106
108
|
override: bool = False,
|
107
109
|
):
|
108
110
|
"""Update the current span with any optional metadata.
|
@@ -112,7 +114,9 @@ class LaminarDecorator:
|
|
112
114
|
override (bool, optional): Whether to override the existing metadata. If False, metadata is merged with the existing metadata. Defaults to False.
|
113
115
|
"""
|
114
116
|
laminar = LaminarSingleton().get()
|
115
|
-
laminar.update_current_span(
|
117
|
+
laminar.update_current_span(
|
118
|
+
metadata=metadata, attributes=attributes, override=override
|
119
|
+
)
|
116
120
|
|
117
121
|
def update_current_trace(
|
118
122
|
self,
|
@@ -232,7 +236,7 @@ def wrap_llm_call(func: Callable, name: str = None, provider: str = None) -> Cal
|
|
232
236
|
if provider_module
|
233
237
|
else {}
|
234
238
|
)
|
235
|
-
attributes[
|
239
|
+
attributes[PROVIDER] = provider_name
|
236
240
|
span = laminar.observe_start(
|
237
241
|
name=name, span_type="LLM", input=inp, attributes=attributes
|
238
242
|
)
|
@@ -255,7 +259,7 @@ def wrap_llm_call(func: Callable, name: str = None, provider: str = None) -> Cal
|
|
255
259
|
if provider_module
|
256
260
|
else {}
|
257
261
|
)
|
258
|
-
attributes[
|
262
|
+
attributes[PROVIDER] = provider_name
|
259
263
|
span = laminar.observe_start(
|
260
264
|
name=name, span_type="LLM", input=inp, attributes=attributes
|
261
265
|
)
|
@@ -24,9 +24,6 @@ class ObservationContext:
|
|
24
24
|
def _get_parent(self) -> "ObservationContext":
|
25
25
|
raise NotImplementedError
|
26
26
|
|
27
|
-
def end(self, *args, **kwargs):
|
28
|
-
raise NotImplementedError
|
29
|
-
|
30
27
|
def update(self, *args, **kwargs):
|
31
28
|
raise NotImplementedError
|
32
29
|
|
@@ -50,7 +47,7 @@ class ObservationContext:
|
|
50
47
|
Returns:
|
51
48
|
SpanContext: The new span context
|
52
49
|
"""
|
53
|
-
parent = self
|
50
|
+
parent = self
|
54
51
|
parent_span_id = (
|
55
52
|
parent.observation.id if isinstance(parent.observation, Span) else None
|
56
53
|
)
|
@@ -87,16 +84,20 @@ class SpanContext(ObservationContext):
|
|
87
84
|
|
88
85
|
def end(
|
89
86
|
self,
|
87
|
+
input: Optional[Any] = None,
|
90
88
|
output: Optional[Any] = None,
|
91
89
|
metadata: Optional[dict[str, Any]] = None,
|
90
|
+
attributes: Optional[dict[str, Any]] = None,
|
92
91
|
evaluate_events: Optional[list[EvaluateEvent]] = None,
|
93
92
|
override: bool = False,
|
94
93
|
) -> "SpanContext":
|
95
94
|
"""End the span with the given output and optional metadata and evaluate events.
|
96
95
|
|
97
96
|
Args:
|
97
|
+
input (Optional[Any], optional): Inputs to the span. Defaults to None.
|
98
98
|
output (Optional[Any], optional): output of the span. Defaults to None.
|
99
99
|
metadata (Optional[dict[str, Any]], optional): any additional metadata to the span. Defaults to None.
|
100
|
+
attributes (Optional[dict[str, Any]], optional): pre-defined attributes (see semantic-convention). Defaults to None.
|
100
101
|
check_event_names (Optional[list[EvaluateEvent]], optional): List of events to evaluate for and tag. Defaults to None.
|
101
102
|
override (bool, optional): override existing metadata fully. If False, metadata is merged. Defaults to False.
|
102
103
|
|
@@ -111,25 +112,31 @@ class SpanContext(ObservationContext):
|
|
111
112
|
)
|
112
113
|
self._get_parent()._children.pop(self.observation.id)
|
113
114
|
return self._update(
|
115
|
+
input=input,
|
114
116
|
output=output,
|
115
117
|
metadata=metadata,
|
116
118
|
evaluate_events=evaluate_events,
|
119
|
+
attributes=attributes,
|
117
120
|
override=override,
|
118
121
|
finalize=True,
|
119
122
|
)
|
120
123
|
|
121
124
|
def update(
|
122
125
|
self,
|
126
|
+
input: Optional[Any] = None,
|
123
127
|
output: Optional[Any] = None,
|
124
128
|
metadata: Optional[dict[str, Any]] = None,
|
129
|
+
attributes: Optional[dict[str, Any]] = None,
|
125
130
|
evaluate_events: Optional[list[EvaluateEvent]] = None,
|
126
131
|
override: bool = False,
|
127
132
|
) -> "SpanContext":
|
128
133
|
"""Update the current span with (optionally) the given output and optional metadata and evaluate events, but don't end it.
|
129
134
|
|
130
135
|
Args:
|
136
|
+
input (Optional[Any], optional): Inputs to the span. Defaults to None.
|
131
137
|
output (Optional[Any], optional): output of the span. Defaults to None.
|
132
138
|
metadata (Optional[dict[str, Any]], optional): any additional metadata to the span. Defaults to None.
|
139
|
+
attributes (Optional[dict[str, Any]], optional): pre-defined attributes (see semantic-convention). Defaults to None.
|
133
140
|
check_event_names (Optional[list[EvaluateEvent]], optional): List of events to evaluate for and tag. Defaults to None.
|
134
141
|
override (bool, optional): override existing metadata fully. If False, metadata is merged. Defaults to False.
|
135
142
|
|
@@ -137,9 +144,11 @@ class SpanContext(ObservationContext):
|
|
137
144
|
SpanContext: the finished span context
|
138
145
|
"""
|
139
146
|
return self._update(
|
147
|
+
input=input or self.observation.input,
|
140
148
|
output=output or self.observation.output,
|
141
|
-
metadata=metadata
|
142
|
-
evaluate_events=evaluate_events
|
149
|
+
metadata=metadata,
|
150
|
+
evaluate_events=evaluate_events,
|
151
|
+
attributes=attributes,
|
143
152
|
override=override,
|
144
153
|
finalize=False,
|
145
154
|
)
|
@@ -182,40 +191,39 @@ class SpanContext(ObservationContext):
|
|
182
191
|
Returns:
|
183
192
|
SpanContext: the updated span context
|
184
193
|
"""
|
185
|
-
existing_evaluate_events = self.observation.evaluateEvents
|
186
|
-
output = self.observation.output
|
187
194
|
self._update(
|
188
|
-
|
189
|
-
|
190
|
-
|
195
|
+
input=self.observation.input,
|
196
|
+
output=self.observation.output,
|
197
|
+
evaluate_events=[
|
198
|
+
EvaluateEvent(
|
199
|
+
name=name,
|
200
|
+
data=data,
|
201
|
+
timestamp=datetime.datetime.now(datetime.timezone.utc),
|
202
|
+
)
|
203
|
+
],
|
191
204
|
override=False,
|
192
205
|
)
|
193
206
|
|
194
207
|
def _update(
|
195
208
|
self,
|
209
|
+
input: Optional[Any] = None,
|
196
210
|
output: Optional[Any] = None,
|
197
211
|
metadata: Optional[dict[str, Any]] = None,
|
212
|
+
attributes: Optional[dict[str, Any]] = None,
|
198
213
|
evaluate_events: Optional[list[EvaluateEvent]] = None,
|
199
214
|
override: bool = False,
|
200
215
|
finalize: bool = False,
|
201
216
|
) -> "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
217
|
self.observation = laminar.update_span(
|
218
|
+
input=input,
|
219
|
+
output=output,
|
213
220
|
span=self.observation,
|
214
221
|
end_time=datetime.datetime.now(datetime.timezone.utc),
|
215
|
-
|
216
|
-
|
217
|
-
evaluate_events=
|
222
|
+
metadata=metadata,
|
223
|
+
attributes=attributes,
|
224
|
+
evaluate_events=evaluate_events,
|
218
225
|
finalize=finalize,
|
226
|
+
override=override,
|
219
227
|
)
|
220
228
|
return self
|
221
229
|
|
@@ -253,42 +261,6 @@ class TraceContext(ObservationContext):
|
|
253
261
|
success=success if success is not None else self.observation.success,
|
254
262
|
)
|
255
263
|
|
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
264
|
def _update(
|
293
265
|
self,
|
294
266
|
user_id: Optional[str] = None,
|
@@ -301,12 +273,10 @@ class TraceContext(ObservationContext):
|
|
301
273
|
self.observation = laminar.update_trace(
|
302
274
|
id=self.observation.id,
|
303
275
|
user_id=user_id,
|
304
|
-
start_time=self.observation.startTime,
|
305
276
|
session_id=session_id,
|
306
277
|
release=release,
|
307
278
|
metadata=metadata,
|
308
279
|
success=success,
|
309
|
-
end_time=end_time,
|
310
280
|
)
|
311
281
|
return self
|
312
282
|
|
@@ -320,9 +290,9 @@ def trace(
|
|
320
290
|
|
321
291
|
Args:
|
322
292
|
user_id (Optional[str], optional): Custom user_id of your user. Useful for grouping and further analytics. Defaults to None.
|
323
|
-
|
324
|
-
|
325
|
-
|
293
|
+
session_id (Optional[str], optional): Custom session_id for your session. Random UUID is generated on Laminar side, if not specified.
|
294
|
+
Defaults to None.
|
295
|
+
release (Optional[str], optional): _description_. Release of your application. Useful for grouping and further analytics. Defaults to None.
|
326
296
|
|
327
297
|
Returns:
|
328
298
|
TraceContext: the pointer to the trace context. Use `.span()` to create a new span within this context.
|
@@ -334,6 +304,5 @@ def trace(
|
|
334
304
|
user_id=user_id,
|
335
305
|
session_id=session_id,
|
336
306
|
release=release,
|
337
|
-
start_time=datetime.datetime.now(datetime.timezone.utc),
|
338
307
|
)
|
339
308
|
return TraceContext(trace, None)
|
@@ -1,3 +1,19 @@
|
|
1
|
+
from ...semantic_conventions.gen_ai_spans import (
|
2
|
+
FINISH_REASONS,
|
3
|
+
FREQUENCY_PENALTY,
|
4
|
+
INPUT_TOKEN_COUNT,
|
5
|
+
MAX_TOKENS,
|
6
|
+
OUTPUT_TOKEN_COUNT,
|
7
|
+
PRESENCE_PENALTY,
|
8
|
+
REQUEST_MODEL,
|
9
|
+
RESPONSE_MODEL,
|
10
|
+
STOP_SEQUENCES,
|
11
|
+
STREAM,
|
12
|
+
TEMPERATURE,
|
13
|
+
TOP_K,
|
14
|
+
TOP_P,
|
15
|
+
TOTAL_TOKEN_COUNT,
|
16
|
+
)
|
1
17
|
from .base import Provider
|
2
18
|
from .utils import parse_or_dump_to_dict
|
3
19
|
|
@@ -85,11 +101,12 @@ class FallbackProvider(Provider):
|
|
85
101
|
decisions.append(None)
|
86
102
|
|
87
103
|
return {
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
104
|
+
RESPONSE_MODEL: obj.get("model"),
|
105
|
+
INPUT_TOKEN_COUNT: obj.get("usage", {}).get("prompt_tokens"),
|
106
|
+
OUTPUT_TOKEN_COUNT: obj.get("usage", {}).get("completion_tokens"),
|
107
|
+
TOTAL_TOKEN_COUNT: obj.get("usage", {}).get("total_tokens"),
|
108
|
+
FINISH_REASONS: obj.get("finish_reason"),
|
109
|
+
# "decision": self._from_singleton_list(decisions),
|
93
110
|
}
|
94
111
|
|
95
112
|
def extract_llm_output(
|
@@ -107,9 +124,15 @@ class FallbackProvider(Provider):
|
|
107
124
|
self, func_args: list[Any], func_kwargs: dict[str, Any]
|
108
125
|
) -> dict[str, Any]:
|
109
126
|
return {
|
110
|
-
|
111
|
-
|
112
|
-
|
127
|
+
REQUEST_MODEL: func_kwargs.get("model"),
|
128
|
+
TEMPERATURE: func_kwargs.get("temperature"),
|
129
|
+
TOP_P: func_kwargs.get("top_p"),
|
130
|
+
TOP_K: func_kwargs.get("top_k"),
|
131
|
+
FREQUENCY_PENALTY: func_kwargs.get("frequency_penalty"),
|
132
|
+
PRESENCE_PENALTY: func_kwargs.get("presence_penalty"),
|
133
|
+
STOP_SEQUENCES: func_kwargs.get("stop"),
|
134
|
+
MAX_TOKENS: func_kwargs.get("max_tokens"),
|
135
|
+
STREAM: func_kwargs.get("stream", False),
|
113
136
|
}
|
114
137
|
|
115
138
|
def _message_to_key_and_output(
|
@@ -1,4 +1,19 @@
|
|
1
1
|
from .base import Provider
|
2
|
+
from ...semantic_conventions.gen_ai_spans import (
|
3
|
+
FINISH_REASONS,
|
4
|
+
FREQUENCY_PENALTY,
|
5
|
+
INPUT_TOKEN_COUNT,
|
6
|
+
MAX_TOKENS,
|
7
|
+
OUTPUT_TOKEN_COUNT,
|
8
|
+
PRESENCE_PENALTY,
|
9
|
+
REQUEST_MODEL,
|
10
|
+
RESPONSE_MODEL,
|
11
|
+
STOP_SEQUENCES,
|
12
|
+
STREAM,
|
13
|
+
TEMPERATURE,
|
14
|
+
TOP_P,
|
15
|
+
TOTAL_TOKEN_COUNT,
|
16
|
+
)
|
2
17
|
from .utils import parse_or_dump_to_dict
|
3
18
|
|
4
19
|
from collections import defaultdict
|
@@ -92,12 +107,12 @@ class OpenAI(Provider):
|
|
92
107
|
decisions.append(None)
|
93
108
|
|
94
109
|
return {
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
"decision": self._from_singleton_list(decisions),
|
110
|
+
RESPONSE_MODEL: obj.get("model"),
|
111
|
+
INPUT_TOKEN_COUNT: obj.get("usage", {}).get("prompt_tokens"),
|
112
|
+
OUTPUT_TOKEN_COUNT: obj.get("usage", {}).get("completion_tokens"),
|
113
|
+
TOTAL_TOKEN_COUNT: obj.get("usage", {}).get("total_tokens"),
|
114
|
+
FINISH_REASONS: obj.get("finish_reason"),
|
115
|
+
# "decision": self._from_singleton_list(decisions),
|
101
116
|
}
|
102
117
|
|
103
118
|
def extract_llm_output(
|
@@ -115,10 +130,14 @@ class OpenAI(Provider):
|
|
115
130
|
self, func_args: list[Any], func_kwargs: dict[str, Any]
|
116
131
|
) -> dict[str, Any]:
|
117
132
|
return {
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
133
|
+
REQUEST_MODEL: func_kwargs.get("model"),
|
134
|
+
TEMPERATURE: func_kwargs.get("temperature"),
|
135
|
+
TOP_P: func_kwargs.get("top_p"),
|
136
|
+
FREQUENCY_PENALTY: func_kwargs.get("frequency_penalty"),
|
137
|
+
PRESENCE_PENALTY: func_kwargs.get("presence_penalty"),
|
138
|
+
STOP_SEQUENCES: func_kwargs.get("stop"),
|
139
|
+
MAX_TOKENS: func_kwargs.get("max_tokens"),
|
140
|
+
STREAM: func_kwargs.get("stream", False),
|
122
141
|
}
|
123
142
|
|
124
143
|
def _message_to_key_and_output(
|
@@ -10,6 +10,7 @@ from .utils import to_dict
|
|
10
10
|
class EvaluateEvent(pydantic.BaseModel):
|
11
11
|
name: str
|
12
12
|
data: str
|
13
|
+
timestamp: Optional[datetime.datetime] = None
|
13
14
|
|
14
15
|
|
15
16
|
class Span(pydantic.BaseModel):
|
@@ -62,6 +63,7 @@ class Span(pydantic.BaseModel):
|
|
62
63
|
def update(
|
63
64
|
self,
|
64
65
|
end_time: Optional[datetime.datetime],
|
66
|
+
input: Optional[Any] = None,
|
65
67
|
output: Optional[Any] = None,
|
66
68
|
metadata: Optional[dict[str, Any]] = None,
|
67
69
|
attributes: Optional[dict[str, Any]] = None,
|
@@ -69,6 +71,7 @@ class Span(pydantic.BaseModel):
|
|
69
71
|
override: bool = False,
|
70
72
|
):
|
71
73
|
self.endTime = end_time or datetime.datetime.now(datetime.timezone.utc)
|
74
|
+
self.input = input
|
72
75
|
self.output = output
|
73
76
|
new_metadata = (
|
74
77
|
metadata if override else {**(self.metadata or {}), **(metadata or {})}
|
@@ -111,8 +114,6 @@ class Trace(pydantic.BaseModel):
|
|
111
114
|
id: uuid.UUID
|
112
115
|
version: str = CURRENT_TRACING_VERSION
|
113
116
|
success: bool = True
|
114
|
-
startTime: Optional[datetime.datetime] = None
|
115
|
-
endTime: Optional[datetime.datetime] = None
|
116
117
|
userId: Optional[str] = None # provided by user or null
|
117
118
|
sessionId: Optional[str] = None # provided by user or uuid()
|
118
119
|
release: Optional[str] = None
|
@@ -121,8 +122,6 @@ class Trace(pydantic.BaseModel):
|
|
121
122
|
def __init__(
|
122
123
|
self,
|
123
124
|
success: bool = True,
|
124
|
-
start_time: Optional[datetime.datetime] = None,
|
125
|
-
end_time: Optional[datetime.datetime] = None,
|
126
125
|
id: Optional[uuid.UUID] = None,
|
127
126
|
user_id: Optional[str] = None,
|
128
127
|
session_id: Optional[str] = None,
|
@@ -132,9 +131,7 @@ class Trace(pydantic.BaseModel):
|
|
132
131
|
id_ = id or uuid.uuid4()
|
133
132
|
super().__init__(
|
134
133
|
id=id_,
|
135
|
-
startTime=start_time,
|
136
134
|
success=success,
|
137
|
-
endTime=end_time,
|
138
135
|
userId=user_id,
|
139
136
|
sessionId=session_id,
|
140
137
|
release=release,
|
File without changes
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# source: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-spans.md
|
2
|
+
# last updated: 2024-08-26
|
3
|
+
|
4
|
+
REQUEST_MODEL: str = "gen_ai.request.model"
|
5
|
+
RESPONSE_MODEL: str = "gen_ai.response.model"
|
6
|
+
PROVIDER: str = "gen_ai.system"
|
7
|
+
INPUT_TOKEN_COUNT: str = "gen_ai.usage.input_tokens"
|
8
|
+
OUTPUT_TOKEN_COUNT: str = "gen_ai.usage.output_tokens"
|
9
|
+
TOTAL_TOKEN_COUNT: str = "gen_ai.usage.total_tokens" # custom, not in the spec
|
10
|
+
# https://github.com/openlit/openlit/blob/main/sdk/python/src/openlit/semcov/__init__.py
|
11
|
+
COST: str = "gen_ai.usage.cost"
|
12
|
+
|
13
|
+
OPERATION: str = "gen_ai.operation.name"
|
14
|
+
|
15
|
+
FREQUENCY_PENALTY: str = "gen_ai.request.frequency_penalty"
|
16
|
+
TEMPERATURE: str = "gen_ai.request.temperature"
|
17
|
+
MAX_TOKENS: str = "gen_ai.request.max_tokens"
|
18
|
+
PRESENCE_PENALTY: str = "gen_ai.request.presence_penalty"
|
19
|
+
STOP_SEQUENCES: str = "gen_ai.request.stop_sequences"
|
20
|
+
TEMPERATURE: str = "gen_ai.request.temperature"
|
21
|
+
TOP_P: str = "gen_ai.request.top_p"
|
22
|
+
TOP_K: str = "gen_ai.request.top_k"
|
23
|
+
|
24
|
+
# https://github.com/openlit/openlit/blob/main/sdk/python/src/openlit/semcov/__init__.py
|
25
|
+
STREAM: str = "gen_ai.request.is_stream"
|
26
|
+
|
27
|
+
FINISH_REASONS = "gen_ai.response.finish_reasons"
|
28
|
+
|
29
|
+
__all__ = [
|
30
|
+
"REQUEST_MODEL",
|
31
|
+
"RESPONSE_MODEL",
|
32
|
+
"PROVIDER",
|
33
|
+
"INPUT_TOKEN_COUNT",
|
34
|
+
"OUTPUT_TOKEN_COUNT",
|
35
|
+
"TOTAL_TOKEN_COUNT",
|
36
|
+
"COST",
|
37
|
+
"OPERATION",
|
38
|
+
"FREQUENCY_PENALTY",
|
39
|
+
"TEMPERATURE",
|
40
|
+
"MAX_TOKENS",
|
41
|
+
"PRESENCE_PENALTY",
|
42
|
+
"STOP_SEQUENCES",
|
43
|
+
"TEMPERATURE",
|
44
|
+
"TOP_P",
|
45
|
+
"TOP_K",
|
46
|
+
"STREAM",
|
47
|
+
"FINISH_REASONS",
|
48
|
+
]
|
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
|