sogni-client 5.28.0__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.
- sogni_client-5.28.0/.gitignore +19 -0
- sogni_client-5.28.0/LICENSE +15 -0
- sogni_client-5.28.0/PKG-INFO +316 -0
- sogni_client-5.28.0/README.md +285 -0
- sogni_client-5.28.0/examples/generate_image.py +31 -0
- sogni_client-5.28.0/examples/krea_identity_edit.py +59 -0
- sogni_client-5.28.0/examples/stream_chat.py +23 -0
- sogni_client-5.28.0/pyproject.toml +74 -0
- sogni_client-5.28.0/sogni_client/__init__.py +120 -0
- sogni_client-5.28.0/sogni_client/account.py +958 -0
- sogni_client-5.28.0/sogni_client/announcements.py +60 -0
- sogni_client-5.28.0/sogni_client/attribution.py +255 -0
- sogni_client-5.28.0/sogni_client/auth.py +227 -0
- sogni_client-5.28.0/sogni_client/chat.py +1213 -0
- sogni_client-5.28.0/sogni_client/client.py +198 -0
- sogni_client-5.28.0/sogni_client/data/hosted_tools.json +2049 -0
- sogni_client-5.28.0/sogni_client/errors.py +155 -0
- sogni_client-5.28.0/sogni_client/events.py +103 -0
- sogni_client-5.28.0/sogni_client/projects.py +3306 -0
- sogni_client-5.28.0/sogni_client/py.typed +1 -0
- sogni_client-5.28.0/sogni_client/recovery.py +214 -0
- sogni_client-5.28.0/sogni_client/replay.py +91 -0
- sogni_client-5.28.0/sogni_client/stats.py +24 -0
- sogni_client-5.28.0/sogni_client/transport.py +565 -0
- sogni_client-5.28.0/sogni_client/utils.py +366 -0
- sogni_client-5.28.0/sogni_client/workflows.py +504 -0
- sogni_client-5.28.0/tests/conftest.py +14 -0
- sogni_client-5.28.0/tests/fixtures/hosted-tool-alias-parity.generated.json +296 -0
- sogni_client-5.28.0/tests/test_account.py +556 -0
- sogni_client-5.28.0/tests/test_announcements.py +104 -0
- sogni_client-5.28.0/tests/test_attribution.py +79 -0
- sogni_client-5.28.0/tests/test_auth.py +268 -0
- sogni_client-5.28.0/tests/test_chat.py +863 -0
- sogni_client-5.28.0/tests/test_client.py +288 -0
- sogni_client-5.28.0/tests/test_errors.py +156 -0
- sogni_client-5.28.0/tests/test_events.py +84 -0
- sogni_client-5.28.0/tests/test_projects.py +1314 -0
- sogni_client-5.28.0/tests/test_recovery.py +336 -0
- sogni_client-5.28.0/tests/test_transport.py +397 -0
- sogni_client-5.28.0/tests/test_utils.py +231 -0
- sogni_client-5.28.0/tests/test_workflows.py +487 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sogni AI
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sogni-client
|
|
3
|
+
Version: 5.28.0
|
|
4
|
+
Summary: Async Python SDK for image, video, audio, and LLM inference on the Sogni Supernet
|
|
5
|
+
Project-URL: Homepage, https://www.sogni.ai
|
|
6
|
+
Project-URL: Documentation, https://docs.sogni.ai/sogni-sdk/python/
|
|
7
|
+
Project-URL: Repository, https://github.com/Sogni-AI/sogni-client-python
|
|
8
|
+
Author: Sogni AI
|
|
9
|
+
License-Expression: ISC
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: generative-ai,image-generation,llm,sogni,video-generation
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: eth-account<1,>=0.13
|
|
23
|
+
Requires-Dist: httpx<1,>=0.27
|
|
24
|
+
Requires-Dist: websockets<17,>=13
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Sogni Client for Python
|
|
33
|
+
|
|
34
|
+
An async Python SDK for image, video, audio, and LLM inference on the Sogni
|
|
35
|
+
Supernet. It follows the public surface and wire protocol of the TypeScript
|
|
36
|
+
`sogni-client`, while using Python naming conventions and async iterators.
|
|
37
|
+
|
|
38
|
+
> The Python port is currently beta. Keep credentials in environment variables
|
|
39
|
+
> or your system keychain; never commit them to source control.
|
|
40
|
+
|
|
41
|
+
[Official quickstart](https://docs.sogni.ai/sogni-sdk/python/) ·
|
|
42
|
+
[Examples](https://github.com/Sogni-AI/sogni-client-python/tree/main/examples) ·
|
|
43
|
+
[Sogni API reference](https://docs.sogni.ai/api-reference/)
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
Install the latest beta directly from the official GitHub repository:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
python -m pip install "sogni-client @ git+https://github.com/Sogni-AI/sogni-client-python.git@main"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For an editable source checkout:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/Sogni-AI/sogni-client-python.git
|
|
57
|
+
cd sogni-client-python
|
|
58
|
+
python -m pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Python 3.10 or newer is required.
|
|
62
|
+
|
|
63
|
+
## Create an image
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import asyncio
|
|
67
|
+
import os
|
|
68
|
+
|
|
69
|
+
from sogni_client import SogniClient
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def main() -> None:
|
|
73
|
+
async with await SogniClient.create(api_key=os.environ["SOGNI_API_KEY"]) as sogni:
|
|
74
|
+
project = await sogni.projects.create(
|
|
75
|
+
type="image",
|
|
76
|
+
model_id="krea2_turbo_fp8_scaled",
|
|
77
|
+
positive_prompt="A tiny observatory above a sea of clouds",
|
|
78
|
+
negative_prompt="text, watermark",
|
|
79
|
+
number_of_media=1,
|
|
80
|
+
width=1024,
|
|
81
|
+
height=1024,
|
|
82
|
+
steps=8,
|
|
83
|
+
)
|
|
84
|
+
print(await project.wait_for_completion())
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
asyncio.run(main())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`SogniClient.create()` generates a unique application ID when one is not
|
|
91
|
+
provided. Pass `app_id="..."` when you deliberately need a stable socket
|
|
92
|
+
identity.
|
|
93
|
+
|
|
94
|
+
The example uses **Krea 2 Turbo** (`krea2_turbo_fp8_scaled`) because it is the
|
|
95
|
+
only model an account's free monthly render credits can be spent on over the
|
|
96
|
+
API — every other model needs paid credits, so a brand-new key would otherwise
|
|
97
|
+
fail on its first call. It is an 8-step model, hence `steps=8`.
|
|
98
|
+
|
|
99
|
+
## Edit an image with Krea 2 Identity Edit
|
|
100
|
+
|
|
101
|
+
Pass one or two local reference images through `context_images`. For two-image
|
|
102
|
+
edits, place the base scene first and the identity or detail reference second.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
project = await sogni.projects.create(
|
|
106
|
+
type="image",
|
|
107
|
+
model_id="krea2_identity_edit_v1_2",
|
|
108
|
+
positive_prompt=(
|
|
109
|
+
"Change only the jacket to vivid sapphire blue. Preserve the exact "
|
|
110
|
+
"facial identity, expression, framing, background, and lighting."
|
|
111
|
+
),
|
|
112
|
+
number_of_media=1,
|
|
113
|
+
width=1024,
|
|
114
|
+
height=1024,
|
|
115
|
+
steps=10,
|
|
116
|
+
guidance=1,
|
|
117
|
+
token_type="spark",
|
|
118
|
+
context_images=["reference.png"],
|
|
119
|
+
)
|
|
120
|
+
print(await project.wait_for_completion(timeout=900))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The runnable example accepts one or two image paths and can also create a batch:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python examples/krea_identity_edit.py reference.png \
|
|
127
|
+
--prompt "Change only the jacket to vivid sapphire blue; preserve identity."
|
|
128
|
+
|
|
129
|
+
python examples/krea_identity_edit.py scene.png identity.png \
|
|
130
|
+
--prompt "Use the first image as the base scene and the second for identity." \
|
|
131
|
+
--count 4
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Chat
|
|
135
|
+
|
|
136
|
+
Socket-backed completion:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
result = await sogni.chat.completions.create(
|
|
140
|
+
model="qwen3.6-35b-a3b-gguf-iq4xs",
|
|
141
|
+
messages=[{"role": "user", "content": "Give me three visual concepts."}],
|
|
142
|
+
)
|
|
143
|
+
print(result["content"])
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Hosted OpenAI-compatible completion:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
result = await sogni.chat.hosted.create(
|
|
150
|
+
model="qwen3.6-35b-a3b-gguf-iq4xs",
|
|
151
|
+
messages=[{"role": "user", "content": "Describe a surreal album cover."}],
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For streaming socket chat, pass `stream=True` and iterate over the returned
|
|
156
|
+
`ChatStream` with `async for`.
|
|
157
|
+
|
|
158
|
+
## Durable workflows
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
workflow = await sogni.workflows.start(
|
|
162
|
+
input={"prompt": "Create a four-panel character turnaround"},
|
|
163
|
+
idempotency_key="turnaround-001",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
async for event in sogni.workflows.stream_events(workflow["id"]):
|
|
167
|
+
print(event["event"], event["data"])
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The client also exposes:
|
|
171
|
+
|
|
172
|
+
- `sogni.account` for authentication, balances, rewards, transactions, and subscriptions
|
|
173
|
+
- `sogni.projects` for generation, uploads, model discovery, and estimates
|
|
174
|
+
- `sogni.chat` for socket, hosted, tool, and durable-run APIs
|
|
175
|
+
- `sogni.workflows` and `sogni.workflows.templates`
|
|
176
|
+
- `sogni.replay` and `sogni.stats`
|
|
177
|
+
|
|
178
|
+
Python `snake_case` arguments are preferred. Common JavaScript-style aliases
|
|
179
|
+
remain accepted to simplify migration.
|
|
180
|
+
|
|
181
|
+
## Resuming projects after a reconnect
|
|
182
|
+
|
|
183
|
+
Generation keeps running on the Supernet while your socket is down. A dropped
|
|
184
|
+
connection is a transport gap, not a failure: tracked projects stay alive, the
|
|
185
|
+
client reconnects with capped exponential backoff for as long as the session is
|
|
186
|
+
authenticated, and on every `authenticated` handshake it reconciles with the
|
|
187
|
+
server. Whatever the client missed is replayed through the normal `project` /
|
|
188
|
+
`job` events, so listeners attached before the gap keep receiving updates and
|
|
189
|
+
`wait_for_completion()` still resolves.
|
|
190
|
+
|
|
191
|
+
Projects the server knows about but this client does not (a restart, a second
|
|
192
|
+
client sharing the account, cleared local state) are rebuilt as tracked
|
|
193
|
+
`Project` instances with `project.recovered is True`. Their `params` are
|
|
194
|
+
reconstructed from the original request; asset inputs are not recoverable.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# Every reconciliation reports what changed. `snapshot` is the raw server view,
|
|
198
|
+
# for apps that keep their own project store.
|
|
199
|
+
sogni.projects.on("projectsSynced", lambda r: print(r["reason"], r["active"], r["lost"]))
|
|
200
|
+
|
|
201
|
+
# In-flight projects this client was not tracking; they are tracked now, so
|
|
202
|
+
# `project` / `job` events follow as usual.
|
|
203
|
+
sogni.projects.on("activeProjectsRecovered", lambda projects: ...)
|
|
204
|
+
|
|
205
|
+
# Projects that finished while this client was away, result URLs already resolved.
|
|
206
|
+
sogni.projects.on("completedProjectsRecovered", lambda projects: ...)
|
|
207
|
+
|
|
208
|
+
# Ask for a fresh reconciliation yourself, e.g. after waking from sleep.
|
|
209
|
+
await sogni.projects.sync()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A project the server no longer lists is looked up on the REST API (which only
|
|
213
|
+
stores finished projects) a few times before it is declared lost; it then fails
|
|
214
|
+
with an error where `is_project_lost_error(error)` is `True`. Apps that persist
|
|
215
|
+
project ids themselves can run the same lookup with
|
|
216
|
+
`sogni.projects.resolve_missing(ids)`.
|
|
217
|
+
|
|
218
|
+
The same snapshot answers "is anything rendering elsewhere on this account?" —
|
|
219
|
+
`sogni.projects.list_projects_elsewhere()` returns those in-flight projects
|
|
220
|
+
read-only (`appSource`, `status`, `model`, per-job step counts). The socket
|
|
221
|
+
rate-limits it to 20 calls per 10s per account, so poll on the order of tens of
|
|
222
|
+
seconds.
|
|
223
|
+
|
|
224
|
+
Recovery is per app instance: the server hands projects back to the `appId` that
|
|
225
|
+
created them, so persist your `appId` and reuse it across restarts.
|
|
226
|
+
|
|
227
|
+
## Announcements
|
|
228
|
+
|
|
229
|
+
Admin-authored in-app announcements — maintenance notices, launches — arrive on
|
|
230
|
+
the `appAlert` socket event. It is opt-in, so an integration that does not ask
|
|
231
|
+
for it is unaffected:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
sogni = await SogniClient.create(
|
|
235
|
+
api_key=os.environ["SOGNI_API_KEY"],
|
|
236
|
+
socket_event_subscriptions={"appAlert": True},
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
sogni.api_client.on("appAlert", lambda announcement: print(announcement["title"]))
|
|
240
|
+
|
|
241
|
+
# What is live right now, for a client that just started up.
|
|
242
|
+
for announcement in await sogni.announcements.active("my-app"):
|
|
243
|
+
print(announcement["title"], announcement["bodyMarkdown"])
|
|
244
|
+
|
|
245
|
+
# Dismissal is stored per ACCOUNT, so it sticks across the user's devices.
|
|
246
|
+
await sogni.announcements.dismiss(announcement["id"])
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`appAlert` is **not** at-most-once: a live pinned announcement is re-sent on
|
|
250
|
+
every reconnect, so a user who was offline when it published still receives it.
|
|
251
|
+
Deduplicate on `id`.
|
|
252
|
+
|
|
253
|
+
## Sensitive content
|
|
254
|
+
|
|
255
|
+
`job.is_nsfw` means the server **withheld** the media: the render ran with the
|
|
256
|
+
Sensitive Content Filter on, a signal fired, and there is nothing to download.
|
|
257
|
+
When the artist turns the filter off the media is delivered and merely labelled
|
|
258
|
+
— that case reports `job.nsfw_detected` with `job.nsfw_sources` (`prompt`
|
|
259
|
+
and/or `image`), has a `result_url` like any other result, and leaves
|
|
260
|
+
`job.is_nsfw` false. Use `job.has_result_media` (or `job.is_withheld`) to decide
|
|
261
|
+
whether media exists, and the viewer's own filter setting to decide whether to
|
|
262
|
+
blur it.
|
|
263
|
+
|
|
264
|
+
## Compatibility
|
|
265
|
+
|
|
266
|
+
This release tracks the current TypeScript source at `5.28.0`. The
|
|
267
|
+
REST, WebSocket, and SSE contracts are covered by credential-free protocol
|
|
268
|
+
tests, including authentication refresh, uploads, project state recovery,
|
|
269
|
+
streaming chat, workflows, templates, replay, and the canonical 25 hosted-tool
|
|
270
|
+
schemas.
|
|
271
|
+
|
|
272
|
+
Current model and transport coverage includes LTX 2.5, MiniMax H3 in all four
|
|
273
|
+
tiers (Standard, 8-step Balanced, 4-step LightX2V Turbo, and the separate
|
|
274
|
+
FastH3 `fastvideo-int8` Turbo engine), Seedance 2.5, Wan 3 and Wan 3.0 Enhanced,
|
|
275
|
+
RTX VSR, MiniMax Music 3, LoRA catalog discovery, queue start estimates,
|
|
276
|
+
live-benchmarked render/total time on cost quotes, in-flight project recovery
|
|
277
|
+
across reconnects, confirmed cancellation, connection/workload attribution, and
|
|
278
|
+
admin announcements (`appAlert` plus the announcements read/dismiss pair).
|
|
279
|
+
|
|
280
|
+
The Python API is async-first; `AsyncSogniClient` is an alias of
|
|
281
|
+
`SogniClient`, not a synchronous wrapper. Browser-only cookie coordination and
|
|
282
|
+
multi-tab behavior have no Python equivalent. Local image references are
|
|
283
|
+
uploaded with their detected MIME type, but the TypeScript client's optional
|
|
284
|
+
browser-side image resizing is not reproduced. All 25 canonical tool schemas
|
|
285
|
+
are exposed; the local project-backed executor handles the six direct media
|
|
286
|
+
generation tools, while the remaining tools run through the hosted or durable
|
|
287
|
+
chat APIs. Live, credentialed smoke tests are intentionally separate from the
|
|
288
|
+
default test suite.
|
|
289
|
+
|
|
290
|
+
## Token authentication
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
sogni = await SogniClient.create(auth_type="token")
|
|
294
|
+
await sogni.set_tokens(token=access_token, refresh_token=refresh_token)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Username/password login and signing are available through `sogni.account.login`.
|
|
298
|
+
API-key use does not require storing a wallet password.
|
|
299
|
+
|
|
300
|
+
## Development
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
python -m pip install -e '.[dev]'
|
|
304
|
+
pytest
|
|
305
|
+
ruff check sogni_client tests
|
|
306
|
+
ruff format --check sogni_client tests
|
|
307
|
+
python -m build
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Live integration tests require explicit credentials and are not run by default.
|
|
311
|
+
|
|
312
|
+
## Documentation
|
|
313
|
+
|
|
314
|
+
- [Python SDK quickstart](https://docs.sogni.ai/sogni-sdk/python/)
|
|
315
|
+
- [Sogni SDK overview](https://docs.sogni.ai/sogni-sdk/)
|
|
316
|
+
- [REST API reference](https://docs.sogni.ai/api-reference/)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# Sogni Client for Python
|
|
2
|
+
|
|
3
|
+
An async Python SDK for image, video, audio, and LLM inference on the Sogni
|
|
4
|
+
Supernet. It follows the public surface and wire protocol of the TypeScript
|
|
5
|
+
`sogni-client`, while using Python naming conventions and async iterators.
|
|
6
|
+
|
|
7
|
+
> The Python port is currently beta. Keep credentials in environment variables
|
|
8
|
+
> or your system keychain; never commit them to source control.
|
|
9
|
+
|
|
10
|
+
[Official quickstart](https://docs.sogni.ai/sogni-sdk/python/) ·
|
|
11
|
+
[Examples](https://github.com/Sogni-AI/sogni-client-python/tree/main/examples) ·
|
|
12
|
+
[Sogni API reference](https://docs.sogni.ai/api-reference/)
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Install the latest beta directly from the official GitHub repository:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python -m pip install "sogni-client @ git+https://github.com/Sogni-AI/sogni-client-python.git@main"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For an editable source checkout:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/Sogni-AI/sogni-client-python.git
|
|
26
|
+
cd sogni-client-python
|
|
27
|
+
python -m pip install -e .
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Python 3.10 or newer is required.
|
|
31
|
+
|
|
32
|
+
## Create an image
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import asyncio
|
|
36
|
+
import os
|
|
37
|
+
|
|
38
|
+
from sogni_client import SogniClient
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def main() -> None:
|
|
42
|
+
async with await SogniClient.create(api_key=os.environ["SOGNI_API_KEY"]) as sogni:
|
|
43
|
+
project = await sogni.projects.create(
|
|
44
|
+
type="image",
|
|
45
|
+
model_id="krea2_turbo_fp8_scaled",
|
|
46
|
+
positive_prompt="A tiny observatory above a sea of clouds",
|
|
47
|
+
negative_prompt="text, watermark",
|
|
48
|
+
number_of_media=1,
|
|
49
|
+
width=1024,
|
|
50
|
+
height=1024,
|
|
51
|
+
steps=8,
|
|
52
|
+
)
|
|
53
|
+
print(await project.wait_for_completion())
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`SogniClient.create()` generates a unique application ID when one is not
|
|
60
|
+
provided. Pass `app_id="..."` when you deliberately need a stable socket
|
|
61
|
+
identity.
|
|
62
|
+
|
|
63
|
+
The example uses **Krea 2 Turbo** (`krea2_turbo_fp8_scaled`) because it is the
|
|
64
|
+
only model an account's free monthly render credits can be spent on over the
|
|
65
|
+
API — every other model needs paid credits, so a brand-new key would otherwise
|
|
66
|
+
fail on its first call. It is an 8-step model, hence `steps=8`.
|
|
67
|
+
|
|
68
|
+
## Edit an image with Krea 2 Identity Edit
|
|
69
|
+
|
|
70
|
+
Pass one or two local reference images through `context_images`. For two-image
|
|
71
|
+
edits, place the base scene first and the identity or detail reference second.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
project = await sogni.projects.create(
|
|
75
|
+
type="image",
|
|
76
|
+
model_id="krea2_identity_edit_v1_2",
|
|
77
|
+
positive_prompt=(
|
|
78
|
+
"Change only the jacket to vivid sapphire blue. Preserve the exact "
|
|
79
|
+
"facial identity, expression, framing, background, and lighting."
|
|
80
|
+
),
|
|
81
|
+
number_of_media=1,
|
|
82
|
+
width=1024,
|
|
83
|
+
height=1024,
|
|
84
|
+
steps=10,
|
|
85
|
+
guidance=1,
|
|
86
|
+
token_type="spark",
|
|
87
|
+
context_images=["reference.png"],
|
|
88
|
+
)
|
|
89
|
+
print(await project.wait_for_completion(timeout=900))
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The runnable example accepts one or two image paths and can also create a batch:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python examples/krea_identity_edit.py reference.png \
|
|
96
|
+
--prompt "Change only the jacket to vivid sapphire blue; preserve identity."
|
|
97
|
+
|
|
98
|
+
python examples/krea_identity_edit.py scene.png identity.png \
|
|
99
|
+
--prompt "Use the first image as the base scene and the second for identity." \
|
|
100
|
+
--count 4
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Chat
|
|
104
|
+
|
|
105
|
+
Socket-backed completion:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
result = await sogni.chat.completions.create(
|
|
109
|
+
model="qwen3.6-35b-a3b-gguf-iq4xs",
|
|
110
|
+
messages=[{"role": "user", "content": "Give me three visual concepts."}],
|
|
111
|
+
)
|
|
112
|
+
print(result["content"])
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Hosted OpenAI-compatible completion:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
result = await sogni.chat.hosted.create(
|
|
119
|
+
model="qwen3.6-35b-a3b-gguf-iq4xs",
|
|
120
|
+
messages=[{"role": "user", "content": "Describe a surreal album cover."}],
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
For streaming socket chat, pass `stream=True` and iterate over the returned
|
|
125
|
+
`ChatStream` with `async for`.
|
|
126
|
+
|
|
127
|
+
## Durable workflows
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
workflow = await sogni.workflows.start(
|
|
131
|
+
input={"prompt": "Create a four-panel character turnaround"},
|
|
132
|
+
idempotency_key="turnaround-001",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async for event in sogni.workflows.stream_events(workflow["id"]):
|
|
136
|
+
print(event["event"], event["data"])
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The client also exposes:
|
|
140
|
+
|
|
141
|
+
- `sogni.account` for authentication, balances, rewards, transactions, and subscriptions
|
|
142
|
+
- `sogni.projects` for generation, uploads, model discovery, and estimates
|
|
143
|
+
- `sogni.chat` for socket, hosted, tool, and durable-run APIs
|
|
144
|
+
- `sogni.workflows` and `sogni.workflows.templates`
|
|
145
|
+
- `sogni.replay` and `sogni.stats`
|
|
146
|
+
|
|
147
|
+
Python `snake_case` arguments are preferred. Common JavaScript-style aliases
|
|
148
|
+
remain accepted to simplify migration.
|
|
149
|
+
|
|
150
|
+
## Resuming projects after a reconnect
|
|
151
|
+
|
|
152
|
+
Generation keeps running on the Supernet while your socket is down. A dropped
|
|
153
|
+
connection is a transport gap, not a failure: tracked projects stay alive, the
|
|
154
|
+
client reconnects with capped exponential backoff for as long as the session is
|
|
155
|
+
authenticated, and on every `authenticated` handshake it reconciles with the
|
|
156
|
+
server. Whatever the client missed is replayed through the normal `project` /
|
|
157
|
+
`job` events, so listeners attached before the gap keep receiving updates and
|
|
158
|
+
`wait_for_completion()` still resolves.
|
|
159
|
+
|
|
160
|
+
Projects the server knows about but this client does not (a restart, a second
|
|
161
|
+
client sharing the account, cleared local state) are rebuilt as tracked
|
|
162
|
+
`Project` instances with `project.recovered is True`. Their `params` are
|
|
163
|
+
reconstructed from the original request; asset inputs are not recoverable.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
# Every reconciliation reports what changed. `snapshot` is the raw server view,
|
|
167
|
+
# for apps that keep their own project store.
|
|
168
|
+
sogni.projects.on("projectsSynced", lambda r: print(r["reason"], r["active"], r["lost"]))
|
|
169
|
+
|
|
170
|
+
# In-flight projects this client was not tracking; they are tracked now, so
|
|
171
|
+
# `project` / `job` events follow as usual.
|
|
172
|
+
sogni.projects.on("activeProjectsRecovered", lambda projects: ...)
|
|
173
|
+
|
|
174
|
+
# Projects that finished while this client was away, result URLs already resolved.
|
|
175
|
+
sogni.projects.on("completedProjectsRecovered", lambda projects: ...)
|
|
176
|
+
|
|
177
|
+
# Ask for a fresh reconciliation yourself, e.g. after waking from sleep.
|
|
178
|
+
await sogni.projects.sync()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
A project the server no longer lists is looked up on the REST API (which only
|
|
182
|
+
stores finished projects) a few times before it is declared lost; it then fails
|
|
183
|
+
with an error where `is_project_lost_error(error)` is `True`. Apps that persist
|
|
184
|
+
project ids themselves can run the same lookup with
|
|
185
|
+
`sogni.projects.resolve_missing(ids)`.
|
|
186
|
+
|
|
187
|
+
The same snapshot answers "is anything rendering elsewhere on this account?" —
|
|
188
|
+
`sogni.projects.list_projects_elsewhere()` returns those in-flight projects
|
|
189
|
+
read-only (`appSource`, `status`, `model`, per-job step counts). The socket
|
|
190
|
+
rate-limits it to 20 calls per 10s per account, so poll on the order of tens of
|
|
191
|
+
seconds.
|
|
192
|
+
|
|
193
|
+
Recovery is per app instance: the server hands projects back to the `appId` that
|
|
194
|
+
created them, so persist your `appId` and reuse it across restarts.
|
|
195
|
+
|
|
196
|
+
## Announcements
|
|
197
|
+
|
|
198
|
+
Admin-authored in-app announcements — maintenance notices, launches — arrive on
|
|
199
|
+
the `appAlert` socket event. It is opt-in, so an integration that does not ask
|
|
200
|
+
for it is unaffected:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
sogni = await SogniClient.create(
|
|
204
|
+
api_key=os.environ["SOGNI_API_KEY"],
|
|
205
|
+
socket_event_subscriptions={"appAlert": True},
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
sogni.api_client.on("appAlert", lambda announcement: print(announcement["title"]))
|
|
209
|
+
|
|
210
|
+
# What is live right now, for a client that just started up.
|
|
211
|
+
for announcement in await sogni.announcements.active("my-app"):
|
|
212
|
+
print(announcement["title"], announcement["bodyMarkdown"])
|
|
213
|
+
|
|
214
|
+
# Dismissal is stored per ACCOUNT, so it sticks across the user's devices.
|
|
215
|
+
await sogni.announcements.dismiss(announcement["id"])
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`appAlert` is **not** at-most-once: a live pinned announcement is re-sent on
|
|
219
|
+
every reconnect, so a user who was offline when it published still receives it.
|
|
220
|
+
Deduplicate on `id`.
|
|
221
|
+
|
|
222
|
+
## Sensitive content
|
|
223
|
+
|
|
224
|
+
`job.is_nsfw` means the server **withheld** the media: the render ran with the
|
|
225
|
+
Sensitive Content Filter on, a signal fired, and there is nothing to download.
|
|
226
|
+
When the artist turns the filter off the media is delivered and merely labelled
|
|
227
|
+
— that case reports `job.nsfw_detected` with `job.nsfw_sources` (`prompt`
|
|
228
|
+
and/or `image`), has a `result_url` like any other result, and leaves
|
|
229
|
+
`job.is_nsfw` false. Use `job.has_result_media` (or `job.is_withheld`) to decide
|
|
230
|
+
whether media exists, and the viewer's own filter setting to decide whether to
|
|
231
|
+
blur it.
|
|
232
|
+
|
|
233
|
+
## Compatibility
|
|
234
|
+
|
|
235
|
+
This release tracks the current TypeScript source at `5.28.0`. The
|
|
236
|
+
REST, WebSocket, and SSE contracts are covered by credential-free protocol
|
|
237
|
+
tests, including authentication refresh, uploads, project state recovery,
|
|
238
|
+
streaming chat, workflows, templates, replay, and the canonical 25 hosted-tool
|
|
239
|
+
schemas.
|
|
240
|
+
|
|
241
|
+
Current model and transport coverage includes LTX 2.5, MiniMax H3 in all four
|
|
242
|
+
tiers (Standard, 8-step Balanced, 4-step LightX2V Turbo, and the separate
|
|
243
|
+
FastH3 `fastvideo-int8` Turbo engine), Seedance 2.5, Wan 3 and Wan 3.0 Enhanced,
|
|
244
|
+
RTX VSR, MiniMax Music 3, LoRA catalog discovery, queue start estimates,
|
|
245
|
+
live-benchmarked render/total time on cost quotes, in-flight project recovery
|
|
246
|
+
across reconnects, confirmed cancellation, connection/workload attribution, and
|
|
247
|
+
admin announcements (`appAlert` plus the announcements read/dismiss pair).
|
|
248
|
+
|
|
249
|
+
The Python API is async-first; `AsyncSogniClient` is an alias of
|
|
250
|
+
`SogniClient`, not a synchronous wrapper. Browser-only cookie coordination and
|
|
251
|
+
multi-tab behavior have no Python equivalent. Local image references are
|
|
252
|
+
uploaded with their detected MIME type, but the TypeScript client's optional
|
|
253
|
+
browser-side image resizing is not reproduced. All 25 canonical tool schemas
|
|
254
|
+
are exposed; the local project-backed executor handles the six direct media
|
|
255
|
+
generation tools, while the remaining tools run through the hosted or durable
|
|
256
|
+
chat APIs. Live, credentialed smoke tests are intentionally separate from the
|
|
257
|
+
default test suite.
|
|
258
|
+
|
|
259
|
+
## Token authentication
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
sogni = await SogniClient.create(auth_type="token")
|
|
263
|
+
await sogni.set_tokens(token=access_token, refresh_token=refresh_token)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Username/password login and signing are available through `sogni.account.login`.
|
|
267
|
+
API-key use does not require storing a wallet password.
|
|
268
|
+
|
|
269
|
+
## Development
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
python -m pip install -e '.[dev]'
|
|
273
|
+
pytest
|
|
274
|
+
ruff check sogni_client tests
|
|
275
|
+
ruff format --check sogni_client tests
|
|
276
|
+
python -m build
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Live integration tests require explicit credentials and are not run by default.
|
|
280
|
+
|
|
281
|
+
## Documentation
|
|
282
|
+
|
|
283
|
+
- [Python SDK quickstart](https://docs.sogni.ai/sogni-sdk/python/)
|
|
284
|
+
- [Sogni SDK overview](https://docs.sogni.ai/sogni-sdk/)
|
|
285
|
+
- [REST API reference](https://docs.sogni.ai/api-reference/)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Generate one image with an API key from the environment.
|
|
2
|
+
|
|
3
|
+
Uses Krea 2 Turbo: it is the only model free monthly render credits can be spent
|
|
4
|
+
on over the API, so this runs on a brand-new key without paid credits.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from sogni_client import SogniClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def main() -> None:
|
|
14
|
+
api_key = os.environ["SOGNI_API_KEY"]
|
|
15
|
+
async with await SogniClient.create(api_key=api_key) as sogni:
|
|
16
|
+
project = await sogni.projects.create(
|
|
17
|
+
type="image",
|
|
18
|
+
model_id="krea2_turbo_fp8_scaled",
|
|
19
|
+
positive_prompt="A glass greenhouse drifting above Singapore at dawn",
|
|
20
|
+
negative_prompt="text, watermark",
|
|
21
|
+
number_of_media=1,
|
|
22
|
+
width=1024,
|
|
23
|
+
height=1024,
|
|
24
|
+
steps=8,
|
|
25
|
+
)
|
|
26
|
+
for url in await project.wait_for_completion():
|
|
27
|
+
print(url)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
asyncio.run(main())
|