latitude-sdk 0.1.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 +1 -0
- latitude_sdk/client/__init__.py +3 -0
- latitude_sdk/client/client.py +140 -0
- latitude_sdk/client/payloads.py +117 -0
- latitude_sdk/client/router.py +117 -0
- latitude_sdk/env/__init__.py +1 -0
- latitude_sdk/env/env.py +18 -0
- latitude_sdk/py.typed +0 -0
- latitude_sdk/sdk/__init__.py +6 -0
- latitude_sdk/sdk/errors.py +59 -0
- latitude_sdk/sdk/evaluations.py +69 -0
- latitude_sdk/sdk/latitude.py +76 -0
- latitude_sdk/sdk/logs.py +66 -0
- latitude_sdk/sdk/prompts.py +297 -0
- latitude_sdk/sdk/types.py +296 -0
- latitude_sdk/util/__init__.py +1 -0
- latitude_sdk/util/utils.py +87 -0
- latitude_sdk-0.1.0b1.dist-info/METADATA +63 -0
- latitude_sdk-0.1.0b1.dist-info/RECORD +20 -0
- latitude_sdk-0.1.0b1.dist-info/WHEEL +4 -0
latitude_sdk/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
from .sdk import *
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import asyncio
|
2
|
+
import json
|
3
|
+
from contextlib import asynccontextmanager
|
4
|
+
from typing import Any, AsyncGenerator, Optional
|
5
|
+
|
6
|
+
import httpx
|
7
|
+
import httpx_sse
|
8
|
+
|
9
|
+
from latitude_sdk.client.payloads import ErrorResponse, RequestBody, RequestHandler, RequestParams
|
10
|
+
from latitude_sdk.client.router import Router, RouterOptions
|
11
|
+
from latitude_sdk.sdk.errors import (
|
12
|
+
ApiError,
|
13
|
+
ApiErrorCodes,
|
14
|
+
ApiErrorDbRef,
|
15
|
+
)
|
16
|
+
from latitude_sdk.sdk.types import LogSources
|
17
|
+
from latitude_sdk.util import Model
|
18
|
+
|
19
|
+
RETRIABLE_STATUSES = [429, 500, 502, 503, 504]
|
20
|
+
|
21
|
+
ClientEvent = httpx_sse.ServerSentEvent
|
22
|
+
|
23
|
+
|
24
|
+
class ClientResponse(httpx.Response):
|
25
|
+
async def sse(self: httpx.Response) -> AsyncGenerator[ClientEvent, Any]:
|
26
|
+
source = httpx_sse.EventSource(self)
|
27
|
+
|
28
|
+
async for event in source.aiter_sse():
|
29
|
+
yield event
|
30
|
+
|
31
|
+
|
32
|
+
httpx.Response.sse = ClientResponse.sse # pyright: ignore [reportAttributeAccessIssue]
|
33
|
+
|
34
|
+
|
35
|
+
class ClientOptions(Model):
|
36
|
+
api_key: str
|
37
|
+
retries: int
|
38
|
+
delay: float
|
39
|
+
timeout: float
|
40
|
+
source: LogSources
|
41
|
+
router: RouterOptions
|
42
|
+
|
43
|
+
|
44
|
+
class Client:
|
45
|
+
options: ClientOptions
|
46
|
+
router: Router
|
47
|
+
|
48
|
+
def __init__(self, options: ClientOptions):
|
49
|
+
self.options = options
|
50
|
+
self.router = Router(options.router)
|
51
|
+
|
52
|
+
@asynccontextmanager
|
53
|
+
async def request(
|
54
|
+
self, handler: RequestHandler, params: RequestParams, body: Optional[RequestBody] = None
|
55
|
+
) -> AsyncGenerator[ClientResponse, Any]:
|
56
|
+
client = httpx.AsyncClient(
|
57
|
+
headers={
|
58
|
+
"Authorization": f"Bearer {self.options.api_key}",
|
59
|
+
"Content-Type": "application/json",
|
60
|
+
},
|
61
|
+
timeout=self.options.timeout,
|
62
|
+
follow_redirects=False,
|
63
|
+
max_redirects=0,
|
64
|
+
)
|
65
|
+
response = None
|
66
|
+
attempt = 1
|
67
|
+
|
68
|
+
try:
|
69
|
+
method, url = self.router.resolve(handler, params)
|
70
|
+
content = None
|
71
|
+
if body:
|
72
|
+
content = json.dumps(
|
73
|
+
{
|
74
|
+
**json.loads(body.model_dump_json()),
|
75
|
+
"__internal": {"source": self.options.source},
|
76
|
+
}
|
77
|
+
)
|
78
|
+
|
79
|
+
while attempt <= self.options.retries:
|
80
|
+
try:
|
81
|
+
response = await client.request(method=method, url=url, content=content)
|
82
|
+
response.raise_for_status()
|
83
|
+
|
84
|
+
yield response # pyright: ignore [reportReturnType]
|
85
|
+
break
|
86
|
+
|
87
|
+
except Exception as exception:
|
88
|
+
if isinstance(exception, ApiError):
|
89
|
+
raise exception
|
90
|
+
|
91
|
+
if attempt >= self.options.retries:
|
92
|
+
raise await self._exception(exception, response) from exception
|
93
|
+
|
94
|
+
if response and response.status_code in RETRIABLE_STATUSES:
|
95
|
+
await asyncio.sleep(self.options.delay * (2 ** (attempt - 1)))
|
96
|
+
else:
|
97
|
+
raise await self._exception(exception, response) from exception
|
98
|
+
|
99
|
+
finally:
|
100
|
+
if response:
|
101
|
+
await response.aclose()
|
102
|
+
|
103
|
+
attempt += 1
|
104
|
+
|
105
|
+
except Exception as exception:
|
106
|
+
if isinstance(exception, ApiError):
|
107
|
+
raise exception
|
108
|
+
|
109
|
+
raise await self._exception(exception, response) from exception
|
110
|
+
|
111
|
+
finally:
|
112
|
+
await client.aclose()
|
113
|
+
|
114
|
+
async def _exception(self, exception: Exception, response: Optional[httpx.Response] = None) -> ApiError:
|
115
|
+
if not response:
|
116
|
+
return ApiError(
|
117
|
+
status=500,
|
118
|
+
code=ApiErrorCodes.InternalServerError,
|
119
|
+
message=str(exception),
|
120
|
+
response=str(exception),
|
121
|
+
)
|
122
|
+
|
123
|
+
try:
|
124
|
+
error = ErrorResponse.model_validate_json(response.content)
|
125
|
+
|
126
|
+
return ApiError(
|
127
|
+
status=response.status_code,
|
128
|
+
code=error.code,
|
129
|
+
message=error.message,
|
130
|
+
response=response.text,
|
131
|
+
db_ref=ApiErrorDbRef(**dict(error.db_ref)) if error.db_ref else None,
|
132
|
+
)
|
133
|
+
|
134
|
+
except Exception:
|
135
|
+
return ApiError(
|
136
|
+
status=response.status_code,
|
137
|
+
code=ApiErrorCodes.InternalServerError,
|
138
|
+
message=str(exception),
|
139
|
+
response=response.text,
|
140
|
+
)
|
@@ -0,0 +1,117 @@
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
2
|
+
|
3
|
+
from latitude_sdk.sdk.types import DbErrorRef, Message
|
4
|
+
from latitude_sdk.util import Field, Model, StrEnum
|
5
|
+
|
6
|
+
|
7
|
+
class ErrorResponse(Model):
|
8
|
+
name: str
|
9
|
+
code: str = Field(alias=str("errorCode"))
|
10
|
+
message: str
|
11
|
+
details: Dict[str, Any]
|
12
|
+
db_ref: Optional[DbErrorRef] = Field(None, alias=str("dbErrorRef"))
|
13
|
+
|
14
|
+
|
15
|
+
class PromptRequestParams(Model):
|
16
|
+
project_id: int
|
17
|
+
version_uuid: Optional[str] = None
|
18
|
+
|
19
|
+
|
20
|
+
class GetPromptRequestParams(PromptRequestParams, Model):
|
21
|
+
path: str
|
22
|
+
|
23
|
+
|
24
|
+
class GetOrCreatePromptRequestParams(PromptRequestParams, Model):
|
25
|
+
pass
|
26
|
+
|
27
|
+
|
28
|
+
class GetOrCreatePromptRequestBody(Model):
|
29
|
+
path: str
|
30
|
+
prompt: Optional[str] = None
|
31
|
+
|
32
|
+
|
33
|
+
class RunPromptRequestParams(PromptRequestParams, Model):
|
34
|
+
pass
|
35
|
+
|
36
|
+
|
37
|
+
class RunPromptRequestBody(Model):
|
38
|
+
path: str
|
39
|
+
parameters: Optional[Dict[str, Any]] = None
|
40
|
+
custom_identifier: Optional[str] = Field(None, alias=str("customIdentifier"))
|
41
|
+
stream: Optional[bool] = None
|
42
|
+
|
43
|
+
|
44
|
+
class ChatPromptRequestParams(Model):
|
45
|
+
conversation_uuid: str
|
46
|
+
|
47
|
+
|
48
|
+
class ChatPromptRequestBody(Model):
|
49
|
+
messages: List[Message]
|
50
|
+
stream: Optional[bool] = None
|
51
|
+
|
52
|
+
|
53
|
+
class LogRequestParams(Model):
|
54
|
+
project_id: int
|
55
|
+
version_uuid: Optional[str] = None
|
56
|
+
|
57
|
+
|
58
|
+
class CreateLogRequestParams(LogRequestParams, Model):
|
59
|
+
pass
|
60
|
+
|
61
|
+
|
62
|
+
class CreateLogRequestBody(Model):
|
63
|
+
path: str
|
64
|
+
messages: List[Message]
|
65
|
+
response: Optional[str] = None
|
66
|
+
|
67
|
+
|
68
|
+
class EvaluationRequestParams(Model):
|
69
|
+
conversation_uuid: str
|
70
|
+
|
71
|
+
|
72
|
+
class TriggerEvaluationRequestParams(EvaluationRequestParams, Model):
|
73
|
+
pass
|
74
|
+
|
75
|
+
|
76
|
+
class TriggerEvaluationRequestBody(Model):
|
77
|
+
evaluation_uuids: Optional[List[str]] = Field(None, alias=str("evaluationUuids"))
|
78
|
+
|
79
|
+
|
80
|
+
class CreateEvaluationResultRequestParams(EvaluationRequestParams, Model):
|
81
|
+
evaluation_uuid: str
|
82
|
+
|
83
|
+
|
84
|
+
class CreateEvaluationResultRequestBody(Model):
|
85
|
+
result: Union[str, bool, int]
|
86
|
+
reason: str
|
87
|
+
|
88
|
+
|
89
|
+
RequestParams = Union[
|
90
|
+
GetPromptRequestParams,
|
91
|
+
GetOrCreatePromptRequestParams,
|
92
|
+
RunPromptRequestParams,
|
93
|
+
ChatPromptRequestParams,
|
94
|
+
CreateLogRequestParams,
|
95
|
+
TriggerEvaluationRequestParams,
|
96
|
+
CreateEvaluationResultRequestParams,
|
97
|
+
]
|
98
|
+
|
99
|
+
|
100
|
+
RequestBody = Union[
|
101
|
+
GetOrCreatePromptRequestBody,
|
102
|
+
RunPromptRequestBody,
|
103
|
+
ChatPromptRequestBody,
|
104
|
+
CreateLogRequestBody,
|
105
|
+
TriggerEvaluationRequestBody,
|
106
|
+
CreateEvaluationResultRequestBody,
|
107
|
+
]
|
108
|
+
|
109
|
+
|
110
|
+
class RequestHandler(StrEnum):
|
111
|
+
GetPrompt = "GET_PROMPT"
|
112
|
+
GetOrCreatePrompt = "GET_OR_CREATE_PROMPT"
|
113
|
+
RunPrompt = "RUN_PROMPT"
|
114
|
+
ChatPrompt = "CHAT_PROMPT"
|
115
|
+
CreateLog = "CREATE_LOG"
|
116
|
+
TriggerEvaluation = "TRIGGER_EVALUATION"
|
117
|
+
CreateEvaluationResult = "CREATE_EVALUATION_RESULT"
|
@@ -0,0 +1,117 @@
|
|
1
|
+
from typing import Callable, Optional, Tuple
|
2
|
+
|
3
|
+
from latitude_sdk.client.payloads import (
|
4
|
+
ChatPromptRequestParams,
|
5
|
+
CreateEvaluationResultRequestParams,
|
6
|
+
CreateLogRequestParams,
|
7
|
+
GetOrCreatePromptRequestParams,
|
8
|
+
GetPromptRequestParams,
|
9
|
+
RequestHandler,
|
10
|
+
RequestParams,
|
11
|
+
RunPromptRequestParams,
|
12
|
+
TriggerEvaluationRequestParams,
|
13
|
+
)
|
14
|
+
from latitude_sdk.sdk.types import GatewayOptions
|
15
|
+
from latitude_sdk.util import Model
|
16
|
+
|
17
|
+
HEAD_COMMIT = "live"
|
18
|
+
|
19
|
+
|
20
|
+
class RouterOptions(Model):
|
21
|
+
gateway: GatewayOptions
|
22
|
+
|
23
|
+
|
24
|
+
class Router:
|
25
|
+
options: RouterOptions
|
26
|
+
|
27
|
+
def __init__(self, options: RouterOptions):
|
28
|
+
self.options = options
|
29
|
+
|
30
|
+
def resolve(self, handler: RequestHandler, params: RequestParams) -> Tuple[str, str]:
|
31
|
+
if handler == RequestHandler.GetPrompt:
|
32
|
+
assert isinstance(params, GetPromptRequestParams)
|
33
|
+
|
34
|
+
return "GET", self.prompts(
|
35
|
+
project_id=params.project_id,
|
36
|
+
version_uuid=params.version_uuid,
|
37
|
+
).prompt(params.path)
|
38
|
+
|
39
|
+
elif handler == RequestHandler.GetOrCreatePrompt:
|
40
|
+
assert isinstance(params, GetOrCreatePromptRequestParams)
|
41
|
+
|
42
|
+
return "POST", self.prompts(
|
43
|
+
project_id=params.project_id,
|
44
|
+
version_uuid=params.version_uuid,
|
45
|
+
).get_or_create
|
46
|
+
|
47
|
+
elif handler == RequestHandler.RunPrompt:
|
48
|
+
assert isinstance(params, RunPromptRequestParams)
|
49
|
+
|
50
|
+
return "POST", self.prompts(
|
51
|
+
project_id=params.project_id,
|
52
|
+
version_uuid=params.version_uuid,
|
53
|
+
).run
|
54
|
+
|
55
|
+
elif handler == RequestHandler.ChatPrompt:
|
56
|
+
assert isinstance(params, ChatPromptRequestParams)
|
57
|
+
|
58
|
+
return "POST", self.conversations().chat(params.conversation_uuid)
|
59
|
+
|
60
|
+
elif handler == RequestHandler.CreateLog:
|
61
|
+
assert isinstance(params, CreateLogRequestParams)
|
62
|
+
|
63
|
+
return "POST", self.prompts(
|
64
|
+
project_id=params.project_id,
|
65
|
+
version_uuid=params.version_uuid,
|
66
|
+
).logs
|
67
|
+
|
68
|
+
elif handler == RequestHandler.TriggerEvaluation:
|
69
|
+
assert isinstance(params, TriggerEvaluationRequestParams)
|
70
|
+
|
71
|
+
return "POST", self.conversations().evaluate(params.conversation_uuid)
|
72
|
+
|
73
|
+
elif handler == RequestHandler.CreateEvaluationResult:
|
74
|
+
assert isinstance(params, CreateEvaluationResultRequestParams)
|
75
|
+
|
76
|
+
return "POST", self.conversations().evaluation_result(params.conversation_uuid, params.evaluation_uuid)
|
77
|
+
|
78
|
+
raise TypeError(f"Unknown handler: {handler}")
|
79
|
+
|
80
|
+
class Conversations(Model):
|
81
|
+
chat: Callable[[str], str]
|
82
|
+
evaluate: Callable[[str], str]
|
83
|
+
evaluation_result: Callable[[str, str], str]
|
84
|
+
|
85
|
+
def conversations(self) -> Conversations:
|
86
|
+
base_url = f"{self.options.gateway.base_url}/conversations"
|
87
|
+
|
88
|
+
return self.Conversations(
|
89
|
+
chat=lambda uuid: f"{base_url}/{uuid}/chat",
|
90
|
+
evaluate=lambda uuid: f"{base_url}/{uuid}/evaluate",
|
91
|
+
evaluation_result=lambda conversation_uuid,
|
92
|
+
evaluation_uuid: f"{base_url}/{conversation_uuid}/evaluations/{evaluation_uuid}/evaluation-results",
|
93
|
+
)
|
94
|
+
|
95
|
+
class Prompts(Model):
|
96
|
+
prompt: Callable[[str], str]
|
97
|
+
get_or_create: str
|
98
|
+
run: str
|
99
|
+
logs: str
|
100
|
+
|
101
|
+
def prompts(self, project_id: int, version_uuid: Optional[str]) -> Prompts:
|
102
|
+
base_url = f"{self.commits_url(project_id, version_uuid)}/documents"
|
103
|
+
|
104
|
+
return self.Prompts(
|
105
|
+
prompt=lambda path: f"{base_url}/{path}",
|
106
|
+
get_or_create=f"{base_url}/get-or-create",
|
107
|
+
run=f"{base_url}/run",
|
108
|
+
logs=f"{base_url}/logs",
|
109
|
+
)
|
110
|
+
|
111
|
+
def commits_url(self, project_id: int, version_uuid: Optional[str]) -> str:
|
112
|
+
version_uuid = version_uuid if version_uuid else HEAD_COMMIT
|
113
|
+
|
114
|
+
return f"{self.projects_url(project_id)}/versions/{version_uuid}"
|
115
|
+
|
116
|
+
def projects_url(self, project_id: int) -> str:
|
117
|
+
return f"{self.options.gateway.base_url}/projects/{project_id}"
|
@@ -0,0 +1 @@
|
|
1
|
+
from .env import *
|
latitude_sdk/env/env.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from latitude_sdk.util import Model, get_env
|
2
|
+
|
3
|
+
DEFAULT_GATEWAY_HOSTNAME = "gateway.latitude.so"
|
4
|
+
DEFAULT_GATEWAY_PORT = 8787
|
5
|
+
DEFAULT_GATEWAY_SSL = True
|
6
|
+
|
7
|
+
|
8
|
+
class Env(Model):
|
9
|
+
GATEWAY_HOSTNAME: str
|
10
|
+
GATEWAY_PORT: int
|
11
|
+
GATEWAY_SSL: bool
|
12
|
+
|
13
|
+
|
14
|
+
env = Env(
|
15
|
+
GATEWAY_HOSTNAME=get_env("GATEWAY_HOSTNAME", DEFAULT_GATEWAY_HOSTNAME),
|
16
|
+
GATEWAY_PORT=get_env("GATEWAY_PORT", DEFAULT_GATEWAY_PORT),
|
17
|
+
GATEWAY_SSL=get_env("GATEWAY_SSL", DEFAULT_GATEWAY_SSL),
|
18
|
+
)
|
latitude_sdk/py.typed
ADDED
File without changes
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from typing import Optional, Union
|
2
|
+
|
3
|
+
from latitude_sdk.util import Model, StrEnum
|
4
|
+
|
5
|
+
|
6
|
+
# NOTE: Incomplete list
|
7
|
+
class ApiErrorCodes(StrEnum):
|
8
|
+
# LatitudeErrorCodes
|
9
|
+
NotFoundError = "NotFoundError"
|
10
|
+
|
11
|
+
# RunErrorCodes
|
12
|
+
AIRunError = "ai_run_error"
|
13
|
+
|
14
|
+
# ApiErrorCodes
|
15
|
+
HTTPException = "http_exception"
|
16
|
+
InternalServerError = "internal_server_error"
|
17
|
+
|
18
|
+
|
19
|
+
UNEXPECTED_ERROR_CODES = [
|
20
|
+
str(ApiErrorCodes.HTTPException),
|
21
|
+
str(ApiErrorCodes.InternalServerError),
|
22
|
+
]
|
23
|
+
|
24
|
+
|
25
|
+
class ApiErrorDbRef(Model):
|
26
|
+
entity_uuid: str
|
27
|
+
entity_type: str
|
28
|
+
|
29
|
+
|
30
|
+
class ApiError(Exception):
|
31
|
+
status: int
|
32
|
+
code: str # NOTE: Cannot be ApiErrorCodes because the list is incomplete
|
33
|
+
message: str
|
34
|
+
response: str
|
35
|
+
db_ref: Optional[ApiErrorDbRef]
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
status: int,
|
40
|
+
code: Union[str, ApiErrorCodes],
|
41
|
+
message: str,
|
42
|
+
response: str,
|
43
|
+
db_ref: Optional[ApiErrorDbRef] = None,
|
44
|
+
):
|
45
|
+
message = self._exception_message(status, str(code), message)
|
46
|
+
super().__init__(message)
|
47
|
+
|
48
|
+
self.status = status
|
49
|
+
self.code = str(code)
|
50
|
+
self.message = message
|
51
|
+
self.response = response
|
52
|
+
self.db_ref = db_ref
|
53
|
+
|
54
|
+
@staticmethod
|
55
|
+
def _exception_message(status: int, code: str, message: str) -> str:
|
56
|
+
if code in UNEXPECTED_ERROR_CODES:
|
57
|
+
return f"Unexpected API Error: {status} {message}"
|
58
|
+
|
59
|
+
return message
|
@@ -0,0 +1,69 @@
|
|
1
|
+
from typing import List, Optional, Union
|
2
|
+
|
3
|
+
from latitude_sdk.client import (
|
4
|
+
Client,
|
5
|
+
CreateEvaluationResultRequestBody,
|
6
|
+
CreateEvaluationResultRequestParams,
|
7
|
+
RequestHandler,
|
8
|
+
TriggerEvaluationRequestBody,
|
9
|
+
TriggerEvaluationRequestParams,
|
10
|
+
)
|
11
|
+
from latitude_sdk.sdk.types import (
|
12
|
+
EvaluationResult,
|
13
|
+
SdkOptions,
|
14
|
+
)
|
15
|
+
from latitude_sdk.util import Model
|
16
|
+
|
17
|
+
|
18
|
+
class TriggerEvaluationOptions(Model):
|
19
|
+
evaluation_uuids: Optional[List[str]] = None
|
20
|
+
|
21
|
+
|
22
|
+
class TriggerEvaluationResult(Model):
|
23
|
+
evaluations: List[str]
|
24
|
+
|
25
|
+
|
26
|
+
class CreateEvaluationResultOptions(Model):
|
27
|
+
result: Union[str, bool, int]
|
28
|
+
reason: str
|
29
|
+
|
30
|
+
|
31
|
+
class CreateEvaluationResultResult(EvaluationResult, Model):
|
32
|
+
pass
|
33
|
+
|
34
|
+
|
35
|
+
class Evaluations:
|
36
|
+
_options: SdkOptions
|
37
|
+
_client: Client
|
38
|
+
|
39
|
+
def __init__(self, client: Client, options: SdkOptions):
|
40
|
+
self._options = options
|
41
|
+
self._client = client
|
42
|
+
|
43
|
+
async def trigger(self, uuid: str, options: TriggerEvaluationOptions) -> TriggerEvaluationResult:
|
44
|
+
async with self._client.request(
|
45
|
+
handler=RequestHandler.TriggerEvaluation,
|
46
|
+
params=TriggerEvaluationRequestParams(
|
47
|
+
conversation_uuid=uuid,
|
48
|
+
),
|
49
|
+
body=TriggerEvaluationRequestBody(
|
50
|
+
evaluation_uuids=options.evaluation_uuids,
|
51
|
+
),
|
52
|
+
) as response:
|
53
|
+
return TriggerEvaluationResult.model_validate_json(response.content)
|
54
|
+
|
55
|
+
async def create_result(
|
56
|
+
self, uuid: str, evaluation_uuid: str, options: CreateEvaluationResultOptions
|
57
|
+
) -> CreateEvaluationResultResult:
|
58
|
+
async with self._client.request(
|
59
|
+
handler=RequestHandler.CreateEvaluationResult,
|
60
|
+
params=CreateEvaluationResultRequestParams(
|
61
|
+
conversation_uuid=uuid,
|
62
|
+
evaluation_uuid=evaluation_uuid,
|
63
|
+
),
|
64
|
+
body=CreateEvaluationResultRequestBody(
|
65
|
+
result=options.result,
|
66
|
+
reason=options.reason,
|
67
|
+
),
|
68
|
+
) as response:
|
69
|
+
return CreateEvaluationResultResult.model_validate_json(response.content)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from latitude_sdk.client import Client, ClientOptions, RouterOptions
|
4
|
+
from latitude_sdk.env import env
|
5
|
+
from latitude_sdk.sdk.evaluations import Evaluations
|
6
|
+
from latitude_sdk.sdk.logs import Logs
|
7
|
+
from latitude_sdk.sdk.prompts import Prompts
|
8
|
+
from latitude_sdk.sdk.types import GatewayOptions, LogSources, SdkOptions
|
9
|
+
from latitude_sdk.util import Model
|
10
|
+
|
11
|
+
|
12
|
+
class InternalOptions(Model):
|
13
|
+
gateway: Optional[GatewayOptions] = None
|
14
|
+
source: Optional[LogSources] = None
|
15
|
+
retries: Optional[int] = None
|
16
|
+
delay: Optional[float] = None
|
17
|
+
timeout: Optional[float] = None
|
18
|
+
|
19
|
+
|
20
|
+
class LatitudeOptions(SdkOptions, Model):
|
21
|
+
internal: Optional[InternalOptions] = None
|
22
|
+
|
23
|
+
|
24
|
+
DEFAULT_INTERNAL_OPTIONS = InternalOptions(
|
25
|
+
gateway=GatewayOptions(
|
26
|
+
host=env.GATEWAY_HOSTNAME,
|
27
|
+
port=env.GATEWAY_PORT,
|
28
|
+
ssl=env.GATEWAY_SSL,
|
29
|
+
api_version="v2",
|
30
|
+
),
|
31
|
+
source=LogSources.Api,
|
32
|
+
retries=3,
|
33
|
+
delay=0.5,
|
34
|
+
timeout=30,
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
DEFAULT_LATITUDE_OPTIONS = LatitudeOptions(internal=DEFAULT_INTERNAL_OPTIONS)
|
39
|
+
|
40
|
+
|
41
|
+
class Latitude:
|
42
|
+
_options: LatitudeOptions
|
43
|
+
_client: Client
|
44
|
+
|
45
|
+
prompts: Prompts
|
46
|
+
logs: Logs
|
47
|
+
evaluations: Evaluations
|
48
|
+
|
49
|
+
def __init__(self, api_key: str, options: LatitudeOptions = DEFAULT_LATITUDE_OPTIONS):
|
50
|
+
options.internal = options.internal or DEFAULT_INTERNAL_OPTIONS
|
51
|
+
options.internal = InternalOptions(**{**dict(DEFAULT_INTERNAL_OPTIONS), **dict(options.internal)})
|
52
|
+
options = LatitudeOptions(**{**dict(DEFAULT_LATITUDE_OPTIONS), **dict(options)})
|
53
|
+
|
54
|
+
assert options.internal is not None
|
55
|
+
assert options.internal.gateway is not None
|
56
|
+
assert options.internal.source is not None
|
57
|
+
assert options.internal.retries is not None
|
58
|
+
assert options.internal.delay is not None
|
59
|
+
assert options.internal.timeout is not None
|
60
|
+
|
61
|
+
self._options = options
|
62
|
+
self._client = Client(
|
63
|
+
ClientOptions(
|
64
|
+
api_key=api_key,
|
65
|
+
retries=options.internal.retries,
|
66
|
+
delay=options.internal.delay,
|
67
|
+
timeout=options.internal.timeout,
|
68
|
+
source=options.internal.source,
|
69
|
+
router=RouterOptions(gateway=options.internal.gateway),
|
70
|
+
)
|
71
|
+
)
|
72
|
+
|
73
|
+
self.prompts = Prompts(self._client, self._options)
|
74
|
+
self.logs = Logs(self._client, self._options)
|
75
|
+
self.evaluations = Evaluations(self._client, self._options)
|
76
|
+
# TODO: Telemetry - needs Telemetry SDK in Python
|
latitude_sdk/sdk/logs.py
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
|
3
|
+
from latitude_sdk.client import Client, CreateLogRequestBody, CreateLogRequestParams, RequestHandler
|
4
|
+
from latitude_sdk.sdk.errors import ApiError, ApiErrorCodes
|
5
|
+
from latitude_sdk.sdk.types import (
|
6
|
+
Log,
|
7
|
+
Message,
|
8
|
+
SdkOptions,
|
9
|
+
)
|
10
|
+
from latitude_sdk.util import Model
|
11
|
+
|
12
|
+
|
13
|
+
class LogOptions(Model):
|
14
|
+
project_id: Optional[int] = None
|
15
|
+
version_uuid: Optional[str] = None
|
16
|
+
|
17
|
+
|
18
|
+
class CreateLogOptions(LogOptions, Model):
|
19
|
+
response: Optional[str] = None
|
20
|
+
|
21
|
+
|
22
|
+
class CreateLogResult(Log, Model):
|
23
|
+
pass
|
24
|
+
|
25
|
+
|
26
|
+
class Logs:
|
27
|
+
_options: SdkOptions
|
28
|
+
_client: Client
|
29
|
+
|
30
|
+
def __init__(self, client: Client, options: SdkOptions):
|
31
|
+
self._options = options
|
32
|
+
self._client = client
|
33
|
+
|
34
|
+
def _ensure_options(self, options: LogOptions) -> LogOptions:
|
35
|
+
project_id = options.project_id or self._options.project_id
|
36
|
+
if not project_id:
|
37
|
+
raise ApiError(
|
38
|
+
status=404,
|
39
|
+
code=ApiErrorCodes.NotFoundError,
|
40
|
+
message="Project ID is required",
|
41
|
+
response="Project ID is required",
|
42
|
+
)
|
43
|
+
|
44
|
+
version_uuid = options.version_uuid or self._options.version_uuid
|
45
|
+
|
46
|
+
return LogOptions(project_id=project_id, version_uuid=version_uuid)
|
47
|
+
|
48
|
+
async def create(self, path: str, messages: List[Message], options: CreateLogOptions) -> CreateLogResult:
|
49
|
+
log_options = self._ensure_options(options)
|
50
|
+
options = CreateLogOptions(**{**dict(options), **dict(log_options)})
|
51
|
+
|
52
|
+
assert options.project_id is not None
|
53
|
+
|
54
|
+
async with self._client.request(
|
55
|
+
handler=RequestHandler.CreateLog,
|
56
|
+
params=CreateLogRequestParams(
|
57
|
+
project_id=options.project_id,
|
58
|
+
version_uuid=options.version_uuid,
|
59
|
+
),
|
60
|
+
body=CreateLogRequestBody(
|
61
|
+
path=path,
|
62
|
+
messages=messages,
|
63
|
+
response=options.response,
|
64
|
+
),
|
65
|
+
) as response:
|
66
|
+
return CreateLogResult.model_validate_json(response.content)
|