latitude-sdk 0.1.0b1__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.
Files changed (28) hide show
  1. latitude_sdk-0.1.0b1/.gitignore +141 -0
  2. latitude_sdk-0.1.0b1/.python-version +1 -0
  3. latitude_sdk-0.1.0b1/PKG-INFO +63 -0
  4. latitude_sdk-0.1.0b1/README.md +46 -0
  5. latitude_sdk-0.1.0b1/examples/all.py +120 -0
  6. latitude_sdk-0.1.0b1/pyproject.toml +64 -0
  7. latitude_sdk-0.1.0b1/scripts/format.py +8 -0
  8. latitude_sdk-0.1.0b1/scripts/lint.py +9 -0
  9. latitude_sdk-0.1.0b1/scripts/test.py +7 -0
  10. latitude_sdk-0.1.0b1/src/latitude_sdk/__init__.py +1 -0
  11. latitude_sdk-0.1.0b1/src/latitude_sdk/client/__init__.py +3 -0
  12. latitude_sdk-0.1.0b1/src/latitude_sdk/client/client.py +140 -0
  13. latitude_sdk-0.1.0b1/src/latitude_sdk/client/payloads.py +117 -0
  14. latitude_sdk-0.1.0b1/src/latitude_sdk/client/router.py +117 -0
  15. latitude_sdk-0.1.0b1/src/latitude_sdk/env/__init__.py +1 -0
  16. latitude_sdk-0.1.0b1/src/latitude_sdk/env/env.py +18 -0
  17. latitude_sdk-0.1.0b1/src/latitude_sdk/py.typed +0 -0
  18. latitude_sdk-0.1.0b1/src/latitude_sdk/sdk/__init__.py +6 -0
  19. latitude_sdk-0.1.0b1/src/latitude_sdk/sdk/errors.py +59 -0
  20. latitude_sdk-0.1.0b1/src/latitude_sdk/sdk/evaluations.py +69 -0
  21. latitude_sdk-0.1.0b1/src/latitude_sdk/sdk/latitude.py +76 -0
  22. latitude_sdk-0.1.0b1/src/latitude_sdk/sdk/logs.py +66 -0
  23. latitude_sdk-0.1.0b1/src/latitude_sdk/sdk/prompts.py +297 -0
  24. latitude_sdk-0.1.0b1/src/latitude_sdk/sdk/types.py +296 -0
  25. latitude_sdk-0.1.0b1/src/latitude_sdk/util/__init__.py +1 -0
  26. latitude_sdk-0.1.0b1/src/latitude_sdk/util/utils.py +87 -0
  27. latitude_sdk-0.1.0b1/tests/prompts/get_test.py +32 -0
  28. latitude_sdk-0.1.0b1/uv.lock +527 -0
