ensoul 0.1.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.
- ensoul-0.1.0/.gitignore +39 -0
- ensoul-0.1.0/PKG-INFO +215 -0
- ensoul-0.1.0/README.md +188 -0
- ensoul-0.1.0/examples/quickstart.py +75 -0
- ensoul-0.1.0/pyproject.toml +55 -0
- ensoul-0.1.0/src/ensoul/__init__.py +60 -0
- ensoul-0.1.0/src/ensoul/_types.py +29 -0
- ensoul-0.1.0/src/ensoul/auth.py +67 -0
- ensoul-0.1.0/src/ensoul/client.py +182 -0
- ensoul-0.1.0/src/ensoul/config.py +33 -0
- ensoul-0.1.0/src/ensoul/errors.py +189 -0
- ensoul-0.1.0/src/ensoul/generated/aggregate.py +47 -0
- ensoul-0.1.0/src/ensoul/generated/auth.py +56 -0
- ensoul-0.1.0/src/ensoul/generated/chat.py +76 -0
- ensoul-0.1.0/src/ensoul/generated/domains.py +69 -0
- ensoul-0.1.0/src/ensoul/generated/endpoints.py +2106 -0
- ensoul-0.1.0/src/ensoul/generated/enums.py +82 -0
- ensoul-0.1.0/src/ensoul/generated/personas.py +112 -0
- ensoul-0.1.0/src/ensoul/generated/sessions.py +54 -0
- ensoul-0.1.0/src/ensoul/generated/simulations.py +98 -0
- ensoul-0.1.0/src/ensoul/http.py +349 -0
- ensoul-0.1.0/src/ensoul/pagination.py +163 -0
- ensoul-0.1.0/src/ensoul/rate_limit.py +89 -0
- ensoul-0.1.0/src/ensoul/resources/__init__.py +40 -0
- ensoul-0.1.0/src/ensoul/resources/aggregate.py +204 -0
- ensoul-0.1.0/src/ensoul/resources/auth_resource.py +121 -0
- ensoul-0.1.0/src/ensoul/resources/chat.py +203 -0
- ensoul-0.1.0/src/ensoul/resources/domains.py +126 -0
- ensoul-0.1.0/src/ensoul/resources/frameworks.py +144 -0
- ensoul-0.1.0/src/ensoul/resources/health.py +59 -0
- ensoul-0.1.0/src/ensoul/resources/info.py +67 -0
- ensoul-0.1.0/src/ensoul/resources/memory.py +182 -0
- ensoul-0.1.0/src/ensoul/resources/personas.py +237 -0
- ensoul-0.1.0/src/ensoul/resources/sessions.py +176 -0
- ensoul-0.1.0/src/ensoul/resources/simulations.py +261 -0
- ensoul-0.1.0/src/ensoul/streaming.py +250 -0
- ensoul-0.1.0/tests/.gitkeep +0 -0
- ensoul-0.1.0/tests/__init__.py +0 -0
- ensoul-0.1.0/tests/conftest.py +51 -0
- ensoul-0.1.0/tests/test_chat.py +143 -0
- ensoul-0.1.0/tests/test_client.py +126 -0
- ensoul-0.1.0/tests/test_config.py +54 -0
- ensoul-0.1.0/tests/test_conformance.py +430 -0
- ensoul-0.1.0/tests/test_errors.py +189 -0
- ensoul-0.1.0/tests/test_integration.py +305 -0
- ensoul-0.1.0/tests/test_pagination.py +159 -0
- ensoul-0.1.0/tests/test_personas.py +218 -0
- ensoul-0.1.0/tests/test_streaming.py +214 -0
- ensoul-0.1.0/uv.lock +484 -0
ensoul-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
.venv/
|
|
7
|
+
*.egg
|
|
8
|
+
|
|
9
|
+
# TypeScript / Node
|
|
10
|
+
node_modules/
|
|
11
|
+
dist/
|
|
12
|
+
*.tsbuildinfo
|
|
13
|
+
|
|
14
|
+
# Swift
|
|
15
|
+
.build/
|
|
16
|
+
.swiftpm/
|
|
17
|
+
Package.resolved
|
|
18
|
+
|
|
19
|
+
# Unity
|
|
20
|
+
Library/
|
|
21
|
+
Temp/
|
|
22
|
+
obj/
|
|
23
|
+
Logs/
|
|
24
|
+
|
|
25
|
+
# C++
|
|
26
|
+
build/
|
|
27
|
+
cmake-build-*/
|
|
28
|
+
*.o
|
|
29
|
+
*.a
|
|
30
|
+
|
|
31
|
+
# Kotlin
|
|
32
|
+
.gradle/
|
|
33
|
+
build/
|
|
34
|
+
*.class
|
|
35
|
+
|
|
36
|
+
# General
|
|
37
|
+
.DS_Store
|
|
38
|
+
*.swp
|
|
39
|
+
*.swo
|
ensoul-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ensoul
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the Ensoul API
|
|
5
|
+
Project-URL: Homepage, https://ensoul-ai.com
|
|
6
|
+
Project-URL: Documentation, https://ensoul-ai.com/products/studio/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/ensoul-ai/ensoul-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/ensoul-ai/ensoul-sdk/issues
|
|
9
|
+
Author-email: Ensoul <support@ensoul.ai>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: httpx>=0.25.0
|
|
19
|
+
Requires-Dist: pydantic>=2.0.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
24
|
+
Requires-Dist: respx; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Ensoul Python SDK
|
|
29
|
+
|
|
30
|
+
Python client library for the Ensoul personality simulation API.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install ensoul
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from ensoul import Ensoul
|
|
42
|
+
|
|
43
|
+
client = Ensoul(api_key="your-api-key")
|
|
44
|
+
|
|
45
|
+
# Create a persona
|
|
46
|
+
persona = client.personas.create(name="Alex", domain="my_domain")
|
|
47
|
+
|
|
48
|
+
# Send a chat message
|
|
49
|
+
response = client.chat.send(persona.id, "Hello, who are you?")
|
|
50
|
+
print(response.response)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The API key can also be set via the `ENSOUL_API_KEY` environment variable, in which case no `api_key` argument is needed.
|
|
54
|
+
|
|
55
|
+
The client supports context managers for automatic cleanup:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
with Ensoul(api_key="your-api-key") as client:
|
|
59
|
+
persona = client.personas.get("persona-id")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Async Usage
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
from ensoul import AsyncEnsoul
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
async with AsyncEnsoul(api_key="your-api-key") as client:
|
|
70
|
+
persona = await client.personas.create(name="Jordan", domain="my_domain")
|
|
71
|
+
response = await client.chat.send(persona.id, "Tell me about yourself.")
|
|
72
|
+
print(response.response)
|
|
73
|
+
|
|
74
|
+
asyncio.run(main())
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Streaming
|
|
78
|
+
|
|
79
|
+
Chat supports server-sent events (SSE) for streaming responses:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from ensoul import Ensoul
|
|
83
|
+
from ensoul.streaming import parse_chat_event
|
|
84
|
+
|
|
85
|
+
client = Ensoul(api_key="your-api-key")
|
|
86
|
+
|
|
87
|
+
stream = client.chat.stream("persona-id", "What do you think about music?")
|
|
88
|
+
for event in stream.events():
|
|
89
|
+
parsed = parse_chat_event(event)
|
|
90
|
+
if not parsed.is_final:
|
|
91
|
+
print(parsed.chunk, end="", flush=True)
|
|
92
|
+
else:
|
|
93
|
+
print() # newline after stream completes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The async client returns an `AsyncSSEStream` that works the same way with `async for`.
|
|
97
|
+
|
|
98
|
+
## Pagination
|
|
99
|
+
|
|
100
|
+
List endpoints return a `SyncPage` (or `AsyncPage`) object. Use `.auto_paging_iter()` to iterate through all pages automatically:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# Manual page access
|
|
104
|
+
page = client.personas.list(per_page=50)
|
|
105
|
+
print(page.items) # current page items
|
|
106
|
+
print(page.total) # total count across all pages
|
|
107
|
+
|
|
108
|
+
# Automatic pagination — fetches subsequent pages as needed
|
|
109
|
+
for persona in client.personas.list().auto_paging_iter():
|
|
110
|
+
print(persona.name)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Async pagination works the same way with `async for`.
|
|
114
|
+
|
|
115
|
+
## Error Handling
|
|
116
|
+
|
|
117
|
+
All SDK errors inherit from `EnsoulError`. HTTP errors are subclasses of `APIError`:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from ensoul.errors import (
|
|
121
|
+
EnsoulError,
|
|
122
|
+
APIError,
|
|
123
|
+
AuthenticationError,
|
|
124
|
+
AuthorizationError,
|
|
125
|
+
NotFoundError,
|
|
126
|
+
RateLimitError,
|
|
127
|
+
ValidationError,
|
|
128
|
+
ConflictError,
|
|
129
|
+
ServerError,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
persona = client.personas.get("nonexistent-id")
|
|
134
|
+
except NotFoundError:
|
|
135
|
+
print("Persona not found")
|
|
136
|
+
except AuthenticationError:
|
|
137
|
+
print("Invalid or missing API key")
|
|
138
|
+
except RateLimitError:
|
|
139
|
+
print("Rate limit exceeded — back off and retry")
|
|
140
|
+
except APIError as e:
|
|
141
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Configuration
|
|
145
|
+
|
|
146
|
+
The client reads two environment variables as defaults:
|
|
147
|
+
|
|
148
|
+
| Variable | Purpose |
|
|
149
|
+
|----------|---------|
|
|
150
|
+
| `ENSOUL_API_KEY` | API key (avoids passing `api_key=` in code) |
|
|
151
|
+
| `ENSOUL_BASE_URL` | API base URL (default: `https://api.ensoul-ai.com`) |
|
|
152
|
+
|
|
153
|
+
**Demo API** — the current hosted demo is available at:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
export ENSOUL_BASE_URL="https://api.demo.ensoul-ai.com"
|
|
157
|
+
export ENSOUL_API_KEY="your-api-key"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
With these set, `Ensoul()` connects to the demo with no constructor arguments.
|
|
161
|
+
|
|
162
|
+
You can also pass the base URL explicitly:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
client = Ensoul(api_key="ens_...", base_url="https://api.demo.ensoul-ai.com")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Authentication
|
|
169
|
+
|
|
170
|
+
**API key** (recommended):
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
client = Ensoul(api_key="ens_...")
|
|
174
|
+
# or set ENSOUL_API_KEY in the environment
|
|
175
|
+
client = Ensoul()
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Bearer token**:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
client = Ensoul(bearer_token="eyJ...")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**OAuth2 token exchange** (client credentials flow):
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
token_response = client.auth.token(
|
|
188
|
+
grant_type="client_credentials",
|
|
189
|
+
client_id="your-client-id",
|
|
190
|
+
client_secret="your-client-secret",
|
|
191
|
+
)
|
|
192
|
+
authed_client = Ensoul(bearer_token=token_response.access_token)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Resources
|
|
196
|
+
|
|
197
|
+
| Namespace | Description |
|
|
198
|
+
|-----------|-------------|
|
|
199
|
+
| `client.personas` | CRUD, list (paginated), batch create, personality vectors, filters, connections |
|
|
200
|
+
| `client.chat` | Send messages, streaming SSE, conversation history |
|
|
201
|
+
| `client.domains` | Domain configuration management |
|
|
202
|
+
| `client.simulations` | Time-based evolution simulations |
|
|
203
|
+
| `client.aggregate` | Aggregate queries with streaming results |
|
|
204
|
+
| `client.memory` | Persona memory management |
|
|
205
|
+
| `client.sessions` | Hierarchical session orchestration |
|
|
206
|
+
| `client.frameworks` | Framework management |
|
|
207
|
+
| `client.auth` | OAuth2 token exchange and API key management |
|
|
208
|
+
| `client.health` | Service health checks |
|
|
209
|
+
| `client.info` | Server info and configuration |
|
|
210
|
+
|
|
211
|
+
## Requirements
|
|
212
|
+
|
|
213
|
+
- Python 3.11+
|
|
214
|
+
- [`httpx`](https://www.python-httpx.org/) — HTTP transport
|
|
215
|
+
- [`pydantic`](https://docs.pydantic.dev/) 2.x — request and response models
|
ensoul-0.1.0/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Ensoul Python SDK
|
|
2
|
+
|
|
3
|
+
Python client library for the Ensoul personality simulation API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install ensoul
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from ensoul import Ensoul
|
|
15
|
+
|
|
16
|
+
client = Ensoul(api_key="your-api-key")
|
|
17
|
+
|
|
18
|
+
# Create a persona
|
|
19
|
+
persona = client.personas.create(name="Alex", domain="my_domain")
|
|
20
|
+
|
|
21
|
+
# Send a chat message
|
|
22
|
+
response = client.chat.send(persona.id, "Hello, who are you?")
|
|
23
|
+
print(response.response)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The API key can also be set via the `ENSOUL_API_KEY` environment variable, in which case no `api_key` argument is needed.
|
|
27
|
+
|
|
28
|
+
The client supports context managers for automatic cleanup:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
with Ensoul(api_key="your-api-key") as client:
|
|
32
|
+
persona = client.personas.get("persona-id")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Async Usage
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import asyncio
|
|
39
|
+
from ensoul import AsyncEnsoul
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
async with AsyncEnsoul(api_key="your-api-key") as client:
|
|
43
|
+
persona = await client.personas.create(name="Jordan", domain="my_domain")
|
|
44
|
+
response = await client.chat.send(persona.id, "Tell me about yourself.")
|
|
45
|
+
print(response.response)
|
|
46
|
+
|
|
47
|
+
asyncio.run(main())
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Streaming
|
|
51
|
+
|
|
52
|
+
Chat supports server-sent events (SSE) for streaming responses:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from ensoul import Ensoul
|
|
56
|
+
from ensoul.streaming import parse_chat_event
|
|
57
|
+
|
|
58
|
+
client = Ensoul(api_key="your-api-key")
|
|
59
|
+
|
|
60
|
+
stream = client.chat.stream("persona-id", "What do you think about music?")
|
|
61
|
+
for event in stream.events():
|
|
62
|
+
parsed = parse_chat_event(event)
|
|
63
|
+
if not parsed.is_final:
|
|
64
|
+
print(parsed.chunk, end="", flush=True)
|
|
65
|
+
else:
|
|
66
|
+
print() # newline after stream completes
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The async client returns an `AsyncSSEStream` that works the same way with `async for`.
|
|
70
|
+
|
|
71
|
+
## Pagination
|
|
72
|
+
|
|
73
|
+
List endpoints return a `SyncPage` (or `AsyncPage`) object. Use `.auto_paging_iter()` to iterate through all pages automatically:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# Manual page access
|
|
77
|
+
page = client.personas.list(per_page=50)
|
|
78
|
+
print(page.items) # current page items
|
|
79
|
+
print(page.total) # total count across all pages
|
|
80
|
+
|
|
81
|
+
# Automatic pagination — fetches subsequent pages as needed
|
|
82
|
+
for persona in client.personas.list().auto_paging_iter():
|
|
83
|
+
print(persona.name)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Async pagination works the same way with `async for`.
|
|
87
|
+
|
|
88
|
+
## Error Handling
|
|
89
|
+
|
|
90
|
+
All SDK errors inherit from `EnsoulError`. HTTP errors are subclasses of `APIError`:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from ensoul.errors import (
|
|
94
|
+
EnsoulError,
|
|
95
|
+
APIError,
|
|
96
|
+
AuthenticationError,
|
|
97
|
+
AuthorizationError,
|
|
98
|
+
NotFoundError,
|
|
99
|
+
RateLimitError,
|
|
100
|
+
ValidationError,
|
|
101
|
+
ConflictError,
|
|
102
|
+
ServerError,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
persona = client.personas.get("nonexistent-id")
|
|
107
|
+
except NotFoundError:
|
|
108
|
+
print("Persona not found")
|
|
109
|
+
except AuthenticationError:
|
|
110
|
+
print("Invalid or missing API key")
|
|
111
|
+
except RateLimitError:
|
|
112
|
+
print("Rate limit exceeded — back off and retry")
|
|
113
|
+
except APIError as e:
|
|
114
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
The client reads two environment variables as defaults:
|
|
120
|
+
|
|
121
|
+
| Variable | Purpose |
|
|
122
|
+
|----------|---------|
|
|
123
|
+
| `ENSOUL_API_KEY` | API key (avoids passing `api_key=` in code) |
|
|
124
|
+
| `ENSOUL_BASE_URL` | API base URL (default: `https://api.ensoul-ai.com`) |
|
|
125
|
+
|
|
126
|
+
**Demo API** — the current hosted demo is available at:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export ENSOUL_BASE_URL="https://api.demo.ensoul-ai.com"
|
|
130
|
+
export ENSOUL_API_KEY="your-api-key"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
With these set, `Ensoul()` connects to the demo with no constructor arguments.
|
|
134
|
+
|
|
135
|
+
You can also pass the base URL explicitly:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
client = Ensoul(api_key="ens_...", base_url="https://api.demo.ensoul-ai.com")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Authentication
|
|
142
|
+
|
|
143
|
+
**API key** (recommended):
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
client = Ensoul(api_key="ens_...")
|
|
147
|
+
# or set ENSOUL_API_KEY in the environment
|
|
148
|
+
client = Ensoul()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Bearer token**:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
client = Ensoul(bearer_token="eyJ...")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**OAuth2 token exchange** (client credentials flow):
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
token_response = client.auth.token(
|
|
161
|
+
grant_type="client_credentials",
|
|
162
|
+
client_id="your-client-id",
|
|
163
|
+
client_secret="your-client-secret",
|
|
164
|
+
)
|
|
165
|
+
authed_client = Ensoul(bearer_token=token_response.access_token)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Resources
|
|
169
|
+
|
|
170
|
+
| Namespace | Description |
|
|
171
|
+
|-----------|-------------|
|
|
172
|
+
| `client.personas` | CRUD, list (paginated), batch create, personality vectors, filters, connections |
|
|
173
|
+
| `client.chat` | Send messages, streaming SSE, conversation history |
|
|
174
|
+
| `client.domains` | Domain configuration management |
|
|
175
|
+
| `client.simulations` | Time-based evolution simulations |
|
|
176
|
+
| `client.aggregate` | Aggregate queries with streaming results |
|
|
177
|
+
| `client.memory` | Persona memory management |
|
|
178
|
+
| `client.sessions` | Hierarchical session orchestration |
|
|
179
|
+
| `client.frameworks` | Framework management |
|
|
180
|
+
| `client.auth` | OAuth2 token exchange and API key management |
|
|
181
|
+
| `client.health` | Service health checks |
|
|
182
|
+
| `client.info` | Server info and configuration |
|
|
183
|
+
|
|
184
|
+
## Requirements
|
|
185
|
+
|
|
186
|
+
- Python 3.11+
|
|
187
|
+
- [`httpx`](https://www.python-httpx.org/) — HTTP transport
|
|
188
|
+
- [`pydantic`](https://docs.pydantic.dev/) 2.x — request and response models
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Ensoul SDK Quickstart Example.
|
|
2
|
+
|
|
3
|
+
Set ENSOUL_API_KEY environment variable before running.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python examples/quickstart.py
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
from ensoul import AsyncEnsoul, Ensoul, NotFoundError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def sync_example() -> None:
|
|
18
|
+
"""Synchronous usage examples."""
|
|
19
|
+
client = Ensoul() # reads ENSOUL_API_KEY from env
|
|
20
|
+
|
|
21
|
+
# List domains
|
|
22
|
+
domains = client.domains.list()
|
|
23
|
+
print("Domains:")
|
|
24
|
+
for domain in domains.items:
|
|
25
|
+
print(f" {domain}")
|
|
26
|
+
|
|
27
|
+
# Create a persona
|
|
28
|
+
persona = client.personas.create(
|
|
29
|
+
name="Research Participant",
|
|
30
|
+
domain="example_domain",
|
|
31
|
+
personality_data={"openness": 75, "conscientiousness": 60},
|
|
32
|
+
)
|
|
33
|
+
print(f"\nCreated persona: {persona.id}")
|
|
34
|
+
|
|
35
|
+
# Chat
|
|
36
|
+
response = client.chat.send(persona.id, "What are your thoughts on technology?")
|
|
37
|
+
print(f"Response: {response.response}")
|
|
38
|
+
|
|
39
|
+
# Streaming chat
|
|
40
|
+
print("Streaming: ", end="")
|
|
41
|
+
for event in client.chat.stream(persona.id, "Tell me more."):
|
|
42
|
+
try:
|
|
43
|
+
data = json.loads(event.data)
|
|
44
|
+
if "chunk" in data:
|
|
45
|
+
print(data["chunk"], end="", flush=True)
|
|
46
|
+
except json.JSONDecodeError:
|
|
47
|
+
pass
|
|
48
|
+
print()
|
|
49
|
+
|
|
50
|
+
# Auto-pagination — iterates all pages automatically
|
|
51
|
+
print("\nAll personas:")
|
|
52
|
+
for p in client.personas.list(per_page=10).auto_paging_iter():
|
|
53
|
+
print(f" {p.name}")
|
|
54
|
+
|
|
55
|
+
# Error handling
|
|
56
|
+
try:
|
|
57
|
+
client.personas.get("nonexistent_id")
|
|
58
|
+
except NotFoundError as e:
|
|
59
|
+
print(f"\nExpected error: {e.message}")
|
|
60
|
+
|
|
61
|
+
client.close()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def async_example() -> None:
|
|
65
|
+
"""Async usage examples."""
|
|
66
|
+
async with AsyncEnsoul() as client:
|
|
67
|
+
personas = await client.personas.list()
|
|
68
|
+
print("Async personas:")
|
|
69
|
+
for p in personas.items:
|
|
70
|
+
print(f" {p.name}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
sync_example()
|
|
75
|
+
asyncio.run(async_example())
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ensoul"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the Ensoul API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ensoul", email = "support@ensoul.ai" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.25.0",
|
|
25
|
+
"pydantic>=2.0.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://ensoul-ai.com"
|
|
30
|
+
Documentation = "https://ensoul-ai.com/products/studio/docs"
|
|
31
|
+
Repository = "https://github.com/ensoul-ai/ensoul-sdk"
|
|
32
|
+
Issues = "https://github.com/ensoul-ai/ensoul-sdk/issues"
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest",
|
|
37
|
+
"pytest-asyncio",
|
|
38
|
+
"respx",
|
|
39
|
+
"ruff",
|
|
40
|
+
"mypy",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/ensoul"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
target-version = "py311"
|
|
48
|
+
line-length = 100
|
|
49
|
+
|
|
50
|
+
[tool.mypy]
|
|
51
|
+
python_version = "3.11"
|
|
52
|
+
strict = true
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Ensoul SDK for Python."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ensoul.client import AsyncEnsoul, Ensoul
|
|
6
|
+
from ensoul.config import ClientConfig
|
|
7
|
+
from ensoul.errors import (
|
|
8
|
+
APIError,
|
|
9
|
+
AuthenticationError,
|
|
10
|
+
AuthorizationError,
|
|
11
|
+
ConflictError,
|
|
12
|
+
EnsoulError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
RateLimitError,
|
|
15
|
+
ServerError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
)
|
|
18
|
+
from ensoul.generated.auth import APIKeyResponse, TokenResponse, UserResponse
|
|
19
|
+
from ensoul.generated.chat import ChatRequest, ChatResponse, ConversationResponse
|
|
20
|
+
from ensoul.generated.enums import SessionStatus, SimulationStatus
|
|
21
|
+
from ensoul.generated.personas import (
|
|
22
|
+
PersonaBatchCreate,
|
|
23
|
+
PersonaBatchResponse,
|
|
24
|
+
PersonaCreate,
|
|
25
|
+
PersonaResponse,
|
|
26
|
+
PersonalityVectorResponse,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"Ensoul",
|
|
33
|
+
"AsyncEnsoul",
|
|
34
|
+
"ClientConfig",
|
|
35
|
+
# Errors
|
|
36
|
+
"EnsoulError",
|
|
37
|
+
"APIError",
|
|
38
|
+
"AuthenticationError",
|
|
39
|
+
"AuthorizationError",
|
|
40
|
+
"NotFoundError",
|
|
41
|
+
"RateLimitError",
|
|
42
|
+
"ValidationError",
|
|
43
|
+
"ConflictError",
|
|
44
|
+
"ServerError",
|
|
45
|
+
# Types
|
|
46
|
+
"PersonaCreate",
|
|
47
|
+
"PersonaResponse",
|
|
48
|
+
"PersonaBatchCreate",
|
|
49
|
+
"PersonaBatchResponse",
|
|
50
|
+
"PersonalityVectorResponse",
|
|
51
|
+
"ChatRequest",
|
|
52
|
+
"ChatResponse",
|
|
53
|
+
"ConversationResponse",
|
|
54
|
+
"TokenResponse",
|
|
55
|
+
"APIKeyResponse",
|
|
56
|
+
"UserResponse",
|
|
57
|
+
"SimulationStatus",
|
|
58
|
+
"SessionStatus",
|
|
59
|
+
"__version__",
|
|
60
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Shared type aliases for the Ensoul SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Protocol, TypeVar, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"HeadersLike",
|
|
11
|
+
"QueryParams",
|
|
12
|
+
"RequestBody",
|
|
13
|
+
"T",
|
|
14
|
+
"PaginatedResponse",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
HeadersLike = dict[str, str]
|
|
18
|
+
QueryParams = dict[str, Any]
|
|
19
|
+
RequestBody = dict[str, Any] | BaseModel
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@runtime_checkable
|
|
24
|
+
class PaginatedResponse(Protocol):
|
|
25
|
+
items: list
|
|
26
|
+
total: int
|
|
27
|
+
page: int
|
|
28
|
+
per_page: int
|
|
29
|
+
pages: int
|