latitude-sdk 0.1.0b3__tar.gz → 0.1.0b5__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.
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/PKG-INFO +1 -1
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/pyproject.toml +2 -1
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/client/client.py +1 -1
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/env/env.py +1 -1
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/sdk/errors.py +10 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/sdk/logs.py +3 -3
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/sdk/prompts.py +8 -6
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/sdk/types.py +36 -37
- latitude_sdk-0.1.0b5/tests/__init__.py +0 -0
- latitude_sdk-0.1.0b5/tests/evaluations/__init__.py +0 -0
- latitude_sdk-0.1.0b5/tests/evaluations/create_result_test.py +59 -0
- latitude_sdk-0.1.0b5/tests/evaluations/trigger_test.py +53 -0
- latitude_sdk-0.1.0b5/tests/logs/__init__.py +0 -0
- latitude_sdk-0.1.0b5/tests/logs/create_test.py +113 -0
- latitude_sdk-0.1.0b5/tests/prompts/__init__.py +0 -0
- latitude_sdk-0.1.0b5/tests/prompts/chat_test.py +294 -0
- latitude_sdk-0.1.0b5/tests/prompts/get_or_create_test.py +104 -0
- latitude_sdk-0.1.0b5/tests/prompts/get_test.py +69 -0
- latitude_sdk-0.1.0b5/tests/prompts/run_test.py +479 -0
- latitude_sdk-0.1.0b5/tests/utils/__init__.py +2 -0
- latitude_sdk-0.1.0b5/tests/utils/fixtures.py +635 -0
- latitude_sdk-0.1.0b5/tests/utils/utils.py +136 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/uv.lock +15 -1
- latitude_sdk-0.1.0b3/tests/prompts/get_test.py +0 -32
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/.gitignore +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/.python-version +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/README.md +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/scripts/format.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/scripts/lint.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/scripts/test.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/__init__.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/client/__init__.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/client/payloads.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/client/router.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/env/__init__.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/py.typed +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/sdk/__init__.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/sdk/evaluations.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/sdk/latitude.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/util/__init__.py +0 -0
- {latitude_sdk-0.1.0b3 → latitude_sdk-0.1.0b5}/src/latitude_sdk/util/utils.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: latitude-sdk
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.0b5
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "latitude-sdk"
|
3
|
-
version = "0.1.0-beta.
|
3
|
+
version = "0.1.0-beta.5"
|
4
4
|
description = "Latitude SDK for Python"
|
5
5
|
authors = [{ name = "Latitude Data SL", email = "hello@latitude.so" }]
|
6
6
|
maintainers = [{ name = "Latitude Data SL", email = "hello@latitude.so" }]
|
@@ -22,6 +22,7 @@ dev = [
|
|
22
22
|
"pytest-asyncio>=0.24.0",
|
23
23
|
"pytest-xdist>=3.6.1",
|
24
24
|
"pytest>=8.3.4",
|
25
|
+
"respx>=0.22.0",
|
25
26
|
"pyright>=1.1.391",
|
26
27
|
"ruff>=0.8.3",
|
27
28
|
"sh>=1.14.3",
|
@@ -16,7 +16,7 @@ from latitude_sdk.sdk.errors import (
|
|
16
16
|
from latitude_sdk.sdk.types import LogSources
|
17
17
|
from latitude_sdk.util import Model
|
18
18
|
|
19
|
-
RETRIABLE_STATUSES = [429, 500, 502, 503, 504]
|
19
|
+
RETRIABLE_STATUSES = [408, 409, 429, 500, 502, 503, 504]
|
20
20
|
|
21
21
|
ClientEvent = httpx_sse.ServerSentEvent
|
22
22
|
|
@@ -57,3 +57,13 @@ class ApiError(Exception):
|
|
57
57
|
return f"Unexpected API Error: {status} {message}"
|
58
58
|
|
59
59
|
return message
|
60
|
+
|
61
|
+
def __eq__(self, other: object) -> bool:
|
62
|
+
return (
|
63
|
+
isinstance(other, ApiError)
|
64
|
+
and self.status == other.status
|
65
|
+
and self.code == other.code
|
66
|
+
and self.message == other.message
|
67
|
+
and self.response == other.response
|
68
|
+
and self.db_ref == other.db_ref
|
69
|
+
)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Iterable, Optional
|
2
2
|
|
3
3
|
from latitude_sdk.client import Client, CreateLogRequestBody, CreateLogRequestParams, RequestHandler
|
4
4
|
from latitude_sdk.sdk.errors import ApiError, ApiErrorCodes
|
@@ -45,7 +45,7 @@ class Logs:
|
|
45
45
|
|
46
46
|
return LogOptions(project_id=project_id, version_uuid=version_uuid)
|
47
47
|
|
48
|
-
async def create(self, path: str, messages:
|
48
|
+
async def create(self, path: str, messages: Iterable[Message], options: CreateLogOptions) -> CreateLogResult:
|
49
49
|
log_options = self._ensure_options(options)
|
50
50
|
options = CreateLogOptions(**{**dict(options), **dict(log_options)})
|
51
51
|
|
@@ -59,7 +59,7 @@ class Logs:
|
|
59
59
|
),
|
60
60
|
body=CreateLogRequestBody(
|
61
61
|
path=path,
|
62
|
-
messages=messages,
|
62
|
+
messages=list(messages),
|
63
63
|
response=options.response,
|
64
64
|
),
|
65
65
|
) as response:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any, AsyncGenerator, Dict, List, Optional
|
1
|
+
from typing import Any, AsyncGenerator, Dict, Iterable, List, Optional
|
2
2
|
|
3
3
|
from latitude_sdk.client import (
|
4
4
|
ChatPromptRequestBody,
|
@@ -119,7 +119,7 @@ class Prompts:
|
|
119
119
|
elif type == str(ChainEvents.Error):
|
120
120
|
event = ChainEventError.model_validate_json(stream_event.data)
|
121
121
|
raise ApiError(
|
122
|
-
status=
|
122
|
+
status=400,
|
123
123
|
code=ApiErrorCodes.AIRunError,
|
124
124
|
message=event.error.message,
|
125
125
|
response=stream_event.data,
|
@@ -130,7 +130,7 @@ class Prompts:
|
|
130
130
|
status=500,
|
131
131
|
code=ApiErrorCodes.InternalServerError,
|
132
132
|
message=f"Unknown latitude event: {type}",
|
133
|
-
response=
|
133
|
+
response=stream_event.data,
|
134
134
|
)
|
135
135
|
|
136
136
|
elif stream_event.event == str(StreamEvents.Provider):
|
@@ -142,7 +142,7 @@ class Prompts:
|
|
142
142
|
status=500,
|
143
143
|
code=ApiErrorCodes.InternalServerError,
|
144
144
|
message=f"Unknown stream event: {stream_event.event}",
|
145
|
-
response=
|
145
|
+
response=stream_event.data,
|
146
146
|
)
|
147
147
|
|
148
148
|
if callbacks.on_event:
|
@@ -247,7 +247,9 @@ class Prompts:
|
|
247
247
|
|
248
248
|
return None
|
249
249
|
|
250
|
-
async def chat(
|
250
|
+
async def chat(
|
251
|
+
self, uuid: str, messages: Iterable[Message], options: ChatPromptOptions
|
252
|
+
) -> Optional[ChatPromptResult]:
|
251
253
|
try:
|
252
254
|
async with self._client.request(
|
253
255
|
handler=RequestHandler.ChatPrompt,
|
@@ -255,7 +257,7 @@ class Prompts:
|
|
255
257
|
conversation_uuid=uuid,
|
256
258
|
),
|
257
259
|
body=ChatPromptRequestBody(
|
258
|
-
messages=messages,
|
260
|
+
messages=list(messages),
|
259
261
|
stream=options.stream,
|
260
262
|
),
|
261
263
|
) as response:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from datetime import datetime
|
2
|
-
from typing import Any, Dict, List, Optional, Protocol, Union, runtime_checkable
|
2
|
+
from typing import Any, Dict, List, Literal, Optional, Protocol, Union, runtime_checkable
|
3
3
|
|
4
4
|
from latitude_sdk.sdk.errors import ApiError
|
5
5
|
from latitude_sdk.util import Field, Model, StrEnum
|
@@ -37,32 +37,32 @@ class ContentType(StrEnum):
|
|
37
37
|
|
38
38
|
|
39
39
|
class TextContent(Model):
|
40
|
-
type: ContentType = ContentType.Text
|
40
|
+
type: Literal[ContentType.Text] = ContentType.Text
|
41
41
|
text: str
|
42
42
|
|
43
43
|
|
44
44
|
class ImageContent(Model):
|
45
|
-
type: ContentType = ContentType.Image
|
45
|
+
type: Literal[ContentType.Image] = ContentType.Image
|
46
46
|
image: str
|
47
47
|
|
48
48
|
|
49
49
|
class FileContent(Model):
|
50
|
-
type: ContentType = ContentType.File
|
50
|
+
type: Literal[ContentType.File] = ContentType.File
|
51
51
|
file: str
|
52
52
|
mime_type: str = Field(alias=str("mimeType"))
|
53
53
|
|
54
54
|
|
55
55
|
class ToolCallContent(Model):
|
56
|
-
type: ContentType = ContentType.ToolCall
|
57
|
-
|
58
|
-
|
59
|
-
|
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
60
|
|
61
61
|
|
62
62
|
class ToolResultContent(Model):
|
63
|
-
type: ContentType = ContentType.ToolResult
|
64
|
-
|
65
|
-
|
63
|
+
type: Literal[ContentType.ToolResult] = ContentType.ToolResult
|
64
|
+
id: str = Field(alias=str("toolCallId"))
|
65
|
+
name: str = Field(alias=str("toolName"))
|
66
66
|
result: str
|
67
67
|
is_error: Optional[bool] = Field(default=None, alias=str("isError"))
|
68
68
|
|
@@ -85,30 +85,23 @@ class MessageRole(StrEnum):
|
|
85
85
|
|
86
86
|
|
87
87
|
class SystemMessage(Model):
|
88
|
-
role: MessageRole = MessageRole.System
|
88
|
+
role: Literal[MessageRole.System] = MessageRole.System
|
89
89
|
content: Union[str, List[TextContent]]
|
90
90
|
|
91
91
|
|
92
92
|
class UserMessage(Model):
|
93
|
-
role: MessageRole = MessageRole.User
|
93
|
+
role: Literal[MessageRole.User] = MessageRole.User
|
94
94
|
content: Union[str, List[Union[TextContent, ImageContent, FileContent]]]
|
95
95
|
name: Optional[str] = None
|
96
96
|
|
97
97
|
|
98
|
-
class ToolCall(Model):
|
99
|
-
id: str
|
100
|
-
name: str
|
101
|
-
arguments: Dict[str, Any]
|
102
|
-
|
103
|
-
|
104
98
|
class AssistantMessage(Model):
|
105
|
-
role: MessageRole = MessageRole.Assistant
|
99
|
+
role: Literal[MessageRole.Assistant] = MessageRole.Assistant
|
106
100
|
content: Union[str, List[Union[TextContent, ToolCallContent]]]
|
107
|
-
tool_calls: Optional[List[ToolCall]] = Field(default=None, alias=str("toolCalls"))
|
108
101
|
|
109
102
|
|
110
103
|
class ToolMessage(Model):
|
111
|
-
role: MessageRole = MessageRole.Tool
|
104
|
+
role: Literal[MessageRole.Tool] = MessageRole.Tool
|
112
105
|
content: List[ToolResultContent]
|
113
106
|
|
114
107
|
|
@@ -121,20 +114,26 @@ class ModelUsage(Model):
|
|
121
114
|
total_tokens: int = Field(alias=str("totalTokens"))
|
122
115
|
|
123
116
|
|
117
|
+
class ToolCall(Model):
|
118
|
+
id: str
|
119
|
+
name: str
|
120
|
+
arguments: Dict[str, Any]
|
121
|
+
|
122
|
+
|
124
123
|
class StreamTypes(StrEnum):
|
125
124
|
Text = "text"
|
126
125
|
Object = "object"
|
127
126
|
|
128
127
|
|
129
128
|
class ChainTextResponse(Model):
|
130
|
-
type: StreamTypes = StreamTypes.Text
|
129
|
+
type: Literal[StreamTypes.Text] = Field(default=StreamTypes.Text, alias=str("streamType"))
|
131
130
|
text: str
|
132
131
|
tool_calls: Optional[List[ToolCall]] = Field(default=None, alias=str("toolCalls"))
|
133
132
|
usage: ModelUsage
|
134
133
|
|
135
134
|
|
136
135
|
class ChainObjectResponse(Model):
|
137
|
-
type: StreamTypes = StreamTypes.Object
|
136
|
+
type: Literal[StreamTypes.Object] = Field(default=StreamTypes.Object, alias=str("streamType"))
|
138
137
|
object: Any
|
139
138
|
usage: ModelUsage
|
140
139
|
|
@@ -165,34 +164,34 @@ class ChainEvents(StrEnum):
|
|
165
164
|
|
166
165
|
|
167
166
|
class ChainEventStep(Model):
|
168
|
-
event: StreamEvents = StreamEvents.Latitude
|
169
|
-
type: ChainEvents = ChainEvents.Step
|
170
|
-
|
167
|
+
event: Literal[StreamEvents.Latitude] = StreamEvents.Latitude
|
168
|
+
type: Literal[ChainEvents.Step] = ChainEvents.Step
|
169
|
+
uuid: Optional[str] = None
|
171
170
|
is_last_step: bool = Field(alias=str("isLastStep"))
|
171
|
+
config: Dict[str, Any]
|
172
172
|
messages: List[Message]
|
173
|
-
uuid: Optional[str] = None
|
174
173
|
|
175
174
|
|
176
175
|
class ChainEventStepCompleted(Model):
|
177
|
-
event: StreamEvents = StreamEvents.Latitude
|
178
|
-
type: ChainEvents = ChainEvents.StepCompleted
|
179
|
-
response: ChainResponse
|
176
|
+
event: Literal[StreamEvents.Latitude] = StreamEvents.Latitude
|
177
|
+
type: Literal[ChainEvents.StepCompleted] = ChainEvents.StepCompleted
|
180
178
|
uuid: Optional[str] = None
|
179
|
+
response: ChainResponse
|
181
180
|
|
182
181
|
|
183
182
|
class ChainEventCompleted(Model):
|
184
|
-
event: StreamEvents = StreamEvents.Latitude
|
185
|
-
type: ChainEvents = ChainEvents.Completed
|
183
|
+
event: Literal[StreamEvents.Latitude] = StreamEvents.Latitude
|
184
|
+
type: Literal[ChainEvents.Completed] = ChainEvents.Completed
|
185
|
+
uuid: Optional[str] = None
|
186
186
|
config: Dict[str, Any]
|
187
187
|
messages: Optional[List[Message]] = None
|
188
188
|
object: Optional[Any] = None
|
189
189
|
response: ChainResponse
|
190
|
-
uuid: Optional[str] = None
|
191
190
|
|
192
191
|
|
193
192
|
class ChainEventError(Model):
|
194
|
-
event: StreamEvents = StreamEvents.Latitude
|
195
|
-
type: ChainEvents = ChainEvents.Error
|
193
|
+
event: Literal[StreamEvents.Latitude] = StreamEvents.Latitude
|
194
|
+
type: Literal[ChainEvents.Error] = ChainEvents.Error
|
196
195
|
error: ChainError
|
197
196
|
|
198
197
|
|
@@ -203,7 +202,7 @@ LatitudeEvent = ChainEvent
|
|
203
202
|
|
204
203
|
|
205
204
|
class FinishedEvent(Model):
|
206
|
-
event: StreamEvents = StreamEvents.Finished
|
205
|
+
event: Literal[StreamEvents.Finished] = StreamEvents.Finished
|
207
206
|
uuid: str
|
208
207
|
conversation: List[Message]
|
209
208
|
response: ChainResponse
|
File without changes
|
File without changes
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from typing import List, cast
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from latitude_sdk import CreateEvaluationResultOptions, CreateEvaluationResultResult
|
6
|
+
from tests.utils import TestCase, fixtures
|
7
|
+
|
8
|
+
|
9
|
+
class TestCreateEvaluationResult(TestCase):
|
10
|
+
async def test_success(self):
|
11
|
+
conversation_uuid = "conversation-uuid"
|
12
|
+
evaluation_uuid = "evaluation-uuid"
|
13
|
+
options = CreateEvaluationResultOptions(result=True, reason="Because Yes")
|
14
|
+
endpoint = f"/conversations/{conversation_uuid}/evaluations/{evaluation_uuid}/evaluation-results"
|
15
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
16
|
+
return_value=httpx.Response(200, json=fixtures.EVALUATION_RESULT_RESPONSE)
|
17
|
+
)
|
18
|
+
|
19
|
+
result = await self.sdk.evaluations.create_result(conversation_uuid, evaluation_uuid, options)
|
20
|
+
request, _ = endpoint_mock.calls.last
|
21
|
+
|
22
|
+
self.assert_requested(
|
23
|
+
request,
|
24
|
+
method="POST",
|
25
|
+
endpoint=endpoint,
|
26
|
+
body={
|
27
|
+
"result": options.result,
|
28
|
+
"reason": options.reason,
|
29
|
+
},
|
30
|
+
)
|
31
|
+
self.assertEqual(endpoint_mock.call_count, 1)
|
32
|
+
self.assertEqual(result, CreateEvaluationResultResult(**dict(fixtures.EVALUATION_RESULT)))
|
33
|
+
|
34
|
+
async def test_fails(self):
|
35
|
+
conversation_uuid = "conversation-uuid"
|
36
|
+
evaluation_uuid = "evaluation-uuid"
|
37
|
+
options = CreateEvaluationResultOptions(result=True, reason="Because Yes")
|
38
|
+
endpoint = f"/conversations/{conversation_uuid}/evaluations/{evaluation_uuid}/evaluation-results"
|
39
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
40
|
+
return_value=httpx.Response(500, json=fixtures.ERROR_RESPONSE)
|
41
|
+
)
|
42
|
+
|
43
|
+
with self.assertRaisesRegex(type(fixtures.ERROR), fixtures.ERROR.message):
|
44
|
+
await self.sdk.evaluations.create_result(conversation_uuid, evaluation_uuid, options)
|
45
|
+
requests = cast(List[httpx.Request], [request for request, _ in endpoint_mock.calls]) # type: ignore
|
46
|
+
|
47
|
+
[
|
48
|
+
self.assert_requested(
|
49
|
+
request,
|
50
|
+
method="POST",
|
51
|
+
endpoint=endpoint,
|
52
|
+
body={
|
53
|
+
"result": options.result,
|
54
|
+
"reason": options.reason,
|
55
|
+
},
|
56
|
+
)
|
57
|
+
for request in requests
|
58
|
+
]
|
59
|
+
self.assertEqual(endpoint_mock.call_count, self.internal_options.retries)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from typing import List, cast
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from latitude_sdk import TriggerEvaluationOptions, TriggerEvaluationResult
|
6
|
+
from tests.utils import TestCase, fixtures
|
7
|
+
|
8
|
+
|
9
|
+
class TestTriggerEvaluation(TestCase):
|
10
|
+
async def test_success(self):
|
11
|
+
conversation_uuid = "conversation-uuid"
|
12
|
+
options = TriggerEvaluationOptions(evaluation_uuids=["evaluation-uuid-1", "evaluation-uuid-2"])
|
13
|
+
endpoint = f"/conversations/{conversation_uuid}/evaluate"
|
14
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
15
|
+
return_value=httpx.Response(200, json=fixtures.EVALUATIONS_RESPONSE)
|
16
|
+
)
|
17
|
+
|
18
|
+
result = await self.sdk.evaluations.trigger(conversation_uuid, options)
|
19
|
+
request, _ = endpoint_mock.calls.last
|
20
|
+
|
21
|
+
self.assert_requested(
|
22
|
+
request,
|
23
|
+
method="POST",
|
24
|
+
endpoint=endpoint,
|
25
|
+
body={
|
26
|
+
"evaluationUuids": options.evaluation_uuids,
|
27
|
+
},
|
28
|
+
)
|
29
|
+
self.assertEqual(endpoint_mock.call_count, 1)
|
30
|
+
self.assertEqual(result, TriggerEvaluationResult(evaluations=fixtures.EVALUATIONS))
|
31
|
+
|
32
|
+
async def test_fails(self):
|
33
|
+
conversation_uuid = "conversation-uuid"
|
34
|
+
options = TriggerEvaluationOptions()
|
35
|
+
endpoint = f"/conversations/{conversation_uuid}/evaluate"
|
36
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
37
|
+
return_value=httpx.Response(500, json=fixtures.ERROR_RESPONSE)
|
38
|
+
)
|
39
|
+
|
40
|
+
with self.assertRaisesRegex(type(fixtures.ERROR), fixtures.ERROR.message):
|
41
|
+
await self.sdk.evaluations.trigger(conversation_uuid, options)
|
42
|
+
requests = cast(List[httpx.Request], [request for request, _ in endpoint_mock.calls]) # type: ignore
|
43
|
+
|
44
|
+
[
|
45
|
+
self.assert_requested(
|
46
|
+
request,
|
47
|
+
method="POST",
|
48
|
+
endpoint=endpoint,
|
49
|
+
body={},
|
50
|
+
)
|
51
|
+
for request in requests
|
52
|
+
]
|
53
|
+
self.assertEqual(endpoint_mock.call_count, self.internal_options.retries)
|
File without changes
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import json
|
2
|
+
from typing import List, cast
|
3
|
+
|
4
|
+
import httpx
|
5
|
+
|
6
|
+
from latitude_sdk import CreateLogOptions, CreateLogResult
|
7
|
+
from tests.utils import TestCase, fixtures
|
8
|
+
|
9
|
+
|
10
|
+
class TestCreateLog(TestCase):
|
11
|
+
async def test_success_global_options(self):
|
12
|
+
path = "prompt-path"
|
13
|
+
messages = self.create_conversation(4)
|
14
|
+
options = CreateLogOptions(response="response")
|
15
|
+
endpoint = f"/projects/{self.project_id}/versions/{self.version_uuid}/documents/logs"
|
16
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
17
|
+
return_value=httpx.Response(200, json=fixtures.LOG_RESPONSE)
|
18
|
+
)
|
19
|
+
|
20
|
+
result = await self.sdk.logs.create(path, messages, options)
|
21
|
+
request, _ = endpoint_mock.calls.last
|
22
|
+
|
23
|
+
self.assert_requested(
|
24
|
+
request,
|
25
|
+
method="POST",
|
26
|
+
endpoint=endpoint,
|
27
|
+
body={
|
28
|
+
"path": path,
|
29
|
+
"messages": [json.loads(message.model_dump_json()) for message in messages],
|
30
|
+
"response": options.response,
|
31
|
+
},
|
32
|
+
)
|
33
|
+
self.assertEqual(endpoint_mock.call_count, 1)
|
34
|
+
self.assertEqual(result, CreateLogResult(**dict(fixtures.LOG)))
|
35
|
+
|
36
|
+
async def test_success_overrides_options(self):
|
37
|
+
path = "prompt-path"
|
38
|
+
messages = self.create_conversation(4)
|
39
|
+
options = CreateLogOptions(project_id=21, version_uuid="version-uuid", response="response")
|
40
|
+
endpoint = f"/projects/{options.project_id}/versions/{options.version_uuid}/documents/logs"
|
41
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
42
|
+
return_value=httpx.Response(200, json=fixtures.LOG_RESPONSE)
|
43
|
+
)
|
44
|
+
|
45
|
+
result = await self.sdk.logs.create(path, messages, options)
|
46
|
+
request, _ = endpoint_mock.calls.last
|
47
|
+
|
48
|
+
self.assert_requested(
|
49
|
+
request,
|
50
|
+
method="POST",
|
51
|
+
endpoint=endpoint,
|
52
|
+
body={
|
53
|
+
"path": path,
|
54
|
+
"messages": [json.loads(message.model_dump_json()) for message in messages],
|
55
|
+
"response": options.response,
|
56
|
+
},
|
57
|
+
)
|
58
|
+
self.assertEqual(endpoint_mock.call_count, 1)
|
59
|
+
self.assertEqual(result, CreateLogResult(**dict(fixtures.LOG)))
|
60
|
+
|
61
|
+
async def test_success_default_version_uuid(self):
|
62
|
+
self.sdk._options.version_uuid = None # pyright: ignore [reportPrivateUsage]
|
63
|
+
path = "prompt-path"
|
64
|
+
messages = self.create_conversation(4)
|
65
|
+
options = CreateLogOptions(response="response")
|
66
|
+
endpoint = f"/projects/{self.project_id}/versions/live/documents/logs"
|
67
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
68
|
+
return_value=httpx.Response(200, json=fixtures.LOG_RESPONSE)
|
69
|
+
)
|
70
|
+
|
71
|
+
result = await self.sdk.logs.create(path, messages, options)
|
72
|
+
request, _ = endpoint_mock.calls.last
|
73
|
+
|
74
|
+
self.assert_requested(
|
75
|
+
request,
|
76
|
+
method="POST",
|
77
|
+
endpoint=endpoint,
|
78
|
+
body={
|
79
|
+
"path": path,
|
80
|
+
"messages": [json.loads(message.model_dump_json()) for message in messages],
|
81
|
+
"response": options.response,
|
82
|
+
},
|
83
|
+
)
|
84
|
+
self.assertEqual(endpoint_mock.call_count, 1)
|
85
|
+
self.assertEqual(result, CreateLogResult(**dict(fixtures.LOG)))
|
86
|
+
|
87
|
+
async def test_fails(self):
|
88
|
+
path = "prompt-path"
|
89
|
+
messages = self.create_conversation(4)
|
90
|
+
options = CreateLogOptions(response="response")
|
91
|
+
endpoint = f"/projects/{self.project_id}/versions/{self.version_uuid}/documents/logs"
|
92
|
+
endpoint_mock = self.gateway_mock.post(endpoint).mock(
|
93
|
+
return_value=httpx.Response(500, json=fixtures.ERROR_RESPONSE)
|
94
|
+
)
|
95
|
+
|
96
|
+
with self.assertRaisesRegex(type(fixtures.ERROR), fixtures.ERROR.message):
|
97
|
+
await self.sdk.logs.create(path, messages, options)
|
98
|
+
requests = cast(List[httpx.Request], [request for request, _ in endpoint_mock.calls]) # type: ignore
|
99
|
+
|
100
|
+
[
|
101
|
+
self.assert_requested(
|
102
|
+
request,
|
103
|
+
method="POST",
|
104
|
+
endpoint=endpoint,
|
105
|
+
body={
|
106
|
+
"path": path,
|
107
|
+
"messages": [json.loads(message.model_dump_json()) for message in messages],
|
108
|
+
"response": options.response,
|
109
|
+
},
|
110
|
+
)
|
111
|
+
for request in requests
|
112
|
+
]
|
113
|
+
self.assertEqual(endpoint_mock.call_count, self.internal_options.retries)
|
File without changes
|