to11ai-sdk 0.0.2__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.
- to11ai_sdk-0.0.2/.gitignore +51 -0
- to11ai_sdk-0.0.2/CHANGELOG.md +8 -0
- to11ai_sdk-0.0.2/PKG-INFO +55 -0
- to11ai_sdk-0.0.2/README.md +45 -0
- to11ai_sdk-0.0.2/pyproject.toml +24 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/__init__.py +47 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/client.py +28 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/errors.py +75 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/gateway.py +40 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/prompts/__init__.py +0 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/prompts/resource.py +174 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/prompts/types.py +159 -0
- to11ai_sdk-0.0.2/src/to11ai_sdk/transport.py +140 -0
- to11ai_sdk-0.0.2/tests/__init__.py +0 -0
- to11ai_sdk-0.0.2/tests/test_client.py +23 -0
- to11ai_sdk-0.0.2/tests/test_errors.py +62 -0
- to11ai_sdk-0.0.2/tests/test_gateway.py +52 -0
- to11ai_sdk-0.0.2/tests/test_prompts.py +166 -0
- to11ai_sdk-0.0.2/tests/test_transport.py +195 -0
- to11ai_sdk-0.0.2/uv.lock +156 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.worktrees/
|
|
2
|
+
.worktree/
|
|
3
|
+
.claude/worktrees/
|
|
4
|
+
node_modules/
|
|
5
|
+
dist/
|
|
6
|
+
.turbo/
|
|
7
|
+
test-results/
|
|
8
|
+
package-lock.json
|
|
9
|
+
yarn.lock
|
|
10
|
+
pnpm-lock.yaml
|
|
11
|
+
*.log
|
|
12
|
+
*.tsbuildinfo
|
|
13
|
+
.env
|
|
14
|
+
.env.local
|
|
15
|
+
# Per-environment overrides — never commit; secrets land in these.
|
|
16
|
+
# (`.env.example` is allowlisted by being explicitly tracked.)
|
|
17
|
+
.env.stg
|
|
18
|
+
.env.staging
|
|
19
|
+
.env.production
|
|
20
|
+
.vercel/
|
|
21
|
+
|
|
22
|
+
# Agent skills caches (regenerated from skills-lock.json)
|
|
23
|
+
**/.junie/
|
|
24
|
+
**/.windsurf/
|
|
25
|
+
|
|
26
|
+
# Agent local settings
|
|
27
|
+
.claude/settings.local.json
|
|
28
|
+
.claude/worktrees
|
|
29
|
+
.claude/scheduled_tasks.lock
|
|
30
|
+
|
|
31
|
+
# Entire.io (AI session tracking)
|
|
32
|
+
.entire/settings.local.json
|
|
33
|
+
.entire/metadata/
|
|
34
|
+
.entire/tmp/
|
|
35
|
+
.entire/logs/
|
|
36
|
+
.entire/current_session
|
|
37
|
+
# Husky hook backups the entire plugin writes when it overlays its own
|
|
38
|
+
# wrappers — local-only, regenerated on plugin (re)install.
|
|
39
|
+
**/*.pre-entire
|
|
40
|
+
|
|
41
|
+
# Stray per-example lockfiles. The examples/ workspace ships a single
|
|
42
|
+
# `examples/bun.lock`; per-subfolder lockfiles only appear when someone
|
|
43
|
+
# accidentally runs `bun install` inside an example dir, and they shadow
|
|
44
|
+
# the workspace resolution.
|
|
45
|
+
examples/*/bun.lock
|
|
46
|
+
|
|
47
|
+
# Not tracked: ClickHouse entrypoint injects a plaintext password into this file
|
|
48
|
+
# on every container start, and we don't want those changes committed to git.
|
|
49
|
+
docker/clickhouse/users.d/default-user.xml
|
|
50
|
+
.notes.md
|
|
51
|
+
.playwright-mcp/
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.0.2](https://github.com/to11ai/platform/compare/python-sdk-v0.0.1...python-sdk-v0.0.2) (2026-05-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Documentation
|
|
7
|
+
|
|
8
|
+
* **python-sdk:** add README + wire into pyproject ([#552](https://github.com/to11ai/platform/issues/552)) ([0e0d48f](https://github.com/to11ai/platform/commit/0e0d48febbdbcaa229bc0d076111c0f7d782d5bf))
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: to11ai-sdk
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Python SDK for the to11.ai platform
|
|
5
|
+
Project-URL: Homepage, https://to11.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/to11ai/platform
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# to11ai-sdk
|
|
12
|
+
|
|
13
|
+
Python SDK for [to11.ai](https://to11.ai) — the AI engineering platform for prompt management, tracing, and evals.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install to11ai-sdk
|
|
19
|
+
# or with uv
|
|
20
|
+
uv add to11ai-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Getting started
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from to11ai_sdk import create_client
|
|
27
|
+
|
|
28
|
+
client = create_client(
|
|
29
|
+
base_url="https://api.to11.ai",
|
|
30
|
+
api_key="...",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
result = client.prompts.resolve(
|
|
34
|
+
project_id="proj_abc123",
|
|
35
|
+
slug="chat-system-prompt",
|
|
36
|
+
labels=["production"],
|
|
37
|
+
variables={"user_name": "Alice"},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
print(result.rendered.text) # "Hello, Alice!"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- Prompt resolution with label-based version selection and Liquid template rendering
|
|
46
|
+
- Typed errors for auth, rate limiting, validation, and network failures
|
|
47
|
+
- Compatible with Python 3.10+
|
|
48
|
+
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
Full API reference and guides: <https://docs.to11.ai>
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# to11ai-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for [to11.ai](https://to11.ai) — the AI engineering platform for prompt management, tracing, and evals.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install to11ai-sdk
|
|
9
|
+
# or with uv
|
|
10
|
+
uv add to11ai-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Getting started
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from to11ai_sdk import create_client
|
|
17
|
+
|
|
18
|
+
client = create_client(
|
|
19
|
+
base_url="https://api.to11.ai",
|
|
20
|
+
api_key="...",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
result = client.prompts.resolve(
|
|
24
|
+
project_id="proj_abc123",
|
|
25
|
+
slug="chat-system-prompt",
|
|
26
|
+
labels=["production"],
|
|
27
|
+
variables={"user_name": "Alice"},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
print(result.rendered.text) # "Hello, Alice!"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- Prompt resolution with label-based version selection and Liquid template rendering
|
|
36
|
+
- Typed errors for auth, rate limiting, validation, and network failures
|
|
37
|
+
- Compatible with Python 3.10+
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
Full API reference and guides: <https://docs.to11.ai>
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "to11ai-sdk"
|
|
7
|
+
version = "0.0.2"
|
|
8
|
+
description = "Python SDK for the to11.ai platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://to11.ai"
|
|
16
|
+
Repository = "https://github.com/to11ai/platform"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["src/to11ai_sdk"]
|
|
20
|
+
|
|
21
|
+
[tool.uv]
|
|
22
|
+
dev-dependencies = [
|
|
23
|
+
"pytest>=8.0",
|
|
24
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from .client import To11aiClient, create_client
|
|
2
|
+
from .errors import (
|
|
3
|
+
To11aiApiError,
|
|
4
|
+
To11aiAuthError,
|
|
5
|
+
To11aiError,
|
|
6
|
+
To11aiNetworkError,
|
|
7
|
+
To11aiRateLimitError,
|
|
8
|
+
To11aiValidationError,
|
|
9
|
+
)
|
|
10
|
+
from .gateway import gateway_auth_headers, gateway_prompt_headers
|
|
11
|
+
from .prompts.resource import PromptResource
|
|
12
|
+
from .prompts.types import (
|
|
13
|
+
ChatMessage,
|
|
14
|
+
ChatRendered,
|
|
15
|
+
PaginatedResult,
|
|
16
|
+
PaginationInfo,
|
|
17
|
+
Prompt,
|
|
18
|
+
PromptLabelPointer,
|
|
19
|
+
PromptVersion,
|
|
20
|
+
RenderedPrompt,
|
|
21
|
+
ResolvedPrompt,
|
|
22
|
+
TextRendered,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"create_client",
|
|
27
|
+
"To11aiClient",
|
|
28
|
+
"PromptResource",
|
|
29
|
+
"gateway_auth_headers",
|
|
30
|
+
"gateway_prompt_headers",
|
|
31
|
+
"ResolvedPrompt",
|
|
32
|
+
"Prompt",
|
|
33
|
+
"PromptVersion",
|
|
34
|
+
"PromptLabelPointer",
|
|
35
|
+
"PaginatedResult",
|
|
36
|
+
"PaginationInfo",
|
|
37
|
+
"ChatMessage",
|
|
38
|
+
"TextRendered",
|
|
39
|
+
"ChatRendered",
|
|
40
|
+
"RenderedPrompt",
|
|
41
|
+
"To11aiError",
|
|
42
|
+
"To11aiApiError",
|
|
43
|
+
"To11aiAuthError",
|
|
44
|
+
"To11aiNetworkError",
|
|
45
|
+
"To11aiRateLimitError",
|
|
46
|
+
"To11aiValidationError",
|
|
47
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .prompts.resource import PromptResource
|
|
6
|
+
from .transport import Transport, TransportConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class To11aiClient:
|
|
11
|
+
prompts: PromptResource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_client(
|
|
15
|
+
*,
|
|
16
|
+
api_key: str,
|
|
17
|
+
base_url: str,
|
|
18
|
+
timeout_s: float = 30.0,
|
|
19
|
+
max_retries: int = 3,
|
|
20
|
+
ttl_seconds: int = 300,
|
|
21
|
+
max_cache_entries: int = 100,
|
|
22
|
+
) -> To11aiClient:
|
|
23
|
+
if not api_key:
|
|
24
|
+
raise ValueError("api_key must not be empty")
|
|
25
|
+
if not base_url:
|
|
26
|
+
raise ValueError("base_url must not be empty")
|
|
27
|
+
transport = Transport(TransportConfig(base_url=base_url, api_key=api_key, timeout_s=timeout_s, max_retries=max_retries))
|
|
28
|
+
return To11aiClient(prompts=PromptResource(transport, ttl_seconds=ttl_seconds, max_cache_entries=max_cache_entries))
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class To11aiError(Exception):
|
|
7
|
+
"""Base class for all to11ai SDK errors."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class To11aiApiError(To11aiError):
|
|
11
|
+
"""Raised when the API returns a non-2xx response."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
message: str,
|
|
16
|
+
*,
|
|
17
|
+
status_code: int,
|
|
18
|
+
error_code: Optional[str] = None,
|
|
19
|
+
details: Any = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
self.status_code = status_code
|
|
23
|
+
self.error_code = error_code
|
|
24
|
+
self.details = details
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class To11aiAuthError(To11aiApiError):
|
|
28
|
+
"""Raised on 401 or 403 responses."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
message: str,
|
|
33
|
+
*,
|
|
34
|
+
status_code: int,
|
|
35
|
+
error_code: Optional[str] = None,
|
|
36
|
+
details: Any = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(message, status_code=status_code, error_code=error_code, details=details)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class To11aiRateLimitError(To11aiApiError):
|
|
42
|
+
"""Raised on 429 responses."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
message: str,
|
|
47
|
+
*,
|
|
48
|
+
retry_after_seconds: Optional[float] = None,
|
|
49
|
+
error_code: Optional[str] = None,
|
|
50
|
+
details: Any = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
super().__init__(message, status_code=429, error_code=error_code, details=details)
|
|
53
|
+
self.retry_after_seconds = retry_after_seconds
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class To11aiValidationError(To11aiApiError):
|
|
57
|
+
"""Raised on 400 responses."""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
message: str,
|
|
62
|
+
*,
|
|
63
|
+
error_code: Optional[str] = None,
|
|
64
|
+
details: Any = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
super().__init__(message, status_code=400, error_code=error_code, details=details)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class To11aiNetworkError(To11aiError):
|
|
70
|
+
"""Raised on network failure or timeout."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, message: str, *, cause: Optional[BaseException] = None) -> None:
|
|
73
|
+
super().__init__(message)
|
|
74
|
+
if cause is not None:
|
|
75
|
+
self.__cause__ = cause
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .prompts.types import ResolvedPrompt
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def gateway_auth_headers(*, api_key: str, project_id: str) -> dict[str, str]:
|
|
7
|
+
"""
|
|
8
|
+
Build auth headers required for every request through the to11 gateway.
|
|
9
|
+
|
|
10
|
+
Example::
|
|
11
|
+
|
|
12
|
+
headers = gateway_auth_headers(api_key=api_key, project_id=project_id)
|
|
13
|
+
response = anthropic_client.messages.create(..., extra_headers=headers)
|
|
14
|
+
"""
|
|
15
|
+
return {
|
|
16
|
+
"x-to11-authorization": f"Bearer {api_key}",
|
|
17
|
+
"x-to11-project-id": project_id,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def gateway_prompt_headers(prompt: ResolvedPrompt) -> dict[str, str]:
|
|
22
|
+
"""
|
|
23
|
+
Build prompt provenance headers from a resolved prompt.
|
|
24
|
+
|
|
25
|
+
Omits ``x-to11-prompt-labels`` when the prompt has no labels —
|
|
26
|
+
the gateway rejects empty header values.
|
|
27
|
+
|
|
28
|
+
Example::
|
|
29
|
+
|
|
30
|
+
prompt = client.prompts.resolve(project_id=..., slug="my-prompt", labels=["production"])
|
|
31
|
+
headers = gateway_prompt_headers(prompt)
|
|
32
|
+
response = anthropic_client.messages.create(..., extra_headers=headers)
|
|
33
|
+
"""
|
|
34
|
+
headers: dict[str, str] = {
|
|
35
|
+
"x-to11-prompt-id": prompt.prompt_id,
|
|
36
|
+
"x-to11-prompt-version": str(prompt.version),
|
|
37
|
+
}
|
|
38
|
+
if prompt.labels:
|
|
39
|
+
headers["x-to11-prompt-labels"] = ",".join(prompt.labels)
|
|
40
|
+
return headers
|
|
File without changes
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
import time as _time
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
from ..errors import To11aiNetworkError
|
|
11
|
+
from ..transport import Transport
|
|
12
|
+
from .types import (
|
|
13
|
+
PaginatedResult, PaginationInfo, Prompt, PromptLabelPointer,
|
|
14
|
+
PromptVersion, ResolvedPrompt,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
DEFAULT_TTL_SECONDS = 300
|
|
18
|
+
DEFAULT_MAX_ENTRIES = 100
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _build_cache_key(project_id: str, slug: str, labels: list[str], variables: Optional[dict[str, Any]] = None) -> str:
|
|
22
|
+
def _sort(obj: Any) -> Any:
|
|
23
|
+
if isinstance(obj, dict):
|
|
24
|
+
return {k: _sort(v) for k, v in sorted(obj.items())}
|
|
25
|
+
if isinstance(obj, list):
|
|
26
|
+
return [_sort(v) for v in obj]
|
|
27
|
+
return obj
|
|
28
|
+
vars_part = json.dumps(_sort(variables), sort_keys=True) if variables is not None else None
|
|
29
|
+
return f"{json.dumps(project_id)}|{json.dumps(slug)}|{json.dumps(sorted(labels))}|{json.dumps(vars_part)}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class _CacheEntry:
|
|
33
|
+
__slots__ = ("expires_at", "value")
|
|
34
|
+
def __init__(self, value: ResolvedPrompt, expires_at: float) -> None:
|
|
35
|
+
self.value = value
|
|
36
|
+
self.expires_at = expires_at
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _Cache:
|
|
40
|
+
def __init__(self, ttl_s: int, max_entries: int) -> None:
|
|
41
|
+
self._ttl = ttl_s
|
|
42
|
+
self._max = max_entries
|
|
43
|
+
self._store: dict[str, _CacheEntry] = {}
|
|
44
|
+
self._lock = threading.Lock()
|
|
45
|
+
|
|
46
|
+
def get(self, key: str) -> Optional[ResolvedPrompt]:
|
|
47
|
+
with self._lock:
|
|
48
|
+
entry = self._store.get(key)
|
|
49
|
+
if entry is None:
|
|
50
|
+
return None
|
|
51
|
+
if _time.time() > entry.expires_at:
|
|
52
|
+
self._store.pop(key, None)
|
|
53
|
+
return None
|
|
54
|
+
del self._store[key]
|
|
55
|
+
self._store[key] = entry
|
|
56
|
+
return copy.deepcopy(entry.value)
|
|
57
|
+
|
|
58
|
+
def set(self, key: str, value: ResolvedPrompt) -> None:
|
|
59
|
+
if self._max <= 0:
|
|
60
|
+
return
|
|
61
|
+
with self._lock:
|
|
62
|
+
self._store.pop(key, None)
|
|
63
|
+
while len(self._store) >= self._max:
|
|
64
|
+
del self._store[next(iter(self._store))]
|
|
65
|
+
self._store[key] = _CacheEntry(value=copy.deepcopy(value), expires_at=_time.time() + self._ttl)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _prompts_path(project_id: str) -> str:
|
|
69
|
+
return f"/v1/projects/{quote(project_id, safe='')}/prompts"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _prompt_path(project_id: str, prompt_id: str) -> str:
|
|
73
|
+
return f"{_prompts_path(project_id)}/{quote(prompt_id, safe='')}"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _unwrap(envelope: dict[str, Any]) -> Any:
|
|
77
|
+
data = envelope.get("data")
|
|
78
|
+
if data is None:
|
|
79
|
+
raise RuntimeError("API returned success but data is null")
|
|
80
|
+
return data
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class PromptResource:
|
|
84
|
+
def __init__(self, transport: Transport, ttl_seconds: int = DEFAULT_TTL_SECONDS, max_cache_entries: int = DEFAULT_MAX_ENTRIES) -> None:
|
|
85
|
+
self._transport = transport
|
|
86
|
+
self._cache = _Cache(ttl_s=ttl_seconds, max_entries=max_cache_entries)
|
|
87
|
+
|
|
88
|
+
def create(self, *, project_id: str, name: str, slug: str, description: Optional[str] = None, tags: Optional[list[str]] = None) -> Prompt:
|
|
89
|
+
body: dict[str, Any] = {"name": name, "slug": slug}
|
|
90
|
+
if description is not None:
|
|
91
|
+
body["description"] = description
|
|
92
|
+
if tags is not None:
|
|
93
|
+
body["tags"] = tags
|
|
94
|
+
return Prompt.from_dict(_unwrap(self._transport.request("POST", _prompts_path(project_id), body=body)))
|
|
95
|
+
|
|
96
|
+
def list(self, *, project_id: str, status: Optional[str] = None, sort_by: Optional[str] = None, tags: Optional[str] = None, page: Optional[int] = None, limit: Optional[int] = None) -> PaginatedResult:
|
|
97
|
+
query: dict[str, Any] = {}
|
|
98
|
+
if status is not None:
|
|
99
|
+
query["status"] = status
|
|
100
|
+
if sort_by is not None:
|
|
101
|
+
query["sortBy"] = sort_by
|
|
102
|
+
if tags is not None:
|
|
103
|
+
query["tags"] = tags
|
|
104
|
+
if page is not None:
|
|
105
|
+
query["page"] = page
|
|
106
|
+
if limit is not None:
|
|
107
|
+
query["limit"] = limit
|
|
108
|
+
envelope = self._transport.request("GET", _prompts_path(project_id), query=query)
|
|
109
|
+
raw = _unwrap(envelope)
|
|
110
|
+
if isinstance(raw, list):
|
|
111
|
+
items = [Prompt.from_dict(d) for d in raw]
|
|
112
|
+
pag_data = envelope.get("pagination") or {"page": 1, "limit": len(items), "total": len(items), "totalPages": 1}
|
|
113
|
+
else:
|
|
114
|
+
items = [Prompt.from_dict(d) for d in raw.get("items", [])]
|
|
115
|
+
pag_data = envelope.get("pagination") or raw.get("pagination") or {"page": 1, "limit": len(items), "total": len(items), "totalPages": 1}
|
|
116
|
+
return PaginatedResult(items=items, pagination=PaginationInfo.from_dict(pag_data))
|
|
117
|
+
|
|
118
|
+
def get(self, *, project_id: str, prompt_id: str) -> Prompt:
|
|
119
|
+
return Prompt.from_dict(_unwrap(self._transport.request("GET", _prompt_path(project_id, prompt_id))))
|
|
120
|
+
|
|
121
|
+
def update(self, *, project_id: str, prompt_id: str, name: Optional[str] = None, description: Optional[str] = None, tags: Optional[list[str]] = None) -> Prompt:
|
|
122
|
+
body: dict[str, Any] = {}
|
|
123
|
+
if name is not None:
|
|
124
|
+
body["name"] = name
|
|
125
|
+
if description is not None:
|
|
126
|
+
body["description"] = description
|
|
127
|
+
if tags is not None:
|
|
128
|
+
body["tags"] = tags
|
|
129
|
+
return Prompt.from_dict(_unwrap(self._transport.request("PATCH", _prompt_path(project_id, prompt_id), body=body)))
|
|
130
|
+
|
|
131
|
+
def archive(self, *, project_id: str, prompt_id: str) -> Prompt:
|
|
132
|
+
return Prompt.from_dict(_unwrap(self._transport.request("DELETE", _prompt_path(project_id, prompt_id))))
|
|
133
|
+
|
|
134
|
+
def create_version(self, *, project_id: str, prompt_id: str, format: str, template_json: Any, variables_schema: Any, model_config: Any = None, changelog: Optional[str] = None) -> PromptVersion: # noqa: A002
|
|
135
|
+
body: dict[str, Any] = {"format": format, "templateJson": template_json, "variablesSchema": variables_schema}
|
|
136
|
+
if model_config is not None:
|
|
137
|
+
body["modelConfig"] = model_config
|
|
138
|
+
if changelog is not None:
|
|
139
|
+
body["changelog"] = changelog
|
|
140
|
+
return PromptVersion.from_dict(_unwrap(self._transport.request("POST", f"{_prompt_path(project_id, prompt_id)}/versions", body=body)))
|
|
141
|
+
|
|
142
|
+
def list_versions(self, *, project_id: str, prompt_id: str) -> list[PromptVersion]:
|
|
143
|
+
return [PromptVersion.from_dict(d) for d in _unwrap(self._transport.request("GET", f"{_prompt_path(project_id, prompt_id)}/versions"))]
|
|
144
|
+
|
|
145
|
+
def get_version(self, *, project_id: str, prompt_id: str, version_number: int) -> PromptVersion:
|
|
146
|
+
return PromptVersion.from_dict(_unwrap(self._transport.request("GET", f"{_prompt_path(project_id, prompt_id)}/versions/{quote(str(version_number), safe='')}")))
|
|
147
|
+
|
|
148
|
+
def move_label(self, *, project_id: str, prompt_id: str, label: str, version_id: str, reason: Optional[str] = None) -> PromptLabelPointer:
|
|
149
|
+
body: dict[str, Any] = {"versionId": version_id}
|
|
150
|
+
if reason is not None:
|
|
151
|
+
body["reason"] = reason
|
|
152
|
+
return PromptLabelPointer.from_dict(_unwrap(self._transport.request("PUT", f"{_prompt_path(project_id, prompt_id)}/labels/{quote(label, safe='')}", body=body)))
|
|
153
|
+
|
|
154
|
+
def list_labels(self, *, project_id: str, prompt_id: str) -> list[PromptLabelPointer]:
|
|
155
|
+
return [PromptLabelPointer.from_dict(d) for d in _unwrap(self._transport.request("GET", f"{_prompt_path(project_id, prompt_id)}/labels"))]
|
|
156
|
+
|
|
157
|
+
def resolve(self, *, project_id: str, slug: str, labels: list[str], variables: Optional[dict[str, Any]] = None, cache: bool = True, fallback: Optional[ResolvedPrompt] = None) -> ResolvedPrompt:
|
|
158
|
+
key = _build_cache_key(project_id, slug, labels, variables)
|
|
159
|
+
if cache:
|
|
160
|
+
cached = self._cache.get(key)
|
|
161
|
+
if cached is not None:
|
|
162
|
+
return cached
|
|
163
|
+
try:
|
|
164
|
+
body: dict[str, Any] = {"slug": slug, "labels": labels}
|
|
165
|
+
if variables is not None:
|
|
166
|
+
body["variables"] = variables
|
|
167
|
+
result = ResolvedPrompt.from_dict(_unwrap(self._transport.request("POST", f"{_prompts_path(project_id)}:resolve", body=body)))
|
|
168
|
+
if cache:
|
|
169
|
+
self._cache.set(key, result)
|
|
170
|
+
return result
|
|
171
|
+
except To11aiNetworkError:
|
|
172
|
+
if fallback is not None:
|
|
173
|
+
return fallback
|
|
174
|
+
raise
|