creativai 0.1.0__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.
- creativai/__init__.py +63 -0
- creativai/_http.py +208 -0
- creativai/client.py +161 -0
- creativai/exceptions.py +78 -0
- creativai/resources/__init__.py +53 -0
- creativai/resources/agentic_chat.py +122 -0
- creativai/resources/chat.py +44 -0
- creativai/resources/collections.py +58 -0
- creativai/resources/data_plates.py +174 -0
- creativai/resources/health.py +20 -0
- creativai/resources/indexing.py +77 -0
- creativai/resources/invoices.py +26 -0
- creativai/resources/jobs.py +36 -0
- creativai/resources/knowledge_extraction.py +88 -0
- creativai/resources/live_stream.py +143 -0
- creativai/resources/media.py +65 -0
- creativai/resources/online_search.py +38 -0
- creativai/resources/organizations.py +28 -0
- creativai/resources/projects.py +31 -0
- creativai/resources/search.py +60 -0
- creativai/resources/sharing.py +161 -0
- creativai/resources/sub_plates.py +83 -0
- creativai/resources/subscriptions.py +54 -0
- creativai/resources/tasks.py +62 -0
- creativai/resources/transactions.py +50 -0
- creativai/resources/transfers.py +33 -0
- creativai/resources/upload_integrations.py +146 -0
- creativai/resources/uploads.py +45 -0
- creativai/resources/users.py +50 -0
- creativai/resources/youtube_search.py +59 -0
- creativai-0.1.0.dist-info/METADATA +473 -0
- creativai-0.1.0.dist-info/RECORD +34 -0
- creativai-0.1.0.dist-info/WHEEL +5 -0
- creativai-0.1.0.dist-info/top_level.txt +1 -0
creativai/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CreativAI — Official Python SDK for the CreativAI Video Intelligence Platform.
|
|
3
|
+
|
|
4
|
+
Quickstart::
|
|
5
|
+
|
|
6
|
+
import creativai
|
|
7
|
+
|
|
8
|
+
client = creativai.CreativAI(api_key="sk_live_...")
|
|
9
|
+
|
|
10
|
+
# Or use the environment variable CREATIVAI_API_KEY:
|
|
11
|
+
client = creativai.CreativAI()
|
|
12
|
+
|
|
13
|
+
# Health check
|
|
14
|
+
client.health.check()
|
|
15
|
+
|
|
16
|
+
# Account info
|
|
17
|
+
info = client.users.get_users_info()
|
|
18
|
+
print(info["credits"])
|
|
19
|
+
|
|
20
|
+
# Create a collection and upload a video
|
|
21
|
+
collection = client.collections.create("my-collection")
|
|
22
|
+
cid = collection["collection_id"]
|
|
23
|
+
client.media.upload_file(cid, "/path/to/video.mp4")
|
|
24
|
+
|
|
25
|
+
# Start indexing
|
|
26
|
+
job = client.indexing.start(cid)
|
|
27
|
+
|
|
28
|
+
# Search
|
|
29
|
+
results = client.search.query(cid, "person near forklift")
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from .client import CreativAI
|
|
33
|
+
from .exceptions import (
|
|
34
|
+
APIError,
|
|
35
|
+
AuthenticationError,
|
|
36
|
+
CreativAIError,
|
|
37
|
+
InsufficientCreditsError,
|
|
38
|
+
NotFoundError,
|
|
39
|
+
PermissionError,
|
|
40
|
+
RateLimitError,
|
|
41
|
+
ServerError,
|
|
42
|
+
StreamingError,
|
|
43
|
+
TimeoutError,
|
|
44
|
+
ValidationError,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"CreativAI",
|
|
49
|
+
# Exceptions
|
|
50
|
+
"CreativAIError",
|
|
51
|
+
"APIError",
|
|
52
|
+
"AuthenticationError",
|
|
53
|
+
"PermissionError",
|
|
54
|
+
"NotFoundError",
|
|
55
|
+
"RateLimitError",
|
|
56
|
+
"InsufficientCreditsError",
|
|
57
|
+
"ValidationError",
|
|
58
|
+
"ServerError",
|
|
59
|
+
"TimeoutError",
|
|
60
|
+
"StreamingError",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
__version__ = "0.1.0"
|
creativai/_http.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CreativAI Python SDK — internal HTTP layer
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Generator, Iterator
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
APIError,
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
InsufficientCreditsError,
|
|
16
|
+
NotFoundError,
|
|
17
|
+
PermissionError,
|
|
18
|
+
RateLimitError,
|
|
19
|
+
ServerError,
|
|
20
|
+
StreamingError,
|
|
21
|
+
ValidationError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_DEFAULT_BASE_URL = "https://creativai-apis.com"
|
|
25
|
+
_DEFAULT_TIMEOUT = 120.0 # seconds
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _HTTPClient:
|
|
29
|
+
"""Thin wrapper around httpx that handles auth, error parsing, and SSE."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
api_key: str,
|
|
34
|
+
base_url: str = _DEFAULT_BASE_URL,
|
|
35
|
+
timeout: float = _DEFAULT_TIMEOUT,
|
|
36
|
+
) -> None:
|
|
37
|
+
self._api_key = api_key
|
|
38
|
+
self._base_url = base_url.rstrip("/")
|
|
39
|
+
try:
|
|
40
|
+
from importlib.metadata import version as _pkg_version
|
|
41
|
+
_sdk_version = _pkg_version("creativai")
|
|
42
|
+
except Exception:
|
|
43
|
+
_sdk_version = "0.1.0"
|
|
44
|
+
self._client = httpx.Client(
|
|
45
|
+
base_url=self._base_url,
|
|
46
|
+
headers={
|
|
47
|
+
"X-API-Key": api_key,
|
|
48
|
+
"Accept": "application/json",
|
|
49
|
+
"User-Agent": f"creativai-python/{_sdk_version}",
|
|
50
|
+
},
|
|
51
|
+
timeout=timeout,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
# Public request helpers
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
def get(self, path: str, *, params: dict[str, Any] | None = None) -> Any:
|
|
59
|
+
response = self._client.get(path, params=params)
|
|
60
|
+
return self._parse(response)
|
|
61
|
+
|
|
62
|
+
def post(
|
|
63
|
+
self,
|
|
64
|
+
path: str,
|
|
65
|
+
*,
|
|
66
|
+
json: Any = None,
|
|
67
|
+
data: dict[str, Any] | None = None,
|
|
68
|
+
files: Any = None,
|
|
69
|
+
params: dict[str, Any] | None = None,
|
|
70
|
+
) -> Any:
|
|
71
|
+
response = self._client.post(path, json=json, data=data, files=files, params=params)
|
|
72
|
+
return self._parse(response)
|
|
73
|
+
|
|
74
|
+
def patch(self, path: str, *, json: Any = None) -> Any:
|
|
75
|
+
response = self._client.patch(path, json=json)
|
|
76
|
+
return self._parse(response)
|
|
77
|
+
|
|
78
|
+
def delete(self, path: str, *, json: Any = None, params: dict[str, Any] | None = None) -> Any:
|
|
79
|
+
response = self._client.delete(path, json=json, params=params)
|
|
80
|
+
return self._parse(response)
|
|
81
|
+
|
|
82
|
+
def put(self, path: str, *, json: Any = None, content: bytes | None = None, headers: dict[str, str] | None = None) -> Any:
|
|
83
|
+
"""Used for presigned S3 PUT uploads — does NOT send API key."""
|
|
84
|
+
extra_headers = headers or {}
|
|
85
|
+
response = httpx.put(path, json=json, content=content, headers=extra_headers, timeout=_DEFAULT_TIMEOUT)
|
|
86
|
+
if response.status_code >= 400:
|
|
87
|
+
raise APIError(
|
|
88
|
+
f"S3 upload failed: HTTP {response.status_code}",
|
|
89
|
+
status_code=response.status_code,
|
|
90
|
+
)
|
|
91
|
+
return response
|
|
92
|
+
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
# SSE streaming
|
|
95
|
+
# ------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
def stream_post(
|
|
98
|
+
self, path: str, *, json: Any = None
|
|
99
|
+
) -> Iterator[dict[str, str]]:
|
|
100
|
+
"""
|
|
101
|
+
Opens an SSE connection via POST and yields raw SSE event dicts
|
|
102
|
+
with keys ``event`` and ``data``.
|
|
103
|
+
|
|
104
|
+
Usage::
|
|
105
|
+
|
|
106
|
+
for event in client.stream_post("/api/v2/agentic-chat/sessions/xxx/chat", json={...}):
|
|
107
|
+
print(event["event"], event["data"])
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
from httpx_sse import connect_sse
|
|
111
|
+
except ImportError as exc:
|
|
112
|
+
raise StreamingError(
|
|
113
|
+
"httpx-sse is required for streaming. "
|
|
114
|
+
"Install it with: pip install httpx-sse"
|
|
115
|
+
) from exc
|
|
116
|
+
|
|
117
|
+
headers = {
|
|
118
|
+
"X-API-Key": self._api_key,
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
"Accept": "text/event-stream",
|
|
121
|
+
"User-Agent": "creativai-python/0.1.0",
|
|
122
|
+
}
|
|
123
|
+
try:
|
|
124
|
+
with httpx.Client(base_url=self._base_url, timeout=None) as client:
|
|
125
|
+
with connect_sse(client, "POST", path, json=json, headers=headers) as event_source:
|
|
126
|
+
for sse in event_source.iter_sse():
|
|
127
|
+
yield {"event": sse.event, "data": sse.data}
|
|
128
|
+
except httpx.HTTPStatusError as exc:
|
|
129
|
+
self._raise_for_status(exc.response)
|
|
130
|
+
except httpx.TimeoutException as exc:
|
|
131
|
+
from .exceptions import TimeoutError as _TimeoutError
|
|
132
|
+
raise _TimeoutError(str(exc)) from exc
|
|
133
|
+
|
|
134
|
+
def stream_get(self, path: str, *, params: dict[str, Any] | None = None) -> Iterator[dict[str, str]]:
|
|
135
|
+
"""Opens an SSE connection via GET and yields raw SSE event dicts."""
|
|
136
|
+
try:
|
|
137
|
+
from httpx_sse import connect_sse
|
|
138
|
+
except ImportError as exc:
|
|
139
|
+
raise StreamingError(
|
|
140
|
+
"httpx-sse is required for streaming. "
|
|
141
|
+
"Install it with: pip install httpx-sse"
|
|
142
|
+
) from exc
|
|
143
|
+
|
|
144
|
+
headers = {
|
|
145
|
+
"X-API-Key": self._api_key,
|
|
146
|
+
"Accept": "text/event-stream",
|
|
147
|
+
"User-Agent": "creativai-python/0.1.0",
|
|
148
|
+
}
|
|
149
|
+
try:
|
|
150
|
+
with httpx.Client(base_url=self._base_url, timeout=None) as client:
|
|
151
|
+
with connect_sse(client, "GET", path, params=params, headers=headers) as event_source:
|
|
152
|
+
for sse in event_source.iter_sse():
|
|
153
|
+
yield {"event": sse.event, "data": sse.data}
|
|
154
|
+
except httpx.HTTPStatusError as exc:
|
|
155
|
+
self._raise_for_status(exc.response)
|
|
156
|
+
except httpx.TimeoutException as exc:
|
|
157
|
+
from .exceptions import TimeoutError as _TimeoutError
|
|
158
|
+
raise _TimeoutError(str(exc)) from exc
|
|
159
|
+
|
|
160
|
+
# ------------------------------------------------------------------
|
|
161
|
+
# Internal helpers
|
|
162
|
+
# ------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def _parse(self, response: httpx.Response) -> Any:
|
|
165
|
+
if response.status_code >= 400:
|
|
166
|
+
self._raise_for_status(response)
|
|
167
|
+
try:
|
|
168
|
+
body = response.json()
|
|
169
|
+
except Exception:
|
|
170
|
+
return response.content
|
|
171
|
+
# Unwrap the standard {"success": true, "data": ..., "error": ...} envelope
|
|
172
|
+
if isinstance(body, dict) and "data" in body:
|
|
173
|
+
return body["data"]
|
|
174
|
+
return body
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
178
|
+
status = response.status_code
|
|
179
|
+
try:
|
|
180
|
+
body = response.json()
|
|
181
|
+
err = body.get("error") or {}
|
|
182
|
+
message = err.get("message") or body.get("detail") or response.text
|
|
183
|
+
code = err.get("code")
|
|
184
|
+
details = err.get("details")
|
|
185
|
+
except Exception:
|
|
186
|
+
message = response.text
|
|
187
|
+
code = None
|
|
188
|
+
details = None
|
|
189
|
+
|
|
190
|
+
exc_map = {
|
|
191
|
+
401: AuthenticationError,
|
|
192
|
+
402: InsufficientCreditsError,
|
|
193
|
+
403: PermissionError,
|
|
194
|
+
404: NotFoundError,
|
|
195
|
+
422: ValidationError,
|
|
196
|
+
429: RateLimitError,
|
|
197
|
+
}
|
|
198
|
+
cls = exc_map.get(status) or (ServerError if status >= 500 else ValidationError if status == 400 else APIError)
|
|
199
|
+
raise cls(message, status_code=status, code=code, details=details)
|
|
200
|
+
|
|
201
|
+
def close(self) -> None:
|
|
202
|
+
self._client.close()
|
|
203
|
+
|
|
204
|
+
def __enter__(self) -> "_HTTPClient":
|
|
205
|
+
return self
|
|
206
|
+
|
|
207
|
+
def __exit__(self, *args: Any) -> None:
|
|
208
|
+
self.close()
|
creativai/client.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CreativAI Python SDK — Main client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from ._http import _HTTPClient, _DEFAULT_BASE_URL, _DEFAULT_TIMEOUT
|
|
11
|
+
from .resources import (
|
|
12
|
+
AgenticChat,
|
|
13
|
+
Chat,
|
|
14
|
+
Collections,
|
|
15
|
+
DataPlates,
|
|
16
|
+
Health,
|
|
17
|
+
Indexing,
|
|
18
|
+
Invoices,
|
|
19
|
+
Jobs,
|
|
20
|
+
KnowledgeExtraction,
|
|
21
|
+
LiveStream,
|
|
22
|
+
Media,
|
|
23
|
+
OnlineSearch,
|
|
24
|
+
Organizations,
|
|
25
|
+
Projects,
|
|
26
|
+
Search,
|
|
27
|
+
Sharing,
|
|
28
|
+
SubPlates,
|
|
29
|
+
Subscriptions,
|
|
30
|
+
Tasks,
|
|
31
|
+
Transactions,
|
|
32
|
+
Transfers,
|
|
33
|
+
UploadIntegrations,
|
|
34
|
+
Uploads,
|
|
35
|
+
Users,
|
|
36
|
+
YouTubeSearch,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CreativAI:
|
|
41
|
+
"""The main entry point for the CreativAI Python SDK.
|
|
42
|
+
|
|
43
|
+
Instantiate with your API key (or set the ``CREATIVAI_API_KEY`` environment
|
|
44
|
+
variable) and start using the namespaced resource clients.
|
|
45
|
+
|
|
46
|
+
Example::
|
|
47
|
+
|
|
48
|
+
import creativai
|
|
49
|
+
|
|
50
|
+
client = creativai.CreativAI(api_key="sk_live_...")
|
|
51
|
+
|
|
52
|
+
# Create a collection
|
|
53
|
+
collection = client.collections.create("my-videos")
|
|
54
|
+
cid = collection["collection_id"]
|
|
55
|
+
|
|
56
|
+
# Upload a local file
|
|
57
|
+
client.media.upload_file(cid, "clip.mp4")
|
|
58
|
+
|
|
59
|
+
# Index it
|
|
60
|
+
job = client.indexing.start(cid)
|
|
61
|
+
print(job["indexing_id"])
|
|
62
|
+
|
|
63
|
+
# Search
|
|
64
|
+
results = client.search.query(cid, "person walking near forklift")
|
|
65
|
+
|
|
66
|
+
# Agentic chat
|
|
67
|
+
session = client.agentic_chat.create_session(cid)
|
|
68
|
+
for event in client.agentic_chat.chat(session["session_id"], "What are the most common activities?"):
|
|
69
|
+
if event["event"] == "answer":
|
|
70
|
+
print(event["data"].get("text", ""))
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
api_key: Your CreativAI API key (``sk_live_...``). Falls back to the
|
|
74
|
+
``CREATIVAI_API_KEY`` environment variable.
|
|
75
|
+
base_url: Override the default API base URL.
|
|
76
|
+
timeout: Request timeout in seconds (default 120).
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
api_key: str | None = None,
|
|
82
|
+
*,
|
|
83
|
+
base_url: str = _DEFAULT_BASE_URL,
|
|
84
|
+
timeout: float = _DEFAULT_TIMEOUT,
|
|
85
|
+
) -> None:
|
|
86
|
+
resolved_key = api_key or os.environ.get("CREATIVAI_API_KEY")
|
|
87
|
+
if not resolved_key:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
"An API key is required. Pass api_key= or set the CREATIVAI_API_KEY "
|
|
90
|
+
"environment variable. Keys begin with 'sk_live_'."
|
|
91
|
+
)
|
|
92
|
+
self._http = _HTTPClient(resolved_key, base_url=base_url, timeout=timeout)
|
|
93
|
+
|
|
94
|
+
# Resource clients — each maps to a logical section of the API
|
|
95
|
+
self.health = Health(self._http)
|
|
96
|
+
self.organizations = Organizations(self._http)
|
|
97
|
+
self.projects = Projects(self._http)
|
|
98
|
+
self.collections = Collections(self._http)
|
|
99
|
+
self.media = Media(self._http)
|
|
100
|
+
self.uploads = Uploads(self._http)
|
|
101
|
+
self.transfers = Transfers(self._http)
|
|
102
|
+
self.indexing = Indexing(self._http)
|
|
103
|
+
self.search = Search(self._http)
|
|
104
|
+
self.data_plates = DataPlates(self._http)
|
|
105
|
+
self.sub_plates = SubPlates(self._http)
|
|
106
|
+
self.knowledge_extraction = KnowledgeExtraction(self._http)
|
|
107
|
+
self.chat = Chat(self._http)
|
|
108
|
+
self.agentic_chat = AgenticChat(self._http)
|
|
109
|
+
self.jobs = Jobs(self._http)
|
|
110
|
+
self.sharing = Sharing(self._http)
|
|
111
|
+
self.tasks = Tasks(self._http)
|
|
112
|
+
self.live_stream = LiveStream(self._http)
|
|
113
|
+
self.online_search = OnlineSearch(self._http)
|
|
114
|
+
self.youtube_search = YouTubeSearch(self._http)
|
|
115
|
+
self.transactions = Transactions(self._http)
|
|
116
|
+
self.users = Users(self._http)
|
|
117
|
+
self.subscriptions = Subscriptions(self._http)
|
|
118
|
+
self.invoices = Invoices(self._http)
|
|
119
|
+
self.upload_integrations = UploadIntegrations(self._http)
|
|
120
|
+
|
|
121
|
+
def close(self) -> None:
|
|
122
|
+
"""Close the underlying HTTP connection pool."""
|
|
123
|
+
self._http.close()
|
|
124
|
+
|
|
125
|
+
def as_mcp_server(self) -> Any:
|
|
126
|
+
"""Return a FastMCP server instance pre-wired to this client.
|
|
127
|
+
|
|
128
|
+
Requires the ``creativai-mcp`` package to be installed::
|
|
129
|
+
|
|
130
|
+
pip install creativai-mcp
|
|
131
|
+
|
|
132
|
+
Example::
|
|
133
|
+
|
|
134
|
+
import creativai, uvicorn
|
|
135
|
+
from starlette.applications import Starlette
|
|
136
|
+
|
|
137
|
+
client = creativai.CreativAI()
|
|
138
|
+
mcp_server = client.as_mcp_server()
|
|
139
|
+
# Run stdio (for Claude Desktop / Cursor):
|
|
140
|
+
mcp_server.run(transport="stdio")
|
|
141
|
+
# Or mount as ASGI for HTTP/SSE:
|
|
142
|
+
app = mcp_server.sse_app()
|
|
143
|
+
uvicorn.run(app, port=8090)
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
from creativai_mcp.server import build_server # type: ignore[import]
|
|
147
|
+
except ImportError as exc:
|
|
148
|
+
raise ImportError(
|
|
149
|
+
"The 'creativai-mcp' package is required to use as_mcp_server(). "
|
|
150
|
+
"Install it with: pip install creativai-mcp"
|
|
151
|
+
) from exc
|
|
152
|
+
return build_server(api_key=self._http._api_key)
|
|
153
|
+
|
|
154
|
+
def __enter__(self) -> "CreativAI":
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
def __exit__(self, *args: Any) -> None:
|
|
158
|
+
self.close()
|
|
159
|
+
|
|
160
|
+
def __repr__(self) -> str:
|
|
161
|
+
return f"CreativAI(base_url={self._http._base_url!r})"
|
creativai/exceptions.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CreativAI Python SDK — exceptions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CreativAIError(Exception):
|
|
10
|
+
"""Base exception for all CreativAI SDK errors."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, message: str, *, code: str | None = None, details: Any = None) -> None:
|
|
13
|
+
super().__init__(message)
|
|
14
|
+
self.message = message
|
|
15
|
+
self.code = code
|
|
16
|
+
self.details = details
|
|
17
|
+
|
|
18
|
+
def __repr__(self) -> str:
|
|
19
|
+
return f"{self.__class__.__name__}(message={self.message!r}, code={self.code!r})"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class APIError(CreativAIError):
|
|
23
|
+
"""Raised when the API returns an error response."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
message: str,
|
|
28
|
+
*,
|
|
29
|
+
status_code: int,
|
|
30
|
+
code: str | None = None,
|
|
31
|
+
details: Any = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(message, code=code, details=details)
|
|
34
|
+
self.status_code = status_code
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return (
|
|
38
|
+
f"{self.__class__.__name__}("
|
|
39
|
+
f"status_code={self.status_code}, "
|
|
40
|
+
f"message={self.message!r}, "
|
|
41
|
+
f"code={self.code!r})"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AuthenticationError(APIError):
|
|
46
|
+
"""Raised for 401 Unauthorized — invalid or missing API key."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PermissionError(APIError):
|
|
50
|
+
"""Raised for 403 Forbidden — caller lacks permission."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NotFoundError(APIError):
|
|
54
|
+
"""Raised for 404 Not Found."""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RateLimitError(APIError):
|
|
58
|
+
"""Raised for 429 Too Many Requests."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class InsufficientCreditsError(APIError):
|
|
62
|
+
"""Raised for 402 Payment Required — not enough credits."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ValidationError(APIError):
|
|
66
|
+
"""Raised for 400/422 validation failures."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ServerError(APIError):
|
|
70
|
+
"""Raised for 5xx server-side errors."""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TimeoutError(CreativAIError):
|
|
74
|
+
"""Raised when a request times out."""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class StreamingError(CreativAIError):
|
|
78
|
+
"""Raised when an SSE streaming connection fails."""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from .health import Health
|
|
2
|
+
from .organizations import Organizations
|
|
3
|
+
from .projects import Projects
|
|
4
|
+
from .collections import Collections
|
|
5
|
+
from .media import Media
|
|
6
|
+
from .uploads import Uploads
|
|
7
|
+
from .transfers import Transfers
|
|
8
|
+
from .indexing import Indexing
|
|
9
|
+
from .search import Search
|
|
10
|
+
from .data_plates import DataPlates
|
|
11
|
+
from .sub_plates import SubPlates
|
|
12
|
+
from .knowledge_extraction import KnowledgeExtraction
|
|
13
|
+
from .chat import Chat
|
|
14
|
+
from .agentic_chat import AgenticChat
|
|
15
|
+
from .jobs import Jobs
|
|
16
|
+
from .sharing import Sharing
|
|
17
|
+
from .tasks import Tasks
|
|
18
|
+
from .live_stream import LiveStream
|
|
19
|
+
from .online_search import OnlineSearch
|
|
20
|
+
from .youtube_search import YouTubeSearch
|
|
21
|
+
from .transactions import Transactions
|
|
22
|
+
from .users import Users
|
|
23
|
+
from .subscriptions import Subscriptions
|
|
24
|
+
from .invoices import Invoices
|
|
25
|
+
from .upload_integrations import UploadIntegrations
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Health",
|
|
29
|
+
"Organizations",
|
|
30
|
+
"Projects",
|
|
31
|
+
"Collections",
|
|
32
|
+
"Media",
|
|
33
|
+
"Uploads",
|
|
34
|
+
"Transfers",
|
|
35
|
+
"Indexing",
|
|
36
|
+
"Search",
|
|
37
|
+
"DataPlates",
|
|
38
|
+
"SubPlates",
|
|
39
|
+
"KnowledgeExtraction",
|
|
40
|
+
"Chat",
|
|
41
|
+
"AgenticChat",
|
|
42
|
+
"Jobs",
|
|
43
|
+
"Sharing",
|
|
44
|
+
"Tasks",
|
|
45
|
+
"LiveStream",
|
|
46
|
+
"OnlineSearch",
|
|
47
|
+
"YouTubeSearch",
|
|
48
|
+
"Transactions",
|
|
49
|
+
"Users",
|
|
50
|
+
"Subscriptions",
|
|
51
|
+
"Invoices",
|
|
52
|
+
"UploadIntegrations",
|
|
53
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CreativAI SDK — Agentic Chat resource (SSE streaming)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any, Iterator
|
|
8
|
+
from .._http import _HTTPClient
|
|
9
|
+
|
|
10
|
+
_PREFIX = "/api/v2/agentic-chat"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgenticChat:
|
|
14
|
+
def __init__(self, http: _HTTPClient) -> None:
|
|
15
|
+
self._http = http
|
|
16
|
+
|
|
17
|
+
# ------------------------------------------------------------------
|
|
18
|
+
# Session management
|
|
19
|
+
# ------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
def create_session(self, collection_id: str, *, title: str | None = None, **kwargs: Any) -> Any:
|
|
22
|
+
"""POST /api/v2/agentic-chat/sessions — Create a new agentic chat session (HTTP 201)."""
|
|
23
|
+
body: dict[str, Any] = {"collection_id": collection_id, **kwargs}
|
|
24
|
+
if title is not None:
|
|
25
|
+
body["title"] = title
|
|
26
|
+
return self._http.post(f"{_PREFIX}/sessions", json=body)
|
|
27
|
+
|
|
28
|
+
def list_sessions(self, *, collection_id: str | None = None, limit: int = 20, **kwargs: Any) -> Any:
|
|
29
|
+
"""GET /api/v2/agentic-chat/sessions — List sessions, optionally filtered by collection."""
|
|
30
|
+
params: dict[str, Any] = {"limit": limit, **kwargs}
|
|
31
|
+
if collection_id is not None:
|
|
32
|
+
params["collection_id"] = collection_id
|
|
33
|
+
return self._http.get(f"{_PREFIX}/sessions", params=params)
|
|
34
|
+
|
|
35
|
+
def get_session(self, session_id: str) -> Any:
|
|
36
|
+
"""GET /api/v2/agentic-chat/sessions/{session_id} — Get session details."""
|
|
37
|
+
return self._http.get(f"{_PREFIX}/sessions/{session_id}")
|
|
38
|
+
|
|
39
|
+
def delete_session(self, session_id: str) -> Any:
|
|
40
|
+
"""DELETE /api/v2/agentic-chat/sessions/{session_id} — Delete a session."""
|
|
41
|
+
return self._http.delete(f"{_PREFIX}/sessions/{session_id}")
|
|
42
|
+
|
|
43
|
+
def get_messages(self, session_id: str) -> Any:
|
|
44
|
+
"""GET /api/v2/agentic-chat/sessions/{session_id}/messages — Get full message history and session state."""
|
|
45
|
+
return self._http.get(f"{_PREFIX}/sessions/{session_id}/messages")
|
|
46
|
+
|
|
47
|
+
def get_status(self, session_id: str) -> Any:
|
|
48
|
+
"""GET /api/v2/agentic-chat/sessions/{session_id}/status — Check current agent status."""
|
|
49
|
+
return self._http.get(f"{_PREFIX}/sessions/{session_id}/status")
|
|
50
|
+
|
|
51
|
+
def get_events(self, session_id: str, **params: Any) -> Any:
|
|
52
|
+
"""GET /api/v2/agentic-chat/sessions/{session_id}/events — Poll new agent events since last call."""
|
|
53
|
+
return self._http.get(f"{_PREFIX}/sessions/{session_id}/events", params=params or None)
|
|
54
|
+
|
|
55
|
+
def stop(self, session_id: str, **kwargs: Any) -> Any:
|
|
56
|
+
"""POST /api/v2/agentic-chat/sessions/{session_id}/stop — Stop the agent at the current step."""
|
|
57
|
+
return self._http.post(f"{_PREFIX}/sessions/{session_id}/stop", json=kwargs or None)
|
|
58
|
+
|
|
59
|
+
def resume(self, session_id: str, **kwargs: Any) -> Any:
|
|
60
|
+
"""POST /api/v2/agentic-chat/sessions/{session_id}/resume — Resume an interrupted agent."""
|
|
61
|
+
return self._http.post(f"{_PREFIX}/sessions/{session_id}/resume", json=kwargs or None)
|
|
62
|
+
|
|
63
|
+
def search_feedback(self, session_id: str, feedback: Any, **kwargs: Any) -> Any:
|
|
64
|
+
"""POST .../search-feedback — Respond to a ``search_feedback_required`` interrupt."""
|
|
65
|
+
return self._http.post(
|
|
66
|
+
f"{_PREFIX}/sessions/{session_id}/search-feedback",
|
|
67
|
+
json={"feedback": feedback, **kwargs},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# ------------------------------------------------------------------
|
|
71
|
+
# Streaming chat
|
|
72
|
+
# ------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
def chat(
|
|
75
|
+
self,
|
|
76
|
+
session_id: str,
|
|
77
|
+
message: str,
|
|
78
|
+
**kwargs: Any,
|
|
79
|
+
) -> Iterator[dict[str, Any]]:
|
|
80
|
+
"""POST .../chat — Send a message and stream SSE events from the agent.
|
|
81
|
+
|
|
82
|
+
Yields parsed event dicts with at minimum ``event`` (event type) and
|
|
83
|
+
``data`` (parsed JSON payload) keys.
|
|
84
|
+
|
|
85
|
+
SSE event types (non-exhaustive):
|
|
86
|
+
``thinking``, ``search``, ``create_plate``, ``extract_knowledge``,
|
|
87
|
+
``web_search``, ``youtube_search``, ``synthesize``,
|
|
88
|
+
``answer``, ``done``, ``error``, ``search_feedback_required``
|
|
89
|
+
|
|
90
|
+
Example::
|
|
91
|
+
|
|
92
|
+
for event in client.agentic_chat.chat(session_id, "What activities are common?"):
|
|
93
|
+
if event["event"] == "answer":
|
|
94
|
+
print(event["data"]["text"])
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
session_id: Active session identifier.
|
|
98
|
+
message: User message text.
|
|
99
|
+
"""
|
|
100
|
+
body: dict[str, Any] = {"message": message, **kwargs}
|
|
101
|
+
for raw in self._http.stream_post(f"{_PREFIX}/sessions/{session_id}/chat", json=body):
|
|
102
|
+
event_type = raw["event"]
|
|
103
|
+
raw_data = raw["data"]
|
|
104
|
+
try:
|
|
105
|
+
data = json.loads(raw_data) if raw_data else {}
|
|
106
|
+
except json.JSONDecodeError:
|
|
107
|
+
data = {"text": raw_data}
|
|
108
|
+
yield {"event": event_type, "data": data}
|
|
109
|
+
|
|
110
|
+
def subscribe_stream(self, session_id: str) -> Iterator[dict[str, Any]]:
|
|
111
|
+
"""GET .../stream — Subscribe to a running agent's SSE stream (reconnect-safe).
|
|
112
|
+
|
|
113
|
+
Yields the same parsed event dicts as :meth:`chat`.
|
|
114
|
+
"""
|
|
115
|
+
for raw in self._http.stream_get(f"{_PREFIX}/sessions/{session_id}/stream"):
|
|
116
|
+
event_type = raw["event"]
|
|
117
|
+
raw_data = raw["data"]
|
|
118
|
+
try:
|
|
119
|
+
data = json.loads(raw_data) if raw_data else {}
|
|
120
|
+
except json.JSONDecodeError:
|
|
121
|
+
data = {"text": raw_data}
|
|
122
|
+
yield {"event": event_type, "data": data}
|