latitude-sdk 0.1.0b8__py3-none-any.whl → 1.0.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.
- latitude_sdk/client/payloads.py +3 -1
- latitude_sdk/sdk/evaluations.py +6 -5
- latitude_sdk/sdk/latitude.py +8 -4
- latitude_sdk/sdk/logs.py +10 -18
- latitude_sdk/sdk/prompts.py +204 -53
- latitude_sdk/sdk/types.py +42 -83
- latitude_sdk/util/utils.py +8 -6
- {latitude_sdk-0.1.0b8.dist-info → latitude_sdk-1.0.0.dist-info}/METADATA +3 -2
- {latitude_sdk-0.1.0b8.dist-info → latitude_sdk-1.0.0.dist-info}/RECORD +10 -10
- {latitude_sdk-0.1.0b8.dist-info → latitude_sdk-1.0.0.dist-info}/WHEEL +0 -0
latitude_sdk/client/payloads.py
CHANGED
latitude_sdk/sdk/evaluations.py
CHANGED
@@ -8,10 +8,7 @@ from latitude_sdk.client import (
|
|
8
8
|
TriggerEvaluationRequestBody,
|
9
9
|
TriggerEvaluationRequestParams,
|
10
10
|
)
|
11
|
-
from latitude_sdk.sdk.types import
|
12
|
-
EvaluationResult,
|
13
|
-
SdkOptions,
|
14
|
-
)
|
11
|
+
from latitude_sdk.sdk.types import EvaluationResult, SdkOptions
|
15
12
|
from latitude_sdk.util import Model
|
16
13
|
|
17
14
|
|
@@ -40,7 +37,9 @@ class Evaluations:
|
|
40
37
|
self._options = options
|
41
38
|
self._client = client
|
42
39
|
|
43
|
-
async def trigger(self, uuid: str, options: TriggerEvaluationOptions) -> TriggerEvaluationResult:
|
40
|
+
async def trigger(self, uuid: str, options: Optional[TriggerEvaluationOptions] = None) -> TriggerEvaluationResult:
|
41
|
+
options = TriggerEvaluationOptions(**{**dict(self._options), **dict(options or {})})
|
42
|
+
|
44
43
|
async with self._client.request(
|
45
44
|
handler=RequestHandler.TriggerEvaluation,
|
46
45
|
params=TriggerEvaluationRequestParams(
|
@@ -55,6 +54,8 @@ class Evaluations:
|
|
55
54
|
async def create_result(
|
56
55
|
self, uuid: str, evaluation_uuid: str, options: CreateEvaluationResultOptions
|
57
56
|
) -> CreateEvaluationResultResult:
|
57
|
+
options = CreateEvaluationResultOptions(**{**dict(self._options), **dict(options)})
|
58
|
+
|
58
59
|
async with self._client.request(
|
59
60
|
handler=RequestHandler.CreateEvaluationResult,
|
60
61
|
params=CreateEvaluationResultRequestParams(
|
latitude_sdk/sdk/latitude.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import Optional
|
2
2
|
|
3
3
|
from latitude_telemetry import Telemetry, TelemetryOptions
|
4
|
+
from promptl_ai import Promptl, PromptlOptions
|
4
5
|
|
5
6
|
from latitude_sdk.client import Client, ClientOptions, RouterOptions
|
6
7
|
from latitude_sdk.env import env
|
@@ -20,6 +21,7 @@ class InternalOptions(Model):
|
|
20
21
|
|
21
22
|
|
22
23
|
class LatitudeOptions(SdkOptions, Model):
|
24
|
+
promptl: Optional[PromptlOptions] = None
|
23
25
|
telemetry: Optional[TelemetryOptions] = None
|
24
26
|
internal: Optional[InternalOptions] = None
|
25
27
|
|
@@ -39,7 +41,7 @@ DEFAULT_INTERNAL_OPTIONS = InternalOptions(
|
|
39
41
|
|
40
42
|
|
41
43
|
DEFAULT_LATITUDE_OPTIONS = LatitudeOptions(
|
42
|
-
telemetry=None, #
|
44
|
+
telemetry=None, # NOTE: Telemetry is opt-in
|
43
45
|
internal=DEFAULT_INTERNAL_OPTIONS,
|
44
46
|
)
|
45
47
|
|
@@ -48,15 +50,16 @@ class Latitude:
|
|
48
50
|
_options: LatitudeOptions
|
49
51
|
_client: Client
|
50
52
|
|
53
|
+
promptl: Promptl
|
51
54
|
telemetry: Optional[Telemetry]
|
52
55
|
|
53
56
|
prompts: Prompts
|
54
57
|
logs: Logs
|
55
58
|
evaluations: Evaluations
|
56
59
|
|
57
|
-
def __init__(self, api_key: str, options: LatitudeOptions):
|
60
|
+
def __init__(self, api_key: str, options: Optional[LatitudeOptions] = None):
|
61
|
+
options = LatitudeOptions(**{**dict(DEFAULT_LATITUDE_OPTIONS), **dict(options or {})})
|
58
62
|
options.internal = InternalOptions(**{**dict(DEFAULT_INTERNAL_OPTIONS), **dict(options.internal or {})})
|
59
|
-
options = LatitudeOptions(**{**dict(DEFAULT_LATITUDE_OPTIONS), **dict(options)})
|
60
63
|
self._options = options
|
61
64
|
|
62
65
|
assert self._options.internal is not None
|
@@ -77,9 +80,10 @@ class Latitude:
|
|
77
80
|
)
|
78
81
|
)
|
79
82
|
|
83
|
+
self.promptl = Promptl(self._options.promptl)
|
80
84
|
if self._options.telemetry:
|
81
85
|
self.telemetry = Telemetry(api_key, self._options.telemetry)
|
82
86
|
|
83
|
-
self.prompts = Prompts(self._client, self._options)
|
87
|
+
self.prompts = Prompts(self._client, self.promptl, self._options)
|
84
88
|
self.logs = Logs(self._client, self._options)
|
85
89
|
self.evaluations = Evaluations(self._client, self._options)
|
latitude_sdk/sdk/logs.py
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Optional, Sequence
|
2
|
+
|
3
|
+
from promptl_ai import MessageLike
|
4
|
+
from promptl_ai.bindings.types import _Message
|
2
5
|
|
3
6
|
from latitude_sdk.client import Client, CreateLogRequestBody, CreateLogRequestParams, RequestHandler
|
4
7
|
from latitude_sdk.sdk.errors import ApiError, ApiErrorCodes
|
5
|
-
from latitude_sdk.sdk.types import
|
6
|
-
Log,
|
7
|
-
Message,
|
8
|
-
SdkOptions,
|
9
|
-
_Message,
|
10
|
-
)
|
8
|
+
from latitude_sdk.sdk.types import Log, SdkOptions
|
11
9
|
from latitude_sdk.util import Model
|
12
10
|
|
13
11
|
|
@@ -32,9 +30,8 @@ class Logs:
|
|
32
30
|
self._options = options
|
33
31
|
self._client = client
|
34
32
|
|
35
|
-
def
|
36
|
-
|
37
|
-
if not project_id:
|
33
|
+
def _ensure_log_options(self, options: LogOptions):
|
34
|
+
if not options.project_id:
|
38
35
|
raise ApiError(
|
39
36
|
status=404,
|
40
37
|
code=ApiErrorCodes.NotFoundError,
|
@@ -42,16 +39,11 @@ class Logs:
|
|
42
39
|
response="Project ID is required",
|
43
40
|
)
|
44
41
|
|
45
|
-
version_uuid = options.version_uuid or self._options.version_uuid
|
46
|
-
|
47
|
-
return LogOptions(project_id=project_id, version_uuid=version_uuid)
|
48
|
-
|
49
42
|
async def create(
|
50
|
-
self, path: str, messages: Sequence[
|
43
|
+
self, path: str, messages: Sequence[MessageLike], options: Optional[CreateLogOptions] = None
|
51
44
|
) -> CreateLogResult:
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
options = CreateLogOptions(**{**dict(self._options), **dict(options or {})})
|
46
|
+
self._ensure_log_options(options)
|
55
47
|
assert options.project_id is not None
|
56
48
|
|
57
49
|
messages = [_Message.validate_python(message) for message in messages]
|
latitude_sdk/sdk/prompts.py
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
import asyncio
|
1
2
|
from typing import Any, AsyncGenerator, Dict, List, Optional, Sequence, Union
|
2
3
|
|
4
|
+
from promptl_ai import Adapter, Message, MessageLike, Promptl, ToolMessage, ToolResultContent
|
5
|
+
from promptl_ai.bindings.types import _Message
|
6
|
+
|
3
7
|
from latitude_sdk.client import (
|
4
8
|
ChatPromptRequestBody,
|
5
9
|
ChatPromptRequestParams,
|
@@ -20,15 +24,37 @@ from latitude_sdk.sdk.types import (
|
|
20
24
|
ChainEventStep,
|
21
25
|
ChainEventStepCompleted,
|
22
26
|
FinishedEvent,
|
23
|
-
|
27
|
+
OnStep,
|
28
|
+
OnToolCall,
|
29
|
+
OnToolCallDetails,
|
24
30
|
Prompt,
|
31
|
+
Providers,
|
25
32
|
SdkOptions,
|
26
33
|
StreamCallbacks,
|
27
34
|
StreamEvents,
|
28
|
-
|
35
|
+
StreamTypes,
|
36
|
+
ToolResult,
|
29
37
|
)
|
30
38
|
from latitude_sdk.util import Model
|
31
39
|
|
40
|
+
_PROVIDER_TO_ADAPTER = {
|
41
|
+
Providers.OpenAI: Adapter.OpenAI,
|
42
|
+
Providers.Anthropic: Adapter.Anthropic,
|
43
|
+
}
|
44
|
+
|
45
|
+
_PROMPT_ATTR_TO_ADAPTER_ATTR = {
|
46
|
+
"maxTokens": ("max_tokens", [Adapter.OpenAI, Adapter.Anthropic]),
|
47
|
+
"topP": ("top_p", [Adapter.OpenAI, Adapter.Anthropic]),
|
48
|
+
"topK": ("top_k", [Adapter.OpenAI, Adapter.Anthropic]),
|
49
|
+
"presencePenalty": ("presence_penalty", [Adapter.OpenAI, Adapter.Anthropic]),
|
50
|
+
"stopSequences": ("stop_sequences", [Adapter.OpenAI, Adapter.Anthropic]),
|
51
|
+
"toolChoice": ("tool_choice", [Adapter.OpenAI, Adapter.Anthropic]),
|
52
|
+
}
|
53
|
+
|
54
|
+
|
55
|
+
class OnToolCallPaused(Exception):
|
56
|
+
pass
|
57
|
+
|
32
58
|
|
33
59
|
class PromptOptions(Model):
|
34
60
|
project_id: Optional[int] = None
|
@@ -54,6 +80,7 @@ class GetOrCreatePromptResult(Prompt, Model):
|
|
54
80
|
class RunPromptOptions(StreamCallbacks, PromptOptions, Model):
|
55
81
|
custom_identifier: Optional[str] = None
|
56
82
|
parameters: Optional[Dict[str, Any]] = None
|
83
|
+
tools: Optional[Dict[str, OnToolCall]] = None
|
57
84
|
stream: Optional[bool] = None
|
58
85
|
|
59
86
|
|
@@ -62,6 +89,7 @@ class RunPromptResult(FinishedEvent, Model):
|
|
62
89
|
|
63
90
|
|
64
91
|
class ChatPromptOptions(StreamCallbacks, Model):
|
92
|
+
tools: Optional[Dict[str, OnToolCall]] = None
|
65
93
|
stream: Optional[bool] = None
|
66
94
|
|
67
95
|
|
@@ -69,17 +97,37 @@ class ChatPromptResult(FinishedEvent, Model):
|
|
69
97
|
pass
|
70
98
|
|
71
99
|
|
100
|
+
class RenderPromptOptions(Model):
|
101
|
+
parameters: Optional[Dict[str, Any]] = None
|
102
|
+
adapter: Optional[Adapter] = None
|
103
|
+
|
104
|
+
|
105
|
+
class RenderPromptResult(Model):
|
106
|
+
messages: List[MessageLike]
|
107
|
+
config: Dict[str, Any]
|
108
|
+
|
109
|
+
|
110
|
+
class RenderChainOptions(Model):
|
111
|
+
parameters: Optional[Dict[str, Any]] = None
|
112
|
+
adapter: Optional[Adapter] = None
|
113
|
+
|
114
|
+
|
115
|
+
class RenderChainResult(RenderPromptResult, Model):
|
116
|
+
pass
|
117
|
+
|
118
|
+
|
72
119
|
class Prompts:
|
73
120
|
_options: SdkOptions
|
74
121
|
_client: Client
|
122
|
+
_promptl: Promptl
|
75
123
|
|
76
|
-
def __init__(self, client: Client, options: SdkOptions):
|
124
|
+
def __init__(self, client: Client, promptl: Promptl, options: SdkOptions):
|
77
125
|
self._options = options
|
78
126
|
self._client = client
|
127
|
+
self._promptl = promptl
|
79
128
|
|
80
|
-
def
|
81
|
-
|
82
|
-
if not project_id:
|
129
|
+
def _ensure_prompt_options(self, options: PromptOptions):
|
130
|
+
if not options.project_id:
|
83
131
|
raise ApiError(
|
84
132
|
status=404,
|
85
133
|
code=ApiErrorCodes.NotFoundError,
|
@@ -87,12 +135,8 @@ class Prompts:
|
|
87
135
|
response="Project ID is required",
|
88
136
|
)
|
89
137
|
|
90
|
-
version_uuid = options.version_uuid or self._options.version_uuid
|
91
|
-
|
92
|
-
return PromptOptions(project_id=project_id, version_uuid=version_uuid)
|
93
|
-
|
94
138
|
async def _handle_stream(
|
95
|
-
self, stream: AsyncGenerator[ClientEvent, Any],
|
139
|
+
self, stream: AsyncGenerator[ClientEvent, Any], on_event: Optional[StreamCallbacks.OnEvent]
|
96
140
|
) -> FinishedEvent:
|
97
141
|
uuid = None
|
98
142
|
conversation: List[Message] = []
|
@@ -146,8 +190,8 @@ class Prompts:
|
|
146
190
|
response=stream_event.data,
|
147
191
|
)
|
148
192
|
|
149
|
-
if
|
150
|
-
|
193
|
+
if on_event:
|
194
|
+
on_event(event)
|
151
195
|
|
152
196
|
if not uuid or not response:
|
153
197
|
raise ApiError(
|
@@ -160,10 +204,65 @@ class Prompts:
|
|
160
204
|
# NOTE: FinishedEvent not in on_event
|
161
205
|
return FinishedEvent(uuid=uuid, conversation=conversation, response=response)
|
162
206
|
|
163
|
-
|
164
|
-
|
165
|
-
|
207
|
+
def _pause_tool_execution(self) -> ToolResult:
|
208
|
+
raise OnToolCallPaused()
|
209
|
+
|
210
|
+
async def _handle_tool_calls(
|
211
|
+
self, result: FinishedEvent, options: Union[RunPromptOptions, ChatPromptOptions]
|
212
|
+
) -> Optional[FinishedEvent]:
|
213
|
+
# Seems Python cannot infer the type
|
214
|
+
assert result.response.type == StreamTypes.Text and result.response.tool_calls is not None
|
215
|
+
|
216
|
+
if not options.tools:
|
217
|
+
raise ApiError(
|
218
|
+
status=400,
|
219
|
+
code=ApiErrorCodes.AIRunError,
|
220
|
+
message="Tools not supplied",
|
221
|
+
response="Tools not supplied",
|
222
|
+
)
|
166
223
|
|
224
|
+
for tool_call in result.response.tool_calls:
|
225
|
+
if tool_call.name not in options.tools:
|
226
|
+
raise ApiError(
|
227
|
+
status=400,
|
228
|
+
code=ApiErrorCodes.AIRunError,
|
229
|
+
message=f"Tool {tool_call.name} not supplied",
|
230
|
+
response=f"Tool {tool_call.name} not supplied",
|
231
|
+
)
|
232
|
+
|
233
|
+
details = OnToolCallDetails(
|
234
|
+
conversation_uuid=result.uuid,
|
235
|
+
messages=result.conversation,
|
236
|
+
pause_execution=self._pause_tool_execution,
|
237
|
+
requested_tool_calls=result.response.tool_calls,
|
238
|
+
)
|
239
|
+
|
240
|
+
tool_results = await asyncio.gather(
|
241
|
+
*[options.tools[tool_call.name](tool_call, details) for tool_call in result.response.tool_calls],
|
242
|
+
return_exceptions=False,
|
243
|
+
)
|
244
|
+
|
245
|
+
tool_messages = [
|
246
|
+
ToolMessage(
|
247
|
+
content=[
|
248
|
+
ToolResultContent(
|
249
|
+
id=tool_result.id,
|
250
|
+
name=tool_result.name,
|
251
|
+
result=tool_result.result,
|
252
|
+
is_error=tool_result.is_error,
|
253
|
+
)
|
254
|
+
]
|
255
|
+
)
|
256
|
+
for tool_result in tool_results
|
257
|
+
]
|
258
|
+
|
259
|
+
next_result = await self.chat(result.uuid, tool_messages, ChatPromptOptions(**dict(options)))
|
260
|
+
|
261
|
+
return FinishedEvent(**dict(next_result)) if next_result else None
|
262
|
+
|
263
|
+
async def get(self, path: str, options: Optional[GetPromptOptions] = None) -> GetPromptResult:
|
264
|
+
options = GetPromptOptions(**{**dict(self._options), **dict(options or {})})
|
265
|
+
self._ensure_prompt_options(options)
|
167
266
|
assert options.project_id is not None
|
168
267
|
|
169
268
|
async with self._client.request(
|
@@ -176,10 +275,11 @@ class Prompts:
|
|
176
275
|
) as response:
|
177
276
|
return GetPromptResult.model_validate_json(response.content)
|
178
277
|
|
179
|
-
async def get_or_create(
|
180
|
-
|
181
|
-
|
182
|
-
|
278
|
+
async def get_or_create(
|
279
|
+
self, path: str, options: Optional[GetOrCreatePromptOptions] = None
|
280
|
+
) -> GetOrCreatePromptResult:
|
281
|
+
options = GetOrCreatePromptOptions(**{**dict(self._options), **dict(options or {})})
|
282
|
+
self._ensure_prompt_options(options)
|
183
283
|
assert options.project_id is not None
|
184
284
|
|
185
285
|
async with self._client.request(
|
@@ -195,13 +295,12 @@ class Prompts:
|
|
195
295
|
) as response:
|
196
296
|
return GetOrCreatePromptResult.model_validate_json(response.content)
|
197
297
|
|
198
|
-
async def run(self, path: str, options: RunPromptOptions) -> Optional[RunPromptResult]:
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
assert options.project_id is not None
|
298
|
+
async def run(self, path: str, options: Optional[RunPromptOptions] = None) -> Optional[RunPromptResult]:
|
299
|
+
options = RunPromptOptions(**{**dict(self._options), **dict(options or {})})
|
300
|
+
self._ensure_prompt_options(options)
|
301
|
+
assert options.project_id is not None
|
204
302
|
|
303
|
+
try:
|
205
304
|
async with self._client.request(
|
206
305
|
handler=RequestHandler.RunPrompt,
|
207
306
|
params=RunPromptRequestParams(
|
@@ -216,21 +315,22 @@ class Prompts:
|
|
216
315
|
),
|
217
316
|
) as response:
|
218
317
|
if options.stream:
|
219
|
-
result = await self._handle_stream(
|
220
|
-
response.sse(),
|
221
|
-
callbacks=StreamCallbacks(
|
222
|
-
on_event=options.on_event,
|
223
|
-
on_finished=options.on_finished,
|
224
|
-
on_error=options.on_error,
|
225
|
-
),
|
226
|
-
)
|
318
|
+
result = await self._handle_stream(response.sse(), options.on_event)
|
227
319
|
else:
|
228
320
|
result = RunPromptResult.model_validate_json(response.content)
|
229
321
|
|
230
|
-
|
231
|
-
|
322
|
+
if options.tools and result.response.type == StreamTypes.Text and result.response.tool_calls:
|
323
|
+
try:
|
324
|
+
# NOTE: The last sdk.chat called will already call on_finished
|
325
|
+
final_result = await self._handle_tool_calls(result, options)
|
326
|
+
return RunPromptResult(**dict(final_result)) if final_result else None
|
327
|
+
except OnToolCallPaused:
|
328
|
+
pass
|
329
|
+
|
330
|
+
if options.on_finished:
|
331
|
+
options.on_finished(FinishedEvent(**dict(result)))
|
232
332
|
|
233
|
-
|
333
|
+
return RunPromptResult(**dict(result))
|
234
334
|
|
235
335
|
except Exception as exception:
|
236
336
|
if not isinstance(exception, ApiError):
|
@@ -249,11 +349,13 @@ class Prompts:
|
|
249
349
|
return None
|
250
350
|
|
251
351
|
async def chat(
|
252
|
-
self, uuid: str, messages: Sequence[
|
352
|
+
self, uuid: str, messages: Sequence[MessageLike], options: Optional[ChatPromptOptions] = None
|
253
353
|
) -> Optional[ChatPromptResult]:
|
254
|
-
|
255
|
-
messages = [_Message.validate_python(message) for message in messages]
|
354
|
+
options = ChatPromptOptions(**{**dict(self._options), **dict(options or {})})
|
256
355
|
|
356
|
+
messages = [_Message.validate_python(message) for message in messages]
|
357
|
+
|
358
|
+
try:
|
257
359
|
async with self._client.request(
|
258
360
|
handler=RequestHandler.ChatPrompt,
|
259
361
|
params=ChatPromptRequestParams(
|
@@ -265,21 +367,22 @@ class Prompts:
|
|
265
367
|
),
|
266
368
|
) as response:
|
267
369
|
if options.stream:
|
268
|
-
result = await self._handle_stream(
|
269
|
-
response.sse(),
|
270
|
-
callbacks=StreamCallbacks(
|
271
|
-
on_event=options.on_event,
|
272
|
-
on_finished=options.on_finished,
|
273
|
-
on_error=options.on_error,
|
274
|
-
),
|
275
|
-
)
|
370
|
+
result = await self._handle_stream(response.sse(), options.on_event)
|
276
371
|
else:
|
277
372
|
result = ChatPromptResult.model_validate_json(response.content)
|
278
373
|
|
279
|
-
|
280
|
-
|
374
|
+
if options.tools and result.response.type == StreamTypes.Text and result.response.tool_calls:
|
375
|
+
try:
|
376
|
+
# NOTE: The last sdk.chat called will already call on_finished
|
377
|
+
final_result = await self._handle_tool_calls(result, options)
|
378
|
+
return ChatPromptResult(**dict(final_result)) if final_result else None
|
379
|
+
except OnToolCallPaused:
|
380
|
+
pass
|
281
381
|
|
282
|
-
|
382
|
+
if options.on_finished:
|
383
|
+
options.on_finished(FinishedEvent(**dict(result)))
|
384
|
+
|
385
|
+
return ChatPromptResult(**dict(result))
|
283
386
|
|
284
387
|
except Exception as exception:
|
285
388
|
if not isinstance(exception, ApiError):
|
@@ -297,6 +400,54 @@ class Prompts:
|
|
297
400
|
|
298
401
|
return None
|
299
402
|
|
300
|
-
|
403
|
+
def _adapt_prompt_config(self, config: Dict[str, Any], adapter: Adapter) -> Dict[str, Any]:
|
404
|
+
adapted_config: Dict[str, Any] = {}
|
405
|
+
|
406
|
+
for attr, value in config.items():
|
407
|
+
if attr in _PROMPT_ATTR_TO_ADAPTER_ATTR and adapter in _PROMPT_ATTR_TO_ADAPTER_ATTR[attr][1]:
|
408
|
+
adapted_config[_PROMPT_ATTR_TO_ADAPTER_ATTR[attr][0]] = value
|
409
|
+
else:
|
410
|
+
adapted_config[attr] = value
|
411
|
+
|
412
|
+
return adapted_config
|
413
|
+
|
414
|
+
async def render(self, prompt: str, options: Optional[RenderPromptOptions] = None) -> RenderPromptResult:
|
415
|
+
options = RenderPromptOptions(**{**dict(self._options), **dict(options or {})})
|
416
|
+
adapter = options.adapter or Adapter.OpenAI
|
417
|
+
|
418
|
+
result = self._promptl.prompts.render(
|
419
|
+
prompt=prompt,
|
420
|
+
parameters=options.parameters,
|
421
|
+
adapter=adapter,
|
422
|
+
)
|
423
|
+
|
424
|
+
return RenderPromptResult(
|
425
|
+
messages=result.messages,
|
426
|
+
config=self._adapt_prompt_config(result.config, adapter),
|
427
|
+
)
|
428
|
+
|
429
|
+
async def render_chain(
|
430
|
+
self, prompt: Prompt, on_step: OnStep, options: Optional[RenderChainOptions] = None
|
431
|
+
) -> RenderChainResult:
|
432
|
+
options = RenderChainOptions(**{**dict(self._options), **dict(options or {})})
|
433
|
+
adapter = options.adapter or _PROVIDER_TO_ADAPTER.get(prompt.provider or Providers.OpenAI, Adapter.OpenAI)
|
434
|
+
|
435
|
+
chain = self._promptl.chains.create(
|
436
|
+
prompt=prompt.content,
|
437
|
+
parameters=options.parameters,
|
438
|
+
adapter=adapter,
|
439
|
+
)
|
440
|
+
|
441
|
+
step = None
|
442
|
+
response = None
|
443
|
+
while not chain.completed:
|
444
|
+
step = chain.step(response)
|
445
|
+
if not step.completed:
|
446
|
+
response = await on_step(step.messages, self._adapt_prompt_config(step.config, adapter))
|
447
|
+
|
448
|
+
assert step is not None
|
301
449
|
|
302
|
-
|
450
|
+
return RenderChainResult(
|
451
|
+
messages=step.messages,
|
452
|
+
config=self._adapt_prompt_config(step.config, adapter),
|
453
|
+
)
|
latitude_sdk/sdk/types.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
from datetime import datetime
|
2
|
-
from typing import Any, Dict, List, Literal, Optional, Protocol, Union, runtime_checkable
|
2
|
+
from typing import Any, Callable, Dict, List, Literal, Optional, Protocol, Sequence, Union, runtime_checkable
|
3
|
+
|
4
|
+
from promptl_ai import Message, MessageLike
|
3
5
|
|
4
6
|
from latitude_sdk.sdk.errors import ApiError
|
5
|
-
from latitude_sdk.util import
|
7
|
+
from latitude_sdk.util import Field, Model, StrEnum
|
6
8
|
|
7
9
|
|
8
10
|
class DbErrorRef(Model):
|
@@ -28,99 +30,35 @@ class Prompt(Model):
|
|
28
30
|
provider: Optional[Providers] = None
|
29
31
|
|
30
32
|
|
31
|
-
class ContentType(StrEnum):
|
32
|
-
Text = "text"
|
33
|
-
Image = "image"
|
34
|
-
File = "file"
|
35
|
-
ToolCall = "tool-call"
|
36
|
-
ToolResult = "tool-result"
|
37
|
-
|
38
|
-
|
39
|
-
class TextContent(Model):
|
40
|
-
type: Literal[ContentType.Text] = ContentType.Text
|
41
|
-
text: str
|
42
|
-
|
43
|
-
|
44
|
-
class ImageContent(Model):
|
45
|
-
type: Literal[ContentType.Image] = ContentType.Image
|
46
|
-
image: str
|
47
|
-
|
48
|
-
|
49
|
-
class FileContent(Model):
|
50
|
-
type: Literal[ContentType.File] = ContentType.File
|
51
|
-
file: str
|
52
|
-
mime_type: str = Field(alias=str("mimeType"))
|
53
|
-
|
54
|
-
|
55
|
-
class ToolCallContent(Model):
|
56
|
-
type: Literal[ContentType.ToolCall] = ContentType.ToolCall
|
57
|
-
id: str = Field(alias=str("toolCallId"))
|
58
|
-
name: str = Field(alias=str("toolName"))
|
59
|
-
arguments: Dict[str, Any] = Field(alias=str("args"))
|
60
|
-
|
61
|
-
|
62
|
-
class ToolResultContent(Model):
|
63
|
-
type: Literal[ContentType.ToolResult] = ContentType.ToolResult
|
64
|
-
id: str = Field(alias=str("toolCallId"))
|
65
|
-
name: str = Field(alias=str("toolName"))
|
66
|
-
result: str
|
67
|
-
is_error: Optional[bool] = Field(default=None, alias=str("isError"))
|
68
|
-
|
69
|
-
|
70
|
-
MessageContent = Union[
|
71
|
-
str,
|
72
|
-
List[TextContent],
|
73
|
-
List[ImageContent],
|
74
|
-
List[FileContent],
|
75
|
-
List[ToolCallContent],
|
76
|
-
List[ToolResultContent],
|
77
|
-
]
|
78
|
-
|
79
|
-
|
80
|
-
class MessageRole(StrEnum):
|
81
|
-
System = "system"
|
82
|
-
User = "user"
|
83
|
-
Assistant = "assistant"
|
84
|
-
Tool = "tool"
|
85
|
-
|
86
|
-
|
87
|
-
class SystemMessage(Model):
|
88
|
-
role: Literal[MessageRole.System] = MessageRole.System
|
89
|
-
content: Union[str, List[TextContent]]
|
90
|
-
|
91
|
-
|
92
|
-
class UserMessage(Model):
|
93
|
-
role: Literal[MessageRole.User] = MessageRole.User
|
94
|
-
content: Union[str, List[Union[TextContent, ImageContent, FileContent]]]
|
95
|
-
name: Optional[str] = None
|
96
|
-
|
97
|
-
|
98
|
-
class AssistantMessage(Model):
|
99
|
-
role: Literal[MessageRole.Assistant] = MessageRole.Assistant
|
100
|
-
content: Union[str, List[Union[TextContent, ToolCallContent]]]
|
101
|
-
|
102
|
-
|
103
|
-
class ToolMessage(Model):
|
104
|
-
role: Literal[MessageRole.Tool] = MessageRole.Tool
|
105
|
-
content: List[ToolResultContent]
|
106
|
-
|
107
|
-
|
108
|
-
Message = Union[SystemMessage, UserMessage, AssistantMessage, ToolMessage]
|
109
|
-
_Message = Adapter(Message)
|
110
|
-
|
111
|
-
|
112
33
|
class ModelUsage(Model):
|
113
34
|
prompt_tokens: int = Field(alias=str("promptTokens"))
|
114
35
|
completion_tokens: int = Field(alias=str("completionTokens"))
|
115
36
|
total_tokens: int = Field(alias=str("totalTokens"))
|
116
37
|
|
117
38
|
|
39
|
+
class FinishReason(StrEnum):
|
40
|
+
Stop = "stop"
|
41
|
+
Length = "length"
|
42
|
+
ContentFilter = "content-filter"
|
43
|
+
ToolCalls = "tool-calls"
|
44
|
+
Error = "error"
|
45
|
+
Other = "other"
|
46
|
+
Unknown = "unknown"
|
47
|
+
|
48
|
+
|
118
49
|
class ToolCall(Model):
|
119
50
|
id: str
|
120
51
|
name: str
|
121
52
|
arguments: Dict[str, Any]
|
122
53
|
|
123
54
|
|
55
|
+
class ToolResult(Model):
|
56
|
+
id: str
|
57
|
+
name: str
|
58
|
+
result: Any
|
59
|
+
is_error: Optional[bool] = None
|
60
|
+
|
61
|
+
|
124
62
|
class StreamTypes(StrEnum):
|
125
63
|
Text = "text"
|
126
64
|
Object = "object"
|
@@ -184,6 +122,7 @@ class ChainEventCompleted(Model):
|
|
184
122
|
event: Literal[StreamEvents.Latitude] = StreamEvents.Latitude
|
185
123
|
type: Literal[ChainEvents.Completed] = ChainEvents.Completed
|
186
124
|
uuid: Optional[str] = None
|
125
|
+
finish_reason: FinishReason = Field(alias=str("finishReason"))
|
187
126
|
config: Dict[str, Any]
|
188
127
|
messages: Optional[List[Message]] = None
|
189
128
|
object: Optional[Any] = None
|
@@ -276,9 +215,29 @@ class StreamCallbacks(Model):
|
|
276
215
|
on_error: Optional[OnError] = None
|
277
216
|
|
278
217
|
|
218
|
+
class OnToolCallDetails(Model):
|
219
|
+
conversation_uuid: str
|
220
|
+
messages: List[Message]
|
221
|
+
pause_execution: Callable[[], ToolResult]
|
222
|
+
requested_tool_calls: List[ToolCall]
|
223
|
+
|
224
|
+
|
225
|
+
@runtime_checkable
|
226
|
+
class OnToolCall(Protocol):
|
227
|
+
async def __call__(self, call: ToolCall, details: OnToolCallDetails) -> ToolResult: ...
|
228
|
+
|
229
|
+
|
230
|
+
@runtime_checkable
|
231
|
+
class OnStep(Protocol):
|
232
|
+
async def __call__(
|
233
|
+
self, messages: List[MessageLike], config: Dict[str, Any]
|
234
|
+
) -> Union[str, MessageLike, Sequence[MessageLike]]: ...
|
235
|
+
|
236
|
+
|
279
237
|
class SdkOptions(Model):
|
280
238
|
project_id: Optional[int] = None
|
281
239
|
version_uuid: Optional[str] = None
|
240
|
+
tools: Optional[Dict[str, OnToolCall]] = None
|
282
241
|
|
283
242
|
|
284
243
|
class GatewayOptions(Model):
|
latitude_sdk/util/utils.py
CHANGED
@@ -64,6 +64,10 @@ class StrEnum(str, Enum):
|
|
64
64
|
Field = pydantic.Field
|
65
65
|
Config = pydantic.ConfigDict
|
66
66
|
Adapter = pydantic.TypeAdapter
|
67
|
+
Aliases = pydantic.AliasChoices
|
68
|
+
Validator = pydantic.WrapValidator
|
69
|
+
ValidatorInfo = pydantic.ValidationInfo
|
70
|
+
ValidatorHandler = pydantic.ValidatorFunctionWrapHandler
|
67
71
|
|
68
72
|
|
69
73
|
class Model(pydantic.BaseModel):
|
@@ -76,12 +80,12 @@ class Model(pydantic.BaseModel):
|
|
76
80
|
@is_like(pydantic.BaseModel.model_dump)
|
77
81
|
def model_dump(self, *args: Any, **kwargs: Any) -> Any:
|
78
82
|
exclude_none = kwargs.pop("exclude_none", True)
|
79
|
-
|
83
|
+
by_alias = kwargs.pop("by_alias", True)
|
84
|
+
return super().model_dump(*args, exclude_none=exclude_none, by_alias=by_alias, **kwargs)
|
80
85
|
|
81
86
|
@is_like(pydantic.BaseModel.dict) # pyright: ignore [reportDeprecated]
|
82
87
|
def dict(self, *args: Any, **kwargs: Any) -> Any:
|
83
|
-
|
84
|
-
return super().dict(*args, exclude_none=exclude_none, **kwargs) # pyright: ignore [reportDeprecated]
|
88
|
+
raise NotImplementedError("deprecated")
|
85
89
|
|
86
90
|
@is_like(pydantic.BaseModel.model_dump_json)
|
87
91
|
def model_dump_json(self, *args: Any, **kwargs: Any) -> Any:
|
@@ -91,6 +95,4 @@ class Model(pydantic.BaseModel):
|
|
91
95
|
|
92
96
|
@is_like(pydantic.BaseModel.json) # pyright: ignore [reportDeprecated]
|
93
97
|
def json(self, *args: Any, **kwargs: Any) -> Any:
|
94
|
-
|
95
|
-
by_alias = kwargs.pop("by_alias", True)
|
96
|
-
return super().json(*args, exclude_none=exclude_none, by_alias=by_alias, **kwargs) # pyright: ignore [reportDeprecated]
|
98
|
+
raise NotImplementedError("deprecated")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: latitude-sdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: Latitude SDK for Python
|
5
5
|
Project-URL: repository, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python
|
6
6
|
Project-URL: homepage, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python#readme
|
@@ -11,7 +11,8 @@ License-Expression: LGPL-3.0
|
|
11
11
|
Requires-Python: <3.13,>=3.9
|
12
12
|
Requires-Dist: httpx-sse>=0.4.0
|
13
13
|
Requires-Dist: httpx>=0.28.1
|
14
|
-
Requires-Dist: latitude-telemetry>=0.
|
14
|
+
Requires-Dist: latitude-telemetry>=1.0.0
|
15
|
+
Requires-Dist: promptl-ai>=1.0.1
|
15
16
|
Requires-Dist: pydantic>=2.10.3
|
16
17
|
Requires-Dist: typing-extensions>=4.12.2
|
17
18
|
Description-Content-Type: text/markdown
|
@@ -2,19 +2,19 @@ latitude_sdk/__init__.py,sha256=-AbNXLmzDZeGbRdDIOpNjdCbacOvLBflSJwQtLlZfgk,19
|
|
2
2
|
latitude_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
latitude_sdk/client/__init__.py,sha256=d8CnNB8UoGwcftiIeeC0twdg01qNvfpj-v7O40I7IiE,68
|
4
4
|
latitude_sdk/client/client.py,sha256=Oc4COkVFR1vFewVKZzUIvztJi_yTxeSMoyeML-ivVsY,4389
|
5
|
-
latitude_sdk/client/payloads.py,sha256=
|
5
|
+
latitude_sdk/client/payloads.py,sha256=odYmsrK5F_Ym9wTCbwuBuiBRBKiScdSnnRn40ywGR3E,2719
|
6
6
|
latitude_sdk/client/router.py,sha256=XYi24oUKL1wHDLvydpwLWCjy9tDGSVPN-zhetuvaj8I,4038
|
7
7
|
latitude_sdk/env/__init__.py,sha256=66of5veJ-u1aNI025L65Rrj321AjrYevMqomTMYIrPQ,19
|
8
8
|
latitude_sdk/env/env.py,sha256=MnXexPOHE6aXcAszrDCbW7hzACUv4YtU1bfxpYwvHNw,455
|
9
9
|
latitude_sdk/sdk/__init__.py,sha256=C9LlIjfnrS7KOK3-ruXKmbT77nSQMm23nZ6-t8sO8ME,137
|
10
10
|
latitude_sdk/sdk/errors.py,sha256=9GlGdDE8LGy3dE2Ry_BipBg-tDbQx7LWXJfSnTJSSBE,1747
|
11
|
-
latitude_sdk/sdk/evaluations.py,sha256=
|
12
|
-
latitude_sdk/sdk/latitude.py,sha256=
|
13
|
-
latitude_sdk/sdk/logs.py,sha256
|
14
|
-
latitude_sdk/sdk/prompts.py,sha256=
|
15
|
-
latitude_sdk/sdk/types.py,sha256=
|
11
|
+
latitude_sdk/sdk/evaluations.py,sha256=fDGtAWjdPG9OuKLit6u-jufVleC1EnshRplK6RN8iyg,2277
|
12
|
+
latitude_sdk/sdk/latitude.py,sha256=fdOWrAs0k3puLIhyU_zwvLsMyV_f1RxDWpV65_Euenc,2928
|
13
|
+
latitude_sdk/sdk/logs.py,sha256=CyHkRJvPl_p7wTSvR9bgxEI5akS0Tjc9FeQRb2C2vMg,1997
|
14
|
+
latitude_sdk/sdk/prompts.py,sha256=w3A-dhE_V1ueYjc8LBRXmQn5XZVCnsMdxs7Rau7P_E0,15818
|
15
|
+
latitude_sdk/sdk/types.py,sha256=D6wKkt5WSC0Ih6QT3QFSaU9YoxURKw8GKM7aH_uQ2-Q,6892
|
16
16
|
latitude_sdk/util/__init__.py,sha256=alIDGBnxWH4JvP-UW-7N99seBBi0r1GV1h8f1ERFBec,21
|
17
|
-
latitude_sdk/util/utils.py,sha256=
|
18
|
-
latitude_sdk-0.
|
19
|
-
latitude_sdk-0.
|
20
|
-
latitude_sdk-0.
|
17
|
+
latitude_sdk/util/utils.py,sha256=hMOmF-u1QaDgOwXN6ME6n4TaQ70yZKLvijDUqNCMwXI,2844
|
18
|
+
latitude_sdk-1.0.0.dist-info/METADATA,sha256=nK2qNPBfe6nAZLSVYkMfaU-ub9Y4flAUXyKc6OMRr2k,2060
|
19
|
+
latitude_sdk-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
20
|
+
latitude_sdk-1.0.0.dist-info/RECORD,,
|
File without changes
|