latitude-sdk 4.0.0b1__py3-none-any.whl → 5.0.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.
- latitude_sdk/__init__.py +6 -0
- latitude_sdk/client/client.py +59 -188
- latitude_sdk/client/payloads.py +3 -3
- latitude_sdk/sdk/__init__.py +1 -0
- latitude_sdk/sdk/errors.py +1 -0
- latitude_sdk/sdk/evaluations.py +5 -14
- latitude_sdk/sdk/latitude.py +1 -0
- latitude_sdk/sdk/projects.py +13 -19
- latitude_sdk/sdk/prompts.py +47 -83
- latitude_sdk/sdk/types.py +46 -13
- latitude_sdk/util/utils.py +5 -0
- latitude_sdk/version/__init__.py +1 -0
- latitude_sdk/version/version.py +63 -0
- {latitude_sdk-4.0.0b1.dist-info → latitude_sdk-5.0.0b1.dist-info}/METADATA +8 -8
- latitude_sdk-5.0.0b1.dist-info/RECORD +24 -0
- latitude_sdk-4.0.0b1.dist-info/RECORD +0 -22
- {latitude_sdk-4.0.0b1.dist-info → latitude_sdk-5.0.0b1.dist-info}/WHEEL +0 -0
- {latitude_sdk-4.0.0b1.dist-info → latitude_sdk-5.0.0b1.dist-info}/licenses/LICENSE.md +0 -0
latitude_sdk/__init__.py
CHANGED
latitude_sdk/client/client.py
CHANGED
@@ -1,27 +1,19 @@
|
|
1
1
|
import asyncio
|
2
2
|
import json
|
3
3
|
from contextlib import asynccontextmanager
|
4
|
-
from typing import Any, AsyncGenerator,
|
4
|
+
from typing import Any, AsyncGenerator, Optional
|
5
5
|
|
6
6
|
import httpx
|
7
7
|
import httpx_sse
|
8
8
|
|
9
|
-
from latitude_sdk.client.payloads import
|
10
|
-
ErrorResponse,
|
11
|
-
RequestBody,
|
12
|
-
RequestHandler,
|
13
|
-
RequestParams,
|
14
|
-
)
|
9
|
+
from latitude_sdk.client.payloads import ErrorResponse, RequestBody, RequestHandler, RequestParams
|
15
10
|
from latitude_sdk.client.router import Router, RouterOptions
|
16
|
-
from latitude_sdk.sdk.errors import
|
17
|
-
ApiError,
|
18
|
-
ApiErrorCodes,
|
19
|
-
ApiErrorDbRef,
|
20
|
-
)
|
11
|
+
from latitude_sdk.sdk.errors import ApiError, ApiErrorCodes, ApiErrorDbRef
|
21
12
|
from latitude_sdk.sdk.types import LogSources
|
22
13
|
from latitude_sdk.util import Model
|
14
|
+
from latitude_sdk.version import version
|
23
15
|
|
24
|
-
RETRIABLE_STATUSES = [408,
|
16
|
+
RETRIABLE_STATUSES = [408, 429, 500, 502, 503, 504]
|
25
17
|
|
26
18
|
ClientEvent = httpx_sse.ServerSentEvent
|
27
19
|
|
@@ -60,165 +52,68 @@ class Client:
|
|
60
52
|
handler: RequestHandler,
|
61
53
|
params: Optional[RequestParams] = None,
|
62
54
|
body: Optional[RequestBody] = None,
|
55
|
+
stream: Optional[bool] = None,
|
63
56
|
) -> AsyncGenerator[ClientResponse, Any]:
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
yield response
|
72
|
-
else:
|
73
|
-
async with self._non_streaming_request(handler, params, body) as response:
|
74
|
-
yield response
|
75
|
-
|
76
|
-
def _is_streaming_request(self, body: Optional[RequestBody]) -> bool:
|
77
|
-
"""Check if this is a streaming request based on the body."""
|
78
|
-
return body is not None and hasattr(body, "stream") and body.stream # type: ignore
|
79
|
-
|
80
|
-
def _prepare_headers(self, is_streaming: bool) -> dict[str, str]:
|
81
|
-
"""Prepare headers for the request."""
|
82
|
-
headers = {
|
83
|
-
"Authorization": f"Bearer {self.options.api_key}",
|
84
|
-
"Content-Type": "application/json",
|
85
|
-
}
|
86
|
-
|
87
|
-
if is_streaming:
|
88
|
-
headers["Accept"] = "text/event-stream"
|
89
|
-
|
90
|
-
return headers
|
91
|
-
|
92
|
-
def _prepare_content(self, body: Optional[RequestBody]) -> Optional[str]:
|
93
|
-
"""Prepare request content from body."""
|
94
|
-
if not body:
|
95
|
-
return None
|
96
|
-
|
97
|
-
return json.dumps(
|
98
|
-
{
|
99
|
-
**json.loads(body.model_dump_json()),
|
100
|
-
"__internal": {"source": self.options.source},
|
101
|
-
}
|
102
|
-
)
|
103
|
-
|
104
|
-
@asynccontextmanager
|
105
|
-
async def _streaming_request(
|
106
|
-
self,
|
107
|
-
handler: RequestHandler,
|
108
|
-
params: Optional[RequestParams] = None,
|
109
|
-
body: Optional[RequestBody] = None,
|
110
|
-
) -> AsyncGenerator[ClientResponse, Any]:
|
111
|
-
"""Handle streaming requests with proper resource management."""
|
112
|
-
headers = self._prepare_headers(is_streaming=True)
|
113
|
-
content = self._prepare_content(body)
|
114
|
-
method, url = self.router.resolve(handler, params)
|
115
|
-
|
116
|
-
async with httpx.AsyncClient(
|
117
|
-
headers=headers,
|
57
|
+
client = httpx.AsyncClient(
|
58
|
+
headers={
|
59
|
+
"Authorization": f"Bearer {self.options.api_key}",
|
60
|
+
"X-Latitude-SDK-Version": version.semver,
|
61
|
+
"Content-Type": "application/json",
|
62
|
+
"Accept": "text/event-stream" if stream else "application/json",
|
63
|
+
},
|
118
64
|
timeout=self.options.timeout,
|
119
65
|
follow_redirects=False,
|
120
66
|
max_redirects=0,
|
121
|
-
)
|
122
|
-
|
123
|
-
|
124
|
-
is_streaming=True,
|
125
|
-
)
|
126
|
-
|
127
|
-
# For streaming responses, yield the response directly
|
128
|
-
# The caller is responsible for consuming the stream
|
129
|
-
yield response
|
130
|
-
|
131
|
-
@asynccontextmanager
|
132
|
-
async def _non_streaming_request(
|
133
|
-
self,
|
134
|
-
handler: RequestHandler,
|
135
|
-
params: Optional[RequestParams] = None,
|
136
|
-
body: Optional[RequestBody] = None,
|
137
|
-
) -> AsyncGenerator[ClientResponse, Any]:
|
138
|
-
"""Handle non-streaming requests with proper resource management."""
|
139
|
-
headers = self._prepare_headers(is_streaming=False)
|
140
|
-
content = self._prepare_content(body)
|
141
|
-
method, url = self.router.resolve(handler, params)
|
142
|
-
|
143
|
-
async with httpx.AsyncClient(
|
144
|
-
headers=headers,
|
145
|
-
timeout=self.options.timeout,
|
146
|
-
follow_redirects=False,
|
147
|
-
max_redirects=0,
|
148
|
-
) as client:
|
149
|
-
response = await self._execute_with_retry(
|
150
|
-
lambda: client.request(method=method, url=url, content=content), # type: ignore
|
151
|
-
is_streaming=False,
|
152
|
-
)
|
153
|
-
|
154
|
-
try:
|
155
|
-
# Pre-read the response text for non-streaming responses
|
156
|
-
# This ensures the response is fully loaded and validates it
|
157
|
-
_ = response.text
|
158
|
-
yield response
|
159
|
-
finally:
|
160
|
-
await response.aclose()
|
161
|
-
|
162
|
-
async def _execute_with_retry(
|
163
|
-
self,
|
164
|
-
request_func: Callable[[], Awaitable[ClientResponse]],
|
165
|
-
is_streaming: bool = False,
|
166
|
-
) -> ClientResponse:
|
167
|
-
"""Execute a request with retry logic."""
|
168
|
-
last_exception = None
|
169
|
-
last_response = None
|
170
|
-
|
171
|
-
for attempt in range(1, self.options.retries + 1):
|
172
|
-
response_cm = None
|
173
|
-
response = None
|
174
|
-
|
175
|
-
try:
|
176
|
-
if is_streaming:
|
177
|
-
# For streaming, request_func returns a context manager
|
178
|
-
response_cm = request_func()
|
179
|
-
response = await response_cm.__aenter__() # type: ignore
|
180
|
-
# Store the context manager for proper cleanup
|
181
|
-
response._stream_cm = response_cm # pyright: ignore [reportAttributeAccessIssue]
|
182
|
-
else:
|
183
|
-
response = await request_func()
|
67
|
+
)
|
68
|
+
response = None
|
69
|
+
attempt = 1
|
184
70
|
|
185
|
-
|
186
|
-
|
71
|
+
try:
|
72
|
+
method, url = self.router.resolve(handler, params)
|
73
|
+
content = None
|
74
|
+
if body:
|
75
|
+
content = json.dumps(
|
76
|
+
{
|
77
|
+
**json.loads(body.model_dump_json()),
|
78
|
+
"__internal": {"source": self.options.source},
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
82
|
+
while attempt <= self.options.retries:
|
83
|
+
try:
|
84
|
+
request = client.build_request(method=method, url=url, content=content)
|
85
|
+
response = await client.send(request=request, stream=stream or False)
|
86
|
+
response.raise_for_status()
|
87
|
+
|
88
|
+
yield response # pyright: ignore [reportReturnType]
|
89
|
+
break
|
187
90
|
|
188
|
-
|
189
|
-
|
91
|
+
except Exception as exception:
|
92
|
+
if isinstance(exception, ApiError):
|
93
|
+
raise exception
|
190
94
|
|
191
|
-
|
192
|
-
|
193
|
-
last_response = exception.response
|
194
|
-
else:
|
195
|
-
last_response = getattr(exception, "response", None)
|
95
|
+
if attempt >= self.options.retries:
|
96
|
+
raise await self._exception(exception, response) from exception
|
196
97
|
|
197
|
-
|
198
|
-
|
199
|
-
|
98
|
+
if response and response.status_code in RETRIABLE_STATUSES:
|
99
|
+
await asyncio.sleep(self.options.delay * (2 ** (attempt - 1)))
|
100
|
+
else:
|
101
|
+
raise await self._exception(exception, response) from exception
|
200
102
|
|
201
|
-
|
202
|
-
|
203
|
-
|
103
|
+
finally:
|
104
|
+
if response:
|
105
|
+
await response.aclose()
|
204
106
|
|
205
|
-
|
206
|
-
if (
|
207
|
-
isinstance(exception, httpx.HTTPStatusError)
|
208
|
-
and exception.response.status_code in RETRIABLE_STATUSES
|
209
|
-
):
|
210
|
-
await asyncio.sleep(self._calculate_delay(attempt))
|
211
|
-
continue
|
107
|
+
attempt += 1
|
212
108
|
|
213
|
-
|
214
|
-
|
109
|
+
except Exception as exception:
|
110
|
+
if isinstance(exception, ApiError):
|
111
|
+
raise exception
|
215
112
|
|
216
|
-
|
217
|
-
raise await self._exception(last_exception, last_response) from last_exception # type: ignore
|
113
|
+
raise await self._exception(exception, response) from exception
|
218
114
|
|
219
|
-
|
220
|
-
|
221
|
-
return self.options.delay * (2 ** (attempt - 1))
|
115
|
+
finally:
|
116
|
+
await client.aclose()
|
222
117
|
|
223
118
|
async def _exception(self, exception: Exception, response: Optional[httpx.Response] = None) -> ApiError:
|
224
119
|
if not response:
|
@@ -229,41 +124,17 @@ class Client:
|
|
229
124
|
response=str(exception),
|
230
125
|
)
|
231
126
|
|
232
|
-
# Try to safely get response text and content, handling streaming responses
|
233
|
-
response_text = ""
|
234
|
-
response_content = b""
|
235
|
-
|
236
127
|
try:
|
237
|
-
|
238
|
-
|
239
|
-
is_streaming = "text/event-stream" in content_type
|
128
|
+
if not response.is_stream_consumed:
|
129
|
+
await response.aread()
|
240
130
|
|
241
|
-
|
242
|
-
if is_streaming:
|
243
|
-
# For streaming responses, we can access content but not text
|
244
|
-
response_content = response.content
|
245
|
-
response_text = ""
|
246
|
-
else:
|
247
|
-
# For non-streaming responses, we can safely access both
|
248
|
-
response_content = response.content
|
249
|
-
response_text = response.text
|
250
|
-
except Exception:
|
251
|
-
# If we can't read the response (e.g., streaming response that hasn't been read),
|
252
|
-
# try to get what we can
|
253
|
-
try:
|
254
|
-
response_content = response.content
|
255
|
-
except Exception:
|
256
|
-
response_content = b""
|
257
|
-
response_text = ""
|
258
|
-
|
259
|
-
try:
|
260
|
-
error = ErrorResponse.model_validate_json(response_content)
|
131
|
+
error = ErrorResponse.model_validate_json(response.content)
|
261
132
|
|
262
133
|
return ApiError(
|
263
134
|
status=response.status_code,
|
264
135
|
code=error.code,
|
265
136
|
message=error.message,
|
266
|
-
response=
|
137
|
+
response=response.text,
|
267
138
|
db_ref=ApiErrorDbRef(**dict(error.db_ref)) if error.db_ref else None,
|
268
139
|
)
|
269
140
|
|
@@ -272,5 +143,5 @@ class Client:
|
|
272
143
|
status=response.status_code,
|
273
144
|
code=ApiErrorCodes.InternalServerError,
|
274
145
|
message=str(exception),
|
275
|
-
response=
|
146
|
+
response=response.text,
|
276
147
|
)
|
latitude_sdk/client/payloads.py
CHANGED
@@ -44,8 +44,8 @@ class RunPromptRequestBody(Model):
|
|
44
44
|
path: str
|
45
45
|
parameters: Optional[Dict[str, Any]] = None
|
46
46
|
custom_identifier: Optional[str] = Field(default=None, alias=str("customIdentifier"))
|
47
|
-
stream: Optional[bool] = None
|
48
47
|
tools: Optional[List[str]] = None
|
48
|
+
stream: Optional[bool] = None
|
49
49
|
|
50
50
|
|
51
51
|
class ChatPromptRequestParams(Model):
|
@@ -54,6 +54,7 @@ class ChatPromptRequestParams(Model):
|
|
54
54
|
|
55
55
|
class ChatPromptRequestBody(Model):
|
56
56
|
messages: List[Message]
|
57
|
+
tools: Optional[List[str]] = None
|
57
58
|
stream: Optional[bool] = None
|
58
59
|
|
59
60
|
|
@@ -81,11 +82,10 @@ class AnnotateEvaluationRequestParams(EvaluationRequestParams, Model):
|
|
81
82
|
|
82
83
|
|
83
84
|
class AnnotateEvaluationRequestBody(Model):
|
84
|
-
score: int
|
85
|
-
|
86
85
|
class Metadata(Model):
|
87
86
|
reason: str
|
88
87
|
|
88
|
+
score: int
|
89
89
|
metadata: Optional[Metadata] = None
|
90
90
|
|
91
91
|
|
latitude_sdk/sdk/__init__.py
CHANGED
latitude_sdk/sdk/errors.py
CHANGED
latitude_sdk/sdk/evaluations.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
from
|
2
|
-
from typing import Any, Optional, Union
|
1
|
+
from typing import Optional
|
3
2
|
|
4
3
|
from latitude_sdk.client import (
|
5
4
|
AnnotateEvaluationRequestBody,
|
@@ -7,24 +6,16 @@ from latitude_sdk.client import (
|
|
7
6
|
Client,
|
8
7
|
RequestHandler,
|
9
8
|
)
|
10
|
-
from latitude_sdk.sdk.types import SdkOptions
|
11
|
-
from latitude_sdk.util import
|
9
|
+
from latitude_sdk.sdk.types import EvaluationResult, SdkOptions
|
10
|
+
from latitude_sdk.util import Model
|
12
11
|
|
13
12
|
|
14
13
|
class AnnotateEvaluationOptions(Model):
|
15
14
|
reason: str
|
16
15
|
|
17
16
|
|
18
|
-
class AnnotateEvaluationResult(Model):
|
19
|
-
|
20
|
-
score: int
|
21
|
-
normalized_score: int = Field(alias=str("normalizedScore"))
|
22
|
-
metadata: dict[str, Any]
|
23
|
-
has_passed: bool = Field(alias=str("hasPassed"))
|
24
|
-
created_at: datetime = Field(alias=str("createdAt"))
|
25
|
-
updated_at: datetime = Field(alias=str("updatedAt"))
|
26
|
-
version_uuid: str = Field(alias=str("versionUuid"))
|
27
|
-
error: Optional[Union[str, None]] = None
|
17
|
+
class AnnotateEvaluationResult(EvaluationResult, Model):
|
18
|
+
pass
|
28
19
|
|
29
20
|
|
30
21
|
class Evaluations:
|
latitude_sdk/sdk/latitude.py
CHANGED
latitude_sdk/sdk/projects.py
CHANGED
@@ -1,41 +1,35 @@
|
|
1
|
-
import json
|
2
1
|
from typing import List
|
3
2
|
|
4
|
-
from latitude_sdk.client import Client
|
5
|
-
from latitude_sdk.client.payloads import CreateProjectRequestBody, RequestHandler
|
3
|
+
from latitude_sdk.client import Client, CreateProjectRequestBody, RequestHandler
|
6
4
|
from latitude_sdk.sdk.types import Project, SdkOptions, Version
|
5
|
+
from latitude_sdk.util import Adapter as AdapterUtil
|
6
|
+
from latitude_sdk.util import Model
|
7
7
|
|
8
|
+
_GetAllProjectResults = AdapterUtil[List[Project]](List[Project])
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
class CreateProjectResult(Model):
|
12
|
+
project: Project
|
13
|
+
version: Version
|
13
14
|
|
14
15
|
|
15
16
|
class Projects:
|
16
|
-
_client: Client
|
17
17
|
_options: SdkOptions
|
18
|
+
_client: Client
|
18
19
|
|
19
20
|
def __init__(self, client: Client, options: SdkOptions):
|
20
|
-
self._client = client
|
21
21
|
self._options = options
|
22
|
+
self._client = client
|
22
23
|
|
23
24
|
async def get_all(self) -> List[Project]:
|
24
25
|
async with self._client.request(
|
25
26
|
handler=RequestHandler.GetAllProjects,
|
26
|
-
params=None,
|
27
27
|
) as response:
|
28
|
-
|
29
|
-
return [Project.model_validate_json(json.dumps(project)) for project in projects_data]
|
28
|
+
return _GetAllProjectResults.validate_json(response.content)
|
30
29
|
|
31
|
-
async def create(self, name: str) ->
|
30
|
+
async def create(self, name: str) -> CreateProjectResult:
|
32
31
|
async with self._client.request(
|
33
32
|
handler=RequestHandler.CreateProject,
|
34
|
-
params=None,
|
35
33
|
body=CreateProjectRequestBody(name=name),
|
36
34
|
) as response:
|
37
|
-
|
38
|
-
return CreateProjectResponse(
|
39
|
-
project=Project.model_validate_json(json.dumps(response_data["project"])),
|
40
|
-
version=Version.model_validate_json(json.dumps(response_data["version"])),
|
41
|
-
)
|
35
|
+
return CreateProjectResult.model_validate_json(response.content)
|
latitude_sdk/sdk/prompts.py
CHANGED
@@ -1,11 +1,6 @@
|
|
1
|
-
from typing import Any, AsyncGenerator,
|
1
|
+
from typing import Any, AsyncGenerator, List, Optional, Sequence
|
2
2
|
|
3
|
-
from promptl_ai import
|
4
|
-
Adapter,
|
5
|
-
Message,
|
6
|
-
MessageLike,
|
7
|
-
Promptl,
|
8
|
-
)
|
3
|
+
from promptl_ai import Adapter, Message, MessageLike, Promptl
|
9
4
|
from promptl_ai.bindings.types import _Message
|
10
5
|
|
11
6
|
from latitude_sdk.client import (
|
@@ -30,6 +25,8 @@ from latitude_sdk.sdk.types import (
|
|
30
25
|
OnToolCall,
|
31
26
|
OnToolCallDetails,
|
32
27
|
Prompt,
|
28
|
+
ProviderEvents,
|
29
|
+
ProviderEventToolCalled,
|
33
30
|
Providers,
|
34
31
|
SdkOptions,
|
35
32
|
StreamCallbacks,
|
@@ -145,7 +142,7 @@ class Prompts:
|
|
145
142
|
self,
|
146
143
|
stream: AsyncGenerator[ClientEvent, Any],
|
147
144
|
on_event: Optional[StreamCallbacks.OnEvent],
|
148
|
-
|
145
|
+
tools: Optional[dict[str, OnToolCall]],
|
149
146
|
) -> FinishedResult:
|
150
147
|
uuid = None
|
151
148
|
conversation: List[Message] = []
|
@@ -153,6 +150,7 @@ class Prompts:
|
|
153
150
|
|
154
151
|
async for stream_event in stream:
|
155
152
|
event = None
|
153
|
+
tool_call = None
|
156
154
|
|
157
155
|
if stream_event.event == str(StreamEvents.Latitude):
|
158
156
|
event = _LatitudeEvent.validate_json(stream_event.data)
|
@@ -174,9 +172,8 @@ class Prompts:
|
|
174
172
|
event = stream_event.json()
|
175
173
|
event["event"] = StreamEvents.Provider
|
176
174
|
|
177
|
-
|
178
|
-
|
179
|
-
await on_tool_call(event)
|
175
|
+
if event.get("type") == str(ProviderEvents.ToolCalled):
|
176
|
+
tool_call = ProviderEventToolCalled.model_validate_json(stream_event.data)
|
180
177
|
|
181
178
|
else:
|
182
179
|
raise ApiError(
|
@@ -189,6 +186,9 @@ class Prompts:
|
|
189
186
|
if on_event:
|
190
187
|
on_event(event)
|
191
188
|
|
189
|
+
if tool_call:
|
190
|
+
await self._handle_tool_call(tool_call, tools)
|
191
|
+
|
192
192
|
if not uuid or not response:
|
193
193
|
raise ApiError(
|
194
194
|
status=500,
|
@@ -197,11 +197,7 @@ class Prompts:
|
|
197
197
|
response="Stream ended without a chain-complete event. Missing uuid or response.",
|
198
198
|
)
|
199
199
|
|
200
|
-
return FinishedResult(
|
201
|
-
uuid=uuid,
|
202
|
-
conversation=conversation,
|
203
|
-
response=response,
|
204
|
-
)
|
200
|
+
return FinishedResult(uuid=uuid, conversation=conversation, response=response)
|
205
201
|
|
206
202
|
@staticmethod
|
207
203
|
async def _wrap_tool_handler(
|
@@ -212,66 +208,45 @@ class Prompts:
|
|
212
208
|
try:
|
213
209
|
result = await handler(arguments, details)
|
214
210
|
|
215
|
-
return ToolResult(**tool_result, result=result)
|
211
|
+
return ToolResult(**tool_result, result=result, is_error=False)
|
216
212
|
except Exception as exception:
|
217
213
|
return ToolResult(**tool_result, result=str(exception), is_error=True)
|
218
214
|
|
219
215
|
async def _handle_tool_call(
|
220
|
-
self,
|
221
|
-
event: dict[str, Any],
|
222
|
-
tools: dict[str, OnToolCall],
|
216
|
+
self, tool_call: ProviderEventToolCalled, tools: Optional[dict[str, OnToolCall]]
|
223
217
|
) -> None:
|
224
|
-
|
225
|
-
|
226
|
-
args: dict[str, Any] = event["args"]
|
227
|
-
|
228
|
-
tool = tools.get(toolName)
|
229
|
-
if not tool:
|
218
|
+
# NOTE: Do not handle tool calls if user specified no tools
|
219
|
+
if not tools:
|
230
220
|
return
|
231
221
|
|
222
|
+
tool_handler = tools.get(tool_call.name)
|
223
|
+
if not tool_handler:
|
224
|
+
raise ApiError(
|
225
|
+
status=400,
|
226
|
+
code=ApiErrorCodes.AIRunError,
|
227
|
+
message=f"Tool {tool_call.name} not supplied",
|
228
|
+
response=f"Tool {tool_call.name} not supplied",
|
229
|
+
)
|
230
|
+
|
232
231
|
tool_result = await self._wrap_tool_handler(
|
233
|
-
|
234
|
-
|
232
|
+
tool_handler,
|
233
|
+
tool_call.arguments,
|
235
234
|
OnToolCallDetails(
|
236
|
-
id=
|
237
|
-
name=
|
238
|
-
arguments=
|
235
|
+
id=tool_call.id,
|
236
|
+
name=tool_call.name,
|
237
|
+
arguments=tool_call.arguments,
|
239
238
|
),
|
240
239
|
)
|
241
240
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
) as _:
|
252
|
-
pass
|
253
|
-
except Exception as exception:
|
254
|
-
if not isinstance(exception, ApiError):
|
255
|
-
exception = ApiError(
|
256
|
-
status=500,
|
257
|
-
code=ApiErrorCodes.InternalServerError,
|
258
|
-
message=str(exception),
|
259
|
-
response=str(exception),
|
260
|
-
)
|
261
|
-
|
262
|
-
# Add context about which tool failed
|
263
|
-
message = f"Failed to execute tool {toolName}. \nLatitude API returned the following error:\
|
264
|
-
\n\n{exception.message}"
|
265
|
-
|
266
|
-
raise ApiError(
|
267
|
-
status=exception.status,
|
268
|
-
code=exception.code,
|
269
|
-
message=message,
|
270
|
-
response=exception.response,
|
271
|
-
) from exception
|
272
|
-
|
273
|
-
def _on_tool_call(self, tools: dict[str, OnToolCall]) -> Callable[[dict[str, Any]], Any]:
|
274
|
-
return lambda event: self._handle_tool_call(event, tools)
|
241
|
+
async with self._client.request(
|
242
|
+
handler=RequestHandler.ToolResults,
|
243
|
+
body=ToolResultsRequestBody(
|
244
|
+
tool_call_id=tool_call.id,
|
245
|
+
result=tool_result.result,
|
246
|
+
is_error=tool_result.is_error,
|
247
|
+
),
|
248
|
+
):
|
249
|
+
pass
|
275
250
|
|
276
251
|
async def get(self, path: str, options: Optional[GetPromptOptions] = None) -> GetPromptResult:
|
277
252
|
options = GetPromptOptions(**{**dict(self._options), **dict(options or {})})
|
@@ -338,16 +313,13 @@ class Prompts:
|
|
338
313
|
path=path,
|
339
314
|
parameters=options.parameters,
|
340
315
|
custom_identifier=options.custom_identifier,
|
341
|
-
stream=options.stream,
|
342
316
|
tools=list(options.tools.keys()) if options.tools and options.stream else None,
|
317
|
+
stream=options.stream,
|
343
318
|
),
|
319
|
+
stream=options.stream,
|
344
320
|
) as response:
|
345
321
|
if options.stream:
|
346
|
-
result = await self._handle_stream(
|
347
|
-
response.sse(),
|
348
|
-
options.on_event,
|
349
|
-
self._on_tool_call(options.tools if options.tools else {}),
|
350
|
-
)
|
322
|
+
result = await self._handle_stream(response.sse(), options.on_event, options.tools)
|
351
323
|
else:
|
352
324
|
result = RunPromptResult.model_validate_json(response.content)
|
353
325
|
|
@@ -373,10 +345,7 @@ class Prompts:
|
|
373
345
|
return None
|
374
346
|
|
375
347
|
async def chat(
|
376
|
-
self,
|
377
|
-
uuid: str,
|
378
|
-
messages: Sequence[MessageLike],
|
379
|
-
options: Optional[ChatPromptOptions] = None,
|
348
|
+
self, uuid: str, messages: Sequence[MessageLike], options: Optional[ChatPromptOptions] = None
|
380
349
|
) -> Optional[ChatPromptResult]:
|
381
350
|
options = ChatPromptOptions(**{**dict(self._options), **dict(options or {})})
|
382
351
|
|
@@ -390,15 +359,13 @@ class Prompts:
|
|
390
359
|
),
|
391
360
|
body=ChatPromptRequestBody(
|
392
361
|
messages=messages,
|
362
|
+
tools=list(options.tools.keys()) if options.tools and options.stream else None,
|
393
363
|
stream=options.stream,
|
394
364
|
),
|
365
|
+
stream=options.stream,
|
395
366
|
) as response:
|
396
367
|
if options.stream:
|
397
|
-
result = await self._handle_stream(
|
398
|
-
response.sse(),
|
399
|
-
options.on_event,
|
400
|
-
self._on_tool_call(options.tools if options.tools else {}),
|
401
|
-
)
|
368
|
+
result = await self._handle_stream(response.sse(), options.on_event, options.tools)
|
402
369
|
else:
|
403
370
|
result = ChatPromptResult.model_validate_json(response.content)
|
404
371
|
|
@@ -451,10 +418,7 @@ class Prompts:
|
|
451
418
|
)
|
452
419
|
|
453
420
|
async def render_chain(
|
454
|
-
self,
|
455
|
-
prompt: Prompt,
|
456
|
-
on_step: OnStep,
|
457
|
-
options: Optional[RenderChainOptions] = None,
|
421
|
+
self, prompt: Prompt, on_step: OnStep, options: Optional[RenderChainOptions] = None
|
458
422
|
) -> RenderChainResult:
|
459
423
|
options = RenderChainOptions(**{**dict(self._options), **dict(options or {})})
|
460
424
|
adapter = options.adapter or _PROVIDER_TO_ADAPTER.get(prompt.provider or Providers.OpenAI, Adapter.OpenAI)
|
latitude_sdk/sdk/types.py
CHANGED
@@ -30,6 +30,10 @@ class Providers(StrEnum):
|
|
30
30
|
Google = "google"
|
31
31
|
GoogleVertex = "google_vertex"
|
32
32
|
AnthropicVertex = "anthropic_vertex"
|
33
|
+
XAI = "xai"
|
34
|
+
AmazonBedrock = "amazon_bedrock"
|
35
|
+
DeepSeek = "deepseek"
|
36
|
+
Perplexity = "perplexity"
|
33
37
|
Custom = "custom"
|
34
38
|
|
35
39
|
|
@@ -68,9 +72,6 @@ class FinishReason(StrEnum):
|
|
68
72
|
Unknown = "unknown"
|
69
73
|
|
70
74
|
|
71
|
-
AGENT_START_TOOL_NAME = "start_autonomous_chain"
|
72
|
-
|
73
|
-
|
74
75
|
class ToolCall(Model):
|
75
76
|
id: str
|
76
77
|
name: str
|
@@ -116,6 +117,23 @@ class StreamEvents(StrEnum):
|
|
116
117
|
Provider = "provider-event"
|
117
118
|
|
118
119
|
|
120
|
+
# NOTE: Incomplete list
|
121
|
+
class ProviderEvents(StrEnum):
|
122
|
+
ToolCalled = "tool-call"
|
123
|
+
|
124
|
+
|
125
|
+
# NOTE: Incomplete event
|
126
|
+
class GenericProviderEvent(Model):
|
127
|
+
event: Literal[StreamEvents.Provider] = StreamEvents.Provider
|
128
|
+
|
129
|
+
|
130
|
+
# NOTE: Incomplete event
|
131
|
+
class ProviderEventToolCalled(GenericProviderEvent, Model):
|
132
|
+
id: str = Field(alias=str("toolCallId"))
|
133
|
+
name: str = Field(alias=str("toolName"))
|
134
|
+
arguments: dict[str, Any] = Field(alias=str("args"))
|
135
|
+
|
136
|
+
|
119
137
|
ProviderEvent = dict[str, Any]
|
120
138
|
|
121
139
|
|
@@ -137,20 +155,20 @@ class GenericChainEvent(Model):
|
|
137
155
|
uuid: str
|
138
156
|
|
139
157
|
|
140
|
-
class ChainEventChainStarted(GenericChainEvent):
|
158
|
+
class ChainEventChainStarted(GenericChainEvent, Model):
|
141
159
|
type: Literal[ChainEvents.ChainStarted] = ChainEvents.ChainStarted
|
142
160
|
|
143
161
|
|
144
|
-
class ChainEventStepStarted(GenericChainEvent):
|
162
|
+
class ChainEventStepStarted(GenericChainEvent, Model):
|
145
163
|
type: Literal[ChainEvents.StepStarted] = ChainEvents.StepStarted
|
146
164
|
|
147
165
|
|
148
|
-
class ChainEventProviderStarted(GenericChainEvent):
|
166
|
+
class ChainEventProviderStarted(GenericChainEvent, Model):
|
149
167
|
type: Literal[ChainEvents.ProviderStarted] = ChainEvents.ProviderStarted
|
150
168
|
config: dict[str, Any]
|
151
169
|
|
152
170
|
|
153
|
-
class ChainEventProviderCompleted(GenericChainEvent):
|
171
|
+
class ChainEventProviderCompleted(GenericChainEvent, Model):
|
154
172
|
type: Literal[ChainEvents.ProviderCompleted] = ChainEvents.ProviderCompleted
|
155
173
|
provider_log_uuid: str = Field(alias=str("providerLogUuid"))
|
156
174
|
token_usage: ModelUsage = Field(alias=str("tokenUsage"))
|
@@ -158,26 +176,26 @@ class ChainEventProviderCompleted(GenericChainEvent):
|
|
158
176
|
response: ChainResponse
|
159
177
|
|
160
178
|
|
161
|
-
class ChainEventToolsStarted(GenericChainEvent):
|
179
|
+
class ChainEventToolsStarted(GenericChainEvent, Model):
|
162
180
|
type: Literal[ChainEvents.ToolsStarted] = ChainEvents.ToolsStarted
|
163
181
|
tools: List[ToolCall]
|
164
182
|
|
165
183
|
|
166
|
-
class ChainEventToolCompleted(GenericChainEvent):
|
184
|
+
class ChainEventToolCompleted(GenericChainEvent, Model):
|
167
185
|
type: Literal[ChainEvents.ToolCompleted] = ChainEvents.ToolCompleted
|
168
186
|
|
169
187
|
|
170
|
-
class ChainEventStepCompleted(GenericChainEvent):
|
188
|
+
class ChainEventStepCompleted(GenericChainEvent, Model):
|
171
189
|
type: Literal[ChainEvents.StepCompleted] = ChainEvents.StepCompleted
|
172
190
|
|
173
191
|
|
174
|
-
class ChainEventChainCompleted(GenericChainEvent):
|
192
|
+
class ChainEventChainCompleted(GenericChainEvent, Model):
|
175
193
|
type: Literal[ChainEvents.ChainCompleted] = ChainEvents.ChainCompleted
|
176
194
|
token_usage: ModelUsage = Field(alias=str("tokenUsage"))
|
177
195
|
finish_reason: FinishReason = Field(alias=str("finishReason"))
|
178
196
|
|
179
197
|
|
180
|
-
class ChainEventChainError(GenericChainEvent):
|
198
|
+
class ChainEventChainError(GenericChainEvent, Model):
|
181
199
|
type: Literal[ChainEvents.ChainError] = ChainEvents.ChainError
|
182
200
|
error: ChainError
|
183
201
|
|
@@ -229,6 +247,18 @@ class Log(Model):
|
|
229
247
|
updated_at: datetime = Field(alias=str("updatedAt"))
|
230
248
|
|
231
249
|
|
250
|
+
class EvaluationResult(Model):
|
251
|
+
uuid: str
|
252
|
+
version_uuid: str = Field(alias=str("versionUuid"))
|
253
|
+
score: int
|
254
|
+
normalized_score: int = Field(alias=str("normalizedScore"))
|
255
|
+
metadata: dict[str, Any]
|
256
|
+
has_passed: bool = Field(alias=str("hasPassed"))
|
257
|
+
error: Optional[Union[str, None]] = None
|
258
|
+
created_at: datetime = Field(alias=str("createdAt"))
|
259
|
+
updated_at: datetime = Field(alias=str("updatedAt"))
|
260
|
+
|
261
|
+
|
232
262
|
class Project(Model):
|
233
263
|
id: int
|
234
264
|
uuid: Optional[str] = None
|
@@ -238,11 +268,14 @@ class Project(Model):
|
|
238
268
|
|
239
269
|
|
240
270
|
class Version(Model):
|
271
|
+
id: int
|
241
272
|
uuid: str
|
242
|
-
|
273
|
+
title: str
|
274
|
+
description: Optional[str] = None
|
243
275
|
project_id: int = Field(alias=str("projectId"))
|
244
276
|
created_at: datetime = Field(alias=str("createdAt"))
|
245
277
|
updated_at: datetime = Field(alias=str("updatedAt"))
|
278
|
+
merged_at: Optional[datetime] = Field(default=None, alias=str("mergedAt"))
|
246
279
|
|
247
280
|
|
248
281
|
class StreamCallbacks(Model):
|
latitude_sdk/util/utils.py
CHANGED
@@ -5,6 +5,11 @@ from typing import Any, Callable, List, TypeVar
|
|
5
5
|
import pydantic
|
6
6
|
from typing_extensions import ParamSpec, Self
|
7
7
|
|
8
|
+
|
9
|
+
def get_package() -> str:
|
10
|
+
return (__package__ or __name__).split(".")[0].replace("_", "-")
|
11
|
+
|
12
|
+
|
8
13
|
T = TypeVar("T", str, bool, int, List[str])
|
9
14
|
|
10
15
|
|
@@ -0,0 +1 @@
|
|
1
|
+
from .version import *
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import re
|
2
|
+
from importlib.metadata import version as get_version
|
3
|
+
from typing import Union
|
4
|
+
|
5
|
+
from latitude_sdk.util import Model, get_package
|
6
|
+
|
7
|
+
_PEP440_PATTERN = re.compile(
|
8
|
+
r"^(\d+(?:\.\d+){0,2})" # major.minor.patch
|
9
|
+
r"(?:([abcu]|rc|u)(\d+))?" # prerelease+number
|
10
|
+
r"(?:\.dev(\d+))?$" # .dev+number
|
11
|
+
)
|
12
|
+
|
13
|
+
_PEP440_PRERELEASES = {
|
14
|
+
"a": "alpha",
|
15
|
+
"b": "beta",
|
16
|
+
"u": "unknown",
|
17
|
+
"rc": "rc",
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
def _to_semver(pepver: str) -> str:
|
22
|
+
version = _PEP440_PATTERN.match(pepver)
|
23
|
+
if not version:
|
24
|
+
return pepver
|
25
|
+
|
26
|
+
release, prerelease, prenumber, devnumber = version.groups()
|
27
|
+
|
28
|
+
parts = release.split(".")
|
29
|
+
major = parts[0] if len(parts) > 0 else "0"
|
30
|
+
minor = parts[1] if len(parts) > 1 else "0"
|
31
|
+
patch = parts[2] if len(parts) > 2 else "0"
|
32
|
+
|
33
|
+
semver = f"{major}.{minor}.{patch}"
|
34
|
+
if prerelease:
|
35
|
+
semver += f"-{_PEP440_PRERELEASES.get(prerelease, 'unknown')}"
|
36
|
+
if prenumber:
|
37
|
+
semver += f".{prenumber}"
|
38
|
+
if devnumber:
|
39
|
+
semver += f"+dev.{devnumber}"
|
40
|
+
|
41
|
+
return semver
|
42
|
+
|
43
|
+
|
44
|
+
def _to_info(pepver: str) -> tuple[Union[int, str], ...]:
|
45
|
+
return tuple(int(x) if x.isdigit() else x for x in pepver.split("."))
|
46
|
+
|
47
|
+
|
48
|
+
class Version(Model):
|
49
|
+
pep440: str
|
50
|
+
semver: str
|
51
|
+
info: tuple[Union[int, str], ...]
|
52
|
+
|
53
|
+
|
54
|
+
try:
|
55
|
+
_version = get_version(get_package())
|
56
|
+
except Exception:
|
57
|
+
_version = "0.0.0u0"
|
58
|
+
|
59
|
+
version = Version(
|
60
|
+
pep440=_version,
|
61
|
+
semver=_to_semver(_version),
|
62
|
+
info=_to_info(_version),
|
63
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: latitude-sdk
|
3
|
-
Version:
|
3
|
+
Version: 5.0.0b1
|
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
|
@@ -62,7 +62,13 @@ Requires uv `0.5.10` or higher.
|
|
62
62
|
|
63
63
|
### Running only a specific test
|
64
64
|
|
65
|
-
|
65
|
+
Specify the test inline:
|
66
|
+
|
67
|
+
```python
|
68
|
+
uv run scripts/test.py <test_path>::<test_case>::<test_name>
|
69
|
+
```
|
70
|
+
|
71
|
+
Or mark the test with an `only` marker:
|
66
72
|
|
67
73
|
```python
|
68
74
|
import pytest
|
@@ -78,12 +84,6 @@ async def my_test(self):
|
|
78
84
|
uv run scripts/test.py -m only
|
79
85
|
```
|
80
86
|
|
81
|
-
Another way is to specify the test in line:
|
82
|
-
|
83
|
-
```python
|
84
|
-
uv run scripts/test.py <test_path>::<test_case>::<test_name>
|
85
|
-
```
|
86
|
-
|
87
87
|
## License
|
88
88
|
|
89
89
|
The SDK is licensed under the [MIT License](https://opensource.org/licenses/MIT) - read the [LICENSE](/LICENSE) file for details.
|
@@ -0,0 +1,24 @@
|
|
1
|
+
latitude_sdk/__init__.py,sha256=Xp6tiHlLtEQxfii5F_gTya5jZdbD63ySlnPI4pJU-58,147
|
2
|
+
latitude_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
latitude_sdk/client/__init__.py,sha256=d8CnNB8UoGwcftiIeeC0twdg01qNvfpj-v7O40I7IiE,68
|
4
|
+
latitude_sdk/client/client.py,sha256=lidk9T5aeocg_rpihl_FUnYTK2kZDtsCFa8IrCCM3tE,4815
|
5
|
+
latitude_sdk/client/payloads.py,sha256=fJftAEQpbNef8cknAZpt_07Ye8n_9EWRuhfoI-DrZvs,3035
|
6
|
+
latitude_sdk/client/router.py,sha256=k5OT42_3riJlTm5Q2BU3AplmpN1FdMxQeIj8GptcllQ,4616
|
7
|
+
latitude_sdk/env/__init__.py,sha256=66of5veJ-u1aNI025L65Rrj321AjrYevMqomTMYIrPQ,19
|
8
|
+
latitude_sdk/env/env.py,sha256=MnXexPOHE6aXcAszrDCbW7hzACUv4YtU1bfxpYwvHNw,455
|
9
|
+
latitude_sdk/sdk/__init__.py,sha256=UDgjaJ5vTIqv-DPYt4Dtav_7zWUYKcPn3T34oAP2Tps,161
|
10
|
+
latitude_sdk/sdk/errors.py,sha256=WNdieePfS6KRiOXUXlh3ryko_Uw_jRjZJg2XZamLYNg,1787
|
11
|
+
latitude_sdk/sdk/evaluations.py,sha256=mk_mvS8T4oymvMuKt_lAB1H6RCMfzNDcRT0mTKUYWu4,1451
|
12
|
+
latitude_sdk/sdk/latitude.py,sha256=0FjPKe3ZAl8YsOkTL77NKPFBoHrMFyg6SfQmKW-8h6E,2703
|
13
|
+
latitude_sdk/sdk/logs.py,sha256=CyHkRJvPl_p7wTSvR9bgxEI5akS0Tjc9FeQRb2C2vMg,1997
|
14
|
+
latitude_sdk/sdk/projects.py,sha256=C1f8eQ6Sz1plCvL-l0XDKorCTtz_r13eyG_rIYv29ew,1134
|
15
|
+
latitude_sdk/sdk/prompts.py,sha256=1tUNbuqRfZs963x5IOygGU36No-5nJiK3hIBzLEQ1sE,15156
|
16
|
+
latitude_sdk/sdk/types.py,sha256=MWJS5rcotbBQjIucvHBlreALMde6_NxJGP3iq5x9eK0,8603
|
17
|
+
latitude_sdk/util/__init__.py,sha256=alIDGBnxWH4JvP-UW-7N99seBBi0r1GV1h8f1ERFBec,21
|
18
|
+
latitude_sdk/util/utils.py,sha256=kbYWGMIMBsEV-u5woUgW77C-1mSUXyAo6VLuThQ_cj0,2942
|
19
|
+
latitude_sdk/version/__init__.py,sha256=av5cneIPX5Jet6040Rlaf9ODQFSRVirka1iagoQJYvA,23
|
20
|
+
latitude_sdk/version/version.py,sha256=J-Hw4S7ezvrqUHHbmTboBGNmo4l1LwGcbN6MiZX1Krc,1467
|
21
|
+
latitude_sdk-5.0.0b1.dist-info/METADATA,sha256=zCmM2wU49INrMNDRKe-9slQO9800TIxZCJMe3mMg4sI,2356
|
22
|
+
latitude_sdk-5.0.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
23
|
+
latitude_sdk-5.0.0b1.dist-info/licenses/LICENSE.md,sha256=yFReu_tr5pjxslWkQREfxA9yVm2r6gay2s6SFCh0XfQ,1073
|
24
|
+
latitude_sdk-5.0.0b1.dist-info/RECORD,,
|
@@ -1,22 +0,0 @@
|
|
1
|
-
latitude_sdk/__init__.py,sha256=-AbNXLmzDZeGbRdDIOpNjdCbacOvLBflSJwQtLlZfgk,19
|
2
|
-
latitude_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
latitude_sdk/client/__init__.py,sha256=d8CnNB8UoGwcftiIeeC0twdg01qNvfpj-v7O40I7IiE,68
|
4
|
-
latitude_sdk/client/client.py,sha256=PR89Qy0hL0GQEEKs4eys7wc3BhIPF_P9N-AJB6ufrV8,9676
|
5
|
-
latitude_sdk/client/payloads.py,sha256=z0tLYE9clybYaFWHgMVJgj9w-6exUQgfdB8egLw1-5E,2998
|
6
|
-
latitude_sdk/client/router.py,sha256=k5OT42_3riJlTm5Q2BU3AplmpN1FdMxQeIj8GptcllQ,4616
|
7
|
-
latitude_sdk/env/__init__.py,sha256=66of5veJ-u1aNI025L65Rrj321AjrYevMqomTMYIrPQ,19
|
8
|
-
latitude_sdk/env/env.py,sha256=MnXexPOHE6aXcAszrDCbW7hzACUv4YtU1bfxpYwvHNw,455
|
9
|
-
latitude_sdk/sdk/__init__.py,sha256=C9LlIjfnrS7KOK3-ruXKmbT77nSQMm23nZ6-t8sO8ME,137
|
10
|
-
latitude_sdk/sdk/errors.py,sha256=9GlGdDE8LGy3dE2Ry_BipBg-tDbQx7LWXJfSnTJSSBE,1747
|
11
|
-
latitude_sdk/sdk/evaluations.py,sha256=UP0DKMOLbqcCYrJcxiqgsqUM3anGs8pkAZu1DoOSwxI,1845
|
12
|
-
latitude_sdk/sdk/latitude.py,sha256=-569RfclTdfjuEIrbz0y6mH3o3LF7KAAWqphLPBN0dU,2702
|
13
|
-
latitude_sdk/sdk/logs.py,sha256=CyHkRJvPl_p7wTSvR9bgxEI5akS0Tjc9FeQRb2C2vMg,1997
|
14
|
-
latitude_sdk/sdk/projects.py,sha256=1l6Vvu01-Oi7tp2L1lAhWyZelWIqJVju6rJlR91MVCQ,1458
|
15
|
-
latitude_sdk/sdk/prompts.py,sha256=pi2eAYssCcc6q6kwfzyYfV4z2gRoN_eAS1rXYEAobd8,15949
|
16
|
-
latitude_sdk/sdk/types.py,sha256=lfSjZUrPed8tNHeJ4vi0HLvpWHFthN9MFCKWGcNY-vg,7481
|
17
|
-
latitude_sdk/util/__init__.py,sha256=alIDGBnxWH4JvP-UW-7N99seBBi0r1GV1h8f1ERFBec,21
|
18
|
-
latitude_sdk/util/utils.py,sha256=hMOmF-u1QaDgOwXN6ME6n4TaQ70yZKLvijDUqNCMwXI,2844
|
19
|
-
latitude_sdk-4.0.0b1.dist-info/METADATA,sha256=o9eFEHQ7z5h7c-A5qpHwwbUDR6pLhO7Bhs-Gpo0u2tA,2372
|
20
|
-
latitude_sdk-4.0.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
21
|
-
latitude_sdk-4.0.0b1.dist-info/licenses/LICENSE.md,sha256=yFReu_tr5pjxslWkQREfxA9yVm2r6gay2s6SFCh0XfQ,1073
|
22
|
-
latitude_sdk-4.0.0b1.dist-info/RECORD,,
|
File without changes
|
File without changes
|