@@ -0,0 +1,141 @@
1
+ # Custom
2
+ .vscode/
3
+ .superinvoke_cache/
4
+ .idea/
5
+ *.tmlanguage.cache
6
+ *.tmPreferences.cache
7
+ *.stTheme.cache
8
+ *.sublime-workspace
9
+ .DS_Store
10
+ *.env
11
+
12
+ # Byte-compiled / optimized / DLL files
13
+ __pycache__/
14
+ *.py[cod]
15
+ *$py.class
16
+
17
+ # C extensions
18
+ *.so
19
+
20
+ # Distribution / packaging
21
+ .Python
22
+ build/
23
+ develop-eggs/
24
+ dist/
25
+ downloads/
26
+ eggs/
27
+ .eggs/
28
+ lib/
29
+ lib64/
30
+ parts/
31
+ sdist/
32
+ var/
33
+ wheels/
34
+ share/python-wheels/
35
+ *.egg-info/
36
+ .installed.cfg
37
+ *.egg
38
+ MANIFEST
39
+
40
+ # PyInstaller
41
+ *.manifest
42
+ *.spec
43
+
44
+ # Installer logs
45
+ pip-log.txt
46
+ pip-delete-this-directory.txt
47
+
48
+ # Unit test / coverage reports
49
+ htmlcov/
50
+ .tox/
51
+ .nox/
52
+ .coverage
53
+ .coverage.*
54
+ .cache
55
+ nosetests.xml
56
+ coverage.xml
57
+ *.cover
58
+ *.py,cover
59
+ .hypothesis/
60
+ .pytest_cache/
61
+ cover/
62
+
63
+ # Translations
64
+ *.mo
65
+ *.pot
66
+
67
+ # Django stuff:
68
+ *.log
69
+ local_settings.py
70
+ db.sqlite3
71
+ db.sqlite3-journal
72
+
73
+ # Flask stuff:
74
+ instance/
75
+ .webassets-cache
76
+
77
+ # Scrapy stuff:
78
+ .scrapy
79
+
80
+ # Sphinx documentation
81
+ docs/_build/
82
+
83
+ # PyBuilder
84
+ .pybuilder/
85
+ target/
86
+
87
+ # Jupyter Notebook
88
+ .ipynb_checkpoints
89
+
90
+ # IPython
91
+ profile_default/
92
+ ipython_config.py
93
+
94
+ # pyenv
95
+ -
96
+
97
+ # pdm
98
+ .pdm.toml
99
+
100
+ # PEP 582
101
+ __pypackages__/
102
+
103
+ # Celery stuff
104
+ celerybeat-schedule
105
+ celerybeat.pid
106
+
107
+ # SageMath parsed files
108
+ *.sage.py
109
+
110
+ # Environments
111
+ .env
112
+ .venv
113
+ venv/
114
+ venv.bak/
115
+
116
+ # Spyder project settings
117
+ .spyderproject
118
+ .spyproject
119
+
120
+ # Rope project settings
121
+ .ropeproject
122
+
123
+ # mkdocs documentation
124
+ /site
125
+
126
+ # mypy
127
+ .mypy_cache/
128
+ .dmypy.json
129
+ dmypy.json
130
+
131
+ # Pyre type checker
132
+ .pyre/
133
+
134
+ # pytype static type analyzer
135
+ .pytype/
136
+
137
+ # Cython debug symbols
138
+ cython_debug/
139
+
140
+ # Ruff
141
+ .ruff_cache/
@@ -0,0 +1 @@
1
+ 3.8.20
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: latitude-sdk
3
+ Version: 0.1.0b1
4
+ Summary: Latitude SDK for Python
5
+ Project-URL: repository, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python
6
+ Project-URL: homepage, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python#readme
7
+ Project-URL: documentation, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python#readme
8
+ Author-email: Latitude Data SL <hello@latitude.so>
9
+ Maintainer-email: Latitude Data SL <hello@latitude.so>
10
+ License-Expression: LGPL-3.0
11
+ Requires-Python: >=3.8
12
+ Requires-Dist: httpx-sse>=0.4.0
13
+ Requires-Dist: httpx>=0.28.1
14
+ Requires-Dist: pydantic>=2.10.3
15
+ Requires-Dist: typing-extensions>=4.12.2
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Latitude SDK for Python
19
+
20
+ ```sh
21
+ pip install latitude-sdk
22
+ ```
23
+
24
+ Requires Python `3.8` or higher.
25
+
26
+ Go to the [documentation](https://docs.latitude.so/guides/sdk/python) to learn more.
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from latitude_sdk import Latitude, LatitudeOptions, RunPromptOptions
32
+
33
+ sdk = Latitude("my-api-key", LatitudeOptions(
34
+ project_id="my-project-id",
35
+ version_uuid="my-version-uuid",
36
+ ))
37
+
38
+ await sdk.prompts.run("joke-teller", RunPromptOptions(
39
+ parameters={"topic": "Python"},
40
+ on_event=lambda event: print(event),
41
+ on_finished=lambda event: print(event),
42
+ on_error=lambda error: print(error),
43
+ stream=True,
44
+ ))
45
+ ```
46
+
47
+ Find more examples [here](https://github.com/latitude-dev/latitude-llm/tree/main/examples/sdks/python).
48
+
49
+ ## Development
50
+
51
+ Requires uv `0.5.10` or higher.
52
+
53
+ - Install dependencies: `uv venv && uv sync --all-extras --all-groups`
54
+ - Add [dev] dependencies: `uv add [--dev] <package>`
55
+ - Run linter: `uv run scripts/lint.py`
56
+ - Run formatter: `uv run scripts/format.py`
57
+ - Run tests: `uv run scripts/test.py`
58
+ - Build package: `uv build`
59
+ - Publish package: `uv publish`
60
+
61
+ ## License
62
+
63
+ The SDK is licensed under the [LGPL-3.0 License](https://opensource.org/licenses/LGPL-3.0) - read the [LICENSE](/LICENSE) file for details.
@@ -0,0 +1,46 @@
1
+ # Latitude SDK for Python
2
+
3
+ ```sh
4
+ pip install latitude-sdk
5
+ ```
6
+
7
+ Requires Python `3.8` or higher.
8
+
9
+ Go to the [documentation](https://docs.latitude.so/guides/sdk/python) to learn more.
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from latitude_sdk import Latitude, LatitudeOptions, RunPromptOptions
15
+
16
+ sdk = Latitude("my-api-key", LatitudeOptions(
17
+ project_id="my-project-id",
18
+ version_uuid="my-version-uuid",
19
+ ))
20
+
21
+ await sdk.prompts.run("joke-teller", RunPromptOptions(
22
+ parameters={"topic": "Python"},
23
+ on_event=lambda event: print(event),
24
+ on_finished=lambda event: print(event),
25
+ on_error=lambda error: print(error),
26
+ stream=True,
27
+ ))
28
+ ```
29
+
30
+ Find more examples [here](https://github.com/latitude-dev/latitude-llm/tree/main/examples/sdks/python).
31
+
32
+ ## Development
33
+
34
+ Requires uv `0.5.10` or higher.
35
+
36
+ - Install dependencies: `uv venv && uv sync --all-extras --all-groups`
37
+ - Add [dev] dependencies: `uv add [--dev] <package>`
38
+ - Run linter: `uv run scripts/lint.py`
39
+ - Run formatter: `uv run scripts/format.py`
40
+ - Run tests: `uv run scripts/test.py`
41
+ - Build package: `uv build`
42
+ - Publish package: `uv publish`
43
+
44
+ ## License
45
+
46
+ The SDK is licensed under the [LGPL-3.0 License](https://opensource.org/licenses/LGPL-3.0) - read the [LICENSE](/LICENSE) file for details.
@@ -0,0 +1,120 @@
1
+ import asyncio
2
+ from pprint import pp # type: ignore
3
+
4
+ from latitude_sdk import (
5
+ ApiError,
6
+ ChatPromptOptions,
7
+ CreateEvaluationResultOptions,
8
+ CreateLogOptions,
9
+ GatewayOptions,
10
+ GetOrCreatePromptOptions,
11
+ GetPromptOptions,
12
+ InternalOptions,
13
+ Latitude,
14
+ LatitudeOptions,
15
+ RunPromptOptions,
16
+ TextContent,
17
+ TriggerEvaluationOptions,
18
+ UserMessage,
19
+ )
20
+
21
+
22
+ # TODO Move to root/examples when latitude-sdk is published
23
+ async def main():
24
+ sdk = Latitude(
25
+ api_key="6f67407c-da6c-4a4d-9615-a3eb59e51d29",
26
+ options=LatitudeOptions(
27
+ project_id=3,
28
+ version_uuid="57502e00-20c2-4411-8b4b-44bc9008079e",
29
+ internal=InternalOptions(gateway=GatewayOptions(host="localhost", port=8787, ssl=False, api_version="v2")),
30
+ ),
31
+ )
32
+
33
+ try:
34
+ print("Getting prompt...")
35
+ get_prompt_result = await sdk.prompts.get("prompt", GetPromptOptions())
36
+ pp(get_prompt_result.model_dump())
37
+ print("\n" * 2)
38
+ print("-" * 100)
39
+
40
+ print("Getting or creating prompt...")
41
+ get_or_create_prompt_result = await sdk.prompts.get_or_create("prompt3", GetOrCreatePromptOptions())
42
+ pp(get_or_create_prompt_result.model_dump())
43
+ print("\n" * 2)
44
+ print("-" * 100)
45
+
46
+ print("Running prompt...")
47
+ run_prompt_result = await sdk.prompts.run(
48
+ "prompt",
49
+ RunPromptOptions(
50
+ on_event=lambda event: print(event, "\n" * 2),
51
+ on_finished=lambda event: print(event, "\n" * 2),
52
+ on_error=lambda error: print(error, "\n" * 2),
53
+ custom_identifier="custom!",
54
+ parameters={"topic": "Python"},
55
+ stream=True,
56
+ ),
57
+ )
58
+ assert run_prompt_result is not None
59
+ pp(run_prompt_result.model_dump())
60
+ print("\n" * 2)
61
+ print("-" * 100)
62
+
63
+ print("Chat prompt...")
64
+ chat_prompt_result = await sdk.prompts.chat(
65
+ run_prompt_result.uuid,
66
+ [
67
+ UserMessage(content=[TextContent(text="Hello, how are you?")]),
68
+ UserMessage(content="I'm fine btw"),
69
+ ],
70
+ ChatPromptOptions(
71
+ on_event=lambda event: print(event, "\n" * 2),
72
+ on_finished=lambda event: print(event, "\n" * 2),
73
+ on_error=lambda error: print(error, "\n" * 2),
74
+ stream=True,
75
+ ),
76
+ )
77
+ assert chat_prompt_result is not None
78
+ pp(chat_prompt_result.model_dump())
79
+ print("\n" * 2)
80
+ print("-" * 100)
81
+
82
+ print("Create log...")
83
+ create_log_result = await sdk.logs.create(
84
+ "prompt",
85
+ [
86
+ UserMessage(content=[TextContent(text="Hello, how are you?")]),
87
+ UserMessage(content=[TextContent(text="I'm fine btw")]),
88
+ ],
89
+ CreateLogOptions(),
90
+ )
91
+ pp(create_log_result.model_dump())
92
+ print("\n" * 2)
93
+ print("-" * 100)
94
+
95
+ print("Trigger evaluation...")
96
+ trigger_evaluation_result = await sdk.evaluations.trigger(
97
+ chat_prompt_result.uuid,
98
+ TriggerEvaluationOptions(evaluation_uuids=["46d29f2d-7086-44b8-9220-af1dea1e3692"]),
99
+ )
100
+ pp(trigger_evaluation_result.model_dump())
101
+ print("\n" * 2)
102
+ print("-" * 100)
103
+
104
+ print("Create evaluation result...")
105
+ create_evaluation_result_result = await sdk.evaluations.create_result(
106
+ chat_prompt_result.uuid,
107
+ "d7a04129-9df8-4047-ba93-6349029a1000",
108
+ CreateEvaluationResultOptions(result="I like it!", reason="Because I like it!"),
109
+ )
110
+ pp(create_evaluation_result_result.model_dump())
111
+ print("\n" * 2)
112
+ print("-" * 100)
113
+
114
+ except ApiError as error:
115
+ pp(error.__dict__)
116
+ except Exception as e:
117
+ raise e
118
+
119
+
120
+ asyncio.run(main())
@@ -0,0 +1,64 @@
1
+ [project]
2
+ name = "latitude-sdk"
3
+ version = "0.1.0-beta.1"
4
+ description = "Latitude SDK for Python"
5
+ authors = [{ name = "Latitude Data SL", email = "hello@latitude.so" }]
6
+ maintainers = [{ name = "Latitude Data SL", email = "hello@latitude.so" }]
7
+ readme = "README.md"
8
+ license = "LGPL-3.0"
9
+ urls.repository = "https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python"
10
+ urls.homepage = "https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python#readme"
11
+ urls.documentation = "https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdks/python#readme"
12
+ requires-python = ">=3.8"
13
+ dependencies = [
14
+ "httpx>=0.28.1",
15
+ "httpx-sse>=0.4.0",
16
+ "pydantic>=2.10.3",
17
+ "typing-extensions>=4.12.2",
18
+ ]
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "pytest-asyncio>=0.24.0",
23
+ "pytest-xdist>=3.6.1",
24
+ "pytest>=8.3.4",
25
+ "pyright>=1.1.391",
26
+ "ruff>=0.8.3",
27
+ "sh>=1.14.3",
28
+ ]
29
+
30
+ [tool.pyright]
31
+ pythonVersion = "3.8"
32
+ typeCheckingMode = "strict"
33
+ reportMissingTypeStubs = false
34
+ reportUnnecessaryIsInstance = false
35
+ reportPrivateUsage = false
36
+
37
+ [tool.ruff]
38
+ target-version = "py38"
39
+ line-length = 120
40
+ indent-width = 4
41
+
42
+ [tool.ruff.lint]
43
+ select = ["B", "C4", "E", "F", "I", "W", "UP"]
44
+ ignore = [
45
+ "F401",
46
+ "F403",
47
+ # Needed because unnecessary str() on field aliases are needed
48
+ # https://docs.pydantic.dev/2.8/concepts/fields/#field-aliases
49
+ "UP018",
50
+ ]
51
+
52
+ [tool.ruff.format]
53
+ quote-style = "double"
54
+ indent-style = "space"
55
+
56
+ [tool.pytest.ini_options]
57
+ addopts = "-p no:warnings -n auto"
58
+ xfail_strict = true
59
+ asyncio_mode = "auto"
60
+ asyncio_default_fixture_loop_scope = "function"
61
+
62
+ [build-system]
63
+ requires = ["hatchling"]
64
+ build-backend = "hatchling.build"
@@ -0,0 +1,8 @@
1
+ import sys
2
+
3
+ from sh import ruff # type: ignore
4
+
5
+ files = sys.argv[1:] or ["."]
6
+
7
+ ruff("check", "--fix", *files, _out=sys.stdout)
8
+ ruff("format", *files, _out=sys.stdout)
@@ -0,0 +1,9 @@
1
+ import sys
2
+
3
+ from sh import pyright, ruff # type: ignore
4
+
5
+ files = sys.argv[1:] or ["."]
6
+
7
+ pyright(*files, _out=sys.stdout)
8
+ ruff("check", *files, _out=sys.stdout)
9
+ ruff("format", "--check", *files, _out=sys.stdout)
@@ -0,0 +1,7 @@
1
+ import sys
2
+
3
+ from sh import pytest # type: ignore
4
+
5
+ files = sys.argv[1:] or ["."]
6
+
7
+ pytest(*files, _out=sys.stdout)
@@ -0,0 +1 @@
1
+ from .sdk import *
@@ -0,0 +1,3 @@
1
+ from .client import *
2
+ from .payloads import *
3
+ from .router 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
+ )