hakim 1.0.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.
- hakim-1.0.0/.gitignore +11 -0
- hakim-1.0.0/LICENSE +21 -0
- hakim-1.0.0/PKG-INFO +259 -0
- hakim-1.0.0/README.md +222 -0
- hakim-1.0.0/pyproject.toml +101 -0
- hakim-1.0.0/src/hakim/__init__.py +64 -0
- hakim-1.0.0/src/hakim/_transport.py +345 -0
- hakim-1.0.0/src/hakim/_types.py +789 -0
- hakim-1.0.0/src/hakim/_version.py +7 -0
- hakim-1.0.0/src/hakim/audio/__init__.py +18 -0
- hakim-1.0.0/src/hakim/audio/speech.py +177 -0
- hakim-1.0.0/src/hakim/audio/speech_stream_ws.py +262 -0
- hakim-1.0.0/src/hakim/audio/transcriptions.py +361 -0
- hakim-1.0.0/src/hakim/audio/translate.py +100 -0
- hakim-1.0.0/src/hakim/audio/translate_stream_ws.py +351 -0
- hakim-1.0.0/src/hakim/audio/voices.py +129 -0
- hakim-1.0.0/src/hakim/chat/__init__.py +5 -0
- hakim-1.0.0/src/hakim/chat/completions.py +297 -0
- hakim-1.0.0/src/hakim/client.py +464 -0
- hakim-1.0.0/src/hakim/errors.py +181 -0
- hakim-1.0.0/src/hakim/jobs.py +57 -0
- hakim-1.0.0/src/hakim/settings.py +99 -0
- hakim-1.0.0/src/hakim/usage.py +101 -0
- hakim-1.0.0/src/hakim/webhooks.py +237 -0
- hakim-1.0.0/tests/__init__.py +0 -0
- hakim-1.0.0/tests/_helpers.py +21 -0
- hakim-1.0.0/tests/conftest.py +22 -0
- hakim-1.0.0/tests/test_audio_speech.py +110 -0
- hakim-1.0.0/tests/test_audio_speech_stream_ws.py +155 -0
- hakim-1.0.0/tests/test_audio_transcriptions.py +208 -0
- hakim-1.0.0/tests/test_audio_translate_stream_ws.py +270 -0
- hakim-1.0.0/tests/test_chat_completions.py +247 -0
- hakim-1.0.0/tests/test_jobs.py +93 -0
- hakim-1.0.0/tests/test_package.py +23 -0
- hakim-1.0.0/tests/test_settings.py +160 -0
- hakim-1.0.0/tests/test_sync_client.py +46 -0
- hakim-1.0.0/tests/test_transport.py +334 -0
- hakim-1.0.0/tests/test_usage.py +61 -0
- hakim-1.0.0/tests/test_voices.py +137 -0
- hakim-1.0.0/tests/test_webhooks.py +182 -0
hakim-1.0.0/.gitignore
ADDED
hakim-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hakim AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hakim-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hakim
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python SDK for the Hakim voice AI API (TTS, STT, voice cloning, webhooks, jobs).
|
|
5
|
+
Project-URL: Homepage, https://tryhakim.ai
|
|
6
|
+
Project-URL: Documentation, https://tryhakim.ai/docs
|
|
7
|
+
Project-URL: Source, https://github.com/tryHakimAI/hakim-python
|
|
8
|
+
Project-URL: Issues, https://github.com/tryHakimAI/hakim-python/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/tryHakimAI/hakim-python/blob/main/CHANGELOG.md
|
|
10
|
+
Author: Hakim
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: ai,arabic,hakim,speech,stt,tts,voice,voice-cloning,webhooks
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: httpx>=0.27
|
|
28
|
+
Requires-Dist: websockets>=13
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
31
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.7; extra == 'dev'
|
|
35
|
+
Requires-Dist: twine>=5; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# hakim — official Python SDK
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/hakim/)
|
|
41
|
+
[](https://pypi.org/project/hakim/)
|
|
42
|
+
|
|
43
|
+
Official Python SDK for [Hakim](https://tryhakim.ai) — Arabic-first TTS,
|
|
44
|
+
STT, voice cloning, webhooks, and batch jobs. Mirrors the surface of the
|
|
45
|
+
Node SDK (`@hakim/voice`).
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install hakim
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick start (async)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import asyncio
|
|
57
|
+
from hakim import AsyncHakim
|
|
58
|
+
|
|
59
|
+
async def main() -> None:
|
|
60
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
61
|
+
audio = await hakim.audio.speech.create(
|
|
62
|
+
model="hakim-fast-v1",
|
|
63
|
+
input="مرحبا بالعالم",
|
|
64
|
+
voice="omar",
|
|
65
|
+
)
|
|
66
|
+
with open("out.mp3", "wb") as f:
|
|
67
|
+
f.write(audio)
|
|
68
|
+
|
|
69
|
+
asyncio.run(main())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Quick start (sync)
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from hakim import Hakim
|
|
76
|
+
|
|
77
|
+
with Hakim(api_key="hk_...") as hakim:
|
|
78
|
+
audio = hakim.tts_create(
|
|
79
|
+
model="hakim-fast-v1",
|
|
80
|
+
input="مرحبا بالعالم",
|
|
81
|
+
voice="omar",
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Chat completions
|
|
86
|
+
|
|
87
|
+
OpenAI-compatible chat API backed by `hakim-chat-v1` (HKM LLM 1).
|
|
88
|
+
Drop-in for any code that targets the OpenAI Chat Completions
|
|
89
|
+
reference — swap the base URL and key and you're done.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
93
|
+
# Non-streaming.
|
|
94
|
+
completion = await hakim.chat.completions.create(
|
|
95
|
+
model="hakim-chat-v1",
|
|
96
|
+
messages=[
|
|
97
|
+
{"role": "system", "content": "أنت مساعد عربي مفيد."},
|
|
98
|
+
{"role": "user", "content": "اكتب قصيدة قصيرة عن البحر."},
|
|
99
|
+
],
|
|
100
|
+
temperature=0.7,
|
|
101
|
+
)
|
|
102
|
+
print(completion["choices"][0]["message"]["content"])
|
|
103
|
+
|
|
104
|
+
# Streaming (SSE). The final chunk carries the rolled-up `usage`.
|
|
105
|
+
stream = await hakim.chat.completions.stream(
|
|
106
|
+
model="hakim-chat-v1",
|
|
107
|
+
messages=[{"role": "user", "content": "مرحبا!"}],
|
|
108
|
+
)
|
|
109
|
+
async for chunk in stream:
|
|
110
|
+
delta = chunk["choices"][0]["delta"]
|
|
111
|
+
if "content" in delta:
|
|
112
|
+
print(delta["content"], end="", flush=True)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Reasoning / chain-of-thought
|
|
116
|
+
|
|
117
|
+
`hakim-chat-v1` is a thinking-capable model. CoT is **off by
|
|
118
|
+
default** — it adds 10–50× latency and burns completion tokens
|
|
119
|
+
that don't reach your UI in time. Opt in on **non-streaming**
|
|
120
|
+
requests only:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
completion = await hakim.chat.completions.create(
|
|
124
|
+
model="hakim-chat-v1",
|
|
125
|
+
messages=[{"role": "user", "content": "حلّ ٢٤ × ١٧ خطوة بخطوة."}],
|
|
126
|
+
reasoning={"enabled": True},
|
|
127
|
+
)
|
|
128
|
+
print(completion["choices"][0]["message"]["reasoning"]) # chain-of-thought
|
|
129
|
+
print(completion["choices"][0]["message"]["content"]) # final answer
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Streaming requests with `reasoning={"enabled": True}` are rejected
|
|
133
|
+
with a `400 invalid_request_error` — real-time agents cannot afford
|
|
134
|
+
the latency cost. The `stream()` method intentionally omits the
|
|
135
|
+
`reasoning` parameter so a misuse fails at edit time rather than
|
|
136
|
+
on the wire.
|
|
137
|
+
|
|
138
|
+
## Streaming TTS
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
142
|
+
stream = await hakim.audio.speech.stream(
|
|
143
|
+
model="hakim-fast-v1",
|
|
144
|
+
input="salam",
|
|
145
|
+
voice="omar",
|
|
146
|
+
)
|
|
147
|
+
async for chunk in stream:
|
|
148
|
+
write_to_speaker(chunk)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Speech-to-text (sync upload)
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
155
|
+
result = await hakim.audio.transcriptions.create(
|
|
156
|
+
file=open("clip.wav", "rb"),
|
|
157
|
+
model="hakim-arab-v2",
|
|
158
|
+
response_format="json",
|
|
159
|
+
)
|
|
160
|
+
print(result["text"])
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Speech-to-text (realtime)
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
167
|
+
async with hakim.audio.transcriptions.stream(
|
|
168
|
+
model="hakim-arab-v2",
|
|
169
|
+
language="ar",
|
|
170
|
+
sample_rate=16000,
|
|
171
|
+
) as handle:
|
|
172
|
+
async def pump_audio() -> None:
|
|
173
|
+
async for chunk in mic_iter():
|
|
174
|
+
await handle.send_audio(chunk)
|
|
175
|
+
await handle.commit()
|
|
176
|
+
|
|
177
|
+
asyncio.create_task(pump_audio())
|
|
178
|
+
|
|
179
|
+
async for event in handle:
|
|
180
|
+
if event["type"] == "partial":
|
|
181
|
+
print("~", event["text"])
|
|
182
|
+
elif event["type"] == "final":
|
|
183
|
+
print("✔", event["text"])
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Voice cloning
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
with open("my_voice.wav", "rb") as f:
|
|
190
|
+
voice = await hakim.voices.create(
|
|
191
|
+
sample=f,
|
|
192
|
+
name="My Voice",
|
|
193
|
+
language="ar",
|
|
194
|
+
consent_confirmed=True,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
while voice["status"] == "processing":
|
|
198
|
+
await asyncio.sleep(2.0)
|
|
199
|
+
voice = await hakim.voices.retrieve(voice["id"])
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Webhooks
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from hakim import verify_webhook_signature
|
|
206
|
+
|
|
207
|
+
def handle_webhook(request) -> None:
|
|
208
|
+
body = request.body
|
|
209
|
+
result = verify_webhook_signature(
|
|
210
|
+
secret="whsec_...",
|
|
211
|
+
body=body,
|
|
212
|
+
signature=request.headers["hakim-signature"],
|
|
213
|
+
)
|
|
214
|
+
if not result.valid:
|
|
215
|
+
raise ValueError(f"bad signature: {result.reason}")
|
|
216
|
+
event = json.loads(body)
|
|
217
|
+
...
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Errors
|
|
221
|
+
|
|
222
|
+
Every HTTP-level failure raises a :class:`HakimError` subclass you can
|
|
223
|
+
branch on:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from hakim import Hakim, RateLimitError, HakimError
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
hakim.tts_create(...)
|
|
230
|
+
except RateLimitError as err:
|
|
231
|
+
retry_after = (err.retry_after_ms or 1000) / 1000
|
|
232
|
+
time.sleep(retry_after)
|
|
233
|
+
except HakimError as err:
|
|
234
|
+
print(err.status, err.code, err.request_id)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Observability
|
|
238
|
+
|
|
239
|
+
Every request auto-attaches:
|
|
240
|
+
|
|
241
|
+
- `Authorization: Bearer <api_key>`
|
|
242
|
+
- `User-Agent: hakim-python/<version> (python/<py_version>; <platform>)`
|
|
243
|
+
- `X-Request-Id: sdk-<uuid>` (echoed back by the server — attach to your
|
|
244
|
+
logs for end-to-end tracing).
|
|
245
|
+
- `Idempotency-Key: <uuid>` on mutating JSON calls (auto-generated;
|
|
246
|
+
override via `idempotency_key=` when you need deterministic keys).
|
|
247
|
+
|
|
248
|
+
Transient failures (5xx, 429, 408, 425, network errors) are retried
|
|
249
|
+
with exponential back-off (±25% jitter) up to `max_retries` (default
|
|
250
|
+
2). `Retry-After` is honoured when the server sends it.
|
|
251
|
+
|
|
252
|
+
## Development
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
pip install -e '.[dev]'
|
|
256
|
+
ruff check src tests
|
|
257
|
+
mypy src
|
|
258
|
+
pytest
|
|
259
|
+
```
|
hakim-1.0.0/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# hakim — official Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/hakim/)
|
|
4
|
+
[](https://pypi.org/project/hakim/)
|
|
5
|
+
|
|
6
|
+
Official Python SDK for [Hakim](https://tryhakim.ai) — Arabic-first TTS,
|
|
7
|
+
STT, voice cloning, webhooks, and batch jobs. Mirrors the surface of the
|
|
8
|
+
Node SDK (`@hakim/voice`).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install hakim
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start (async)
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import asyncio
|
|
20
|
+
from hakim import AsyncHakim
|
|
21
|
+
|
|
22
|
+
async def main() -> None:
|
|
23
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
24
|
+
audio = await hakim.audio.speech.create(
|
|
25
|
+
model="hakim-fast-v1",
|
|
26
|
+
input="مرحبا بالعالم",
|
|
27
|
+
voice="omar",
|
|
28
|
+
)
|
|
29
|
+
with open("out.mp3", "wb") as f:
|
|
30
|
+
f.write(audio)
|
|
31
|
+
|
|
32
|
+
asyncio.run(main())
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start (sync)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from hakim import Hakim
|
|
39
|
+
|
|
40
|
+
with Hakim(api_key="hk_...") as hakim:
|
|
41
|
+
audio = hakim.tts_create(
|
|
42
|
+
model="hakim-fast-v1",
|
|
43
|
+
input="مرحبا بالعالم",
|
|
44
|
+
voice="omar",
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Chat completions
|
|
49
|
+
|
|
50
|
+
OpenAI-compatible chat API backed by `hakim-chat-v1` (HKM LLM 1).
|
|
51
|
+
Drop-in for any code that targets the OpenAI Chat Completions
|
|
52
|
+
reference — swap the base URL and key and you're done.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
56
|
+
# Non-streaming.
|
|
57
|
+
completion = await hakim.chat.completions.create(
|
|
58
|
+
model="hakim-chat-v1",
|
|
59
|
+
messages=[
|
|
60
|
+
{"role": "system", "content": "أنت مساعد عربي مفيد."},
|
|
61
|
+
{"role": "user", "content": "اكتب قصيدة قصيرة عن البحر."},
|
|
62
|
+
],
|
|
63
|
+
temperature=0.7,
|
|
64
|
+
)
|
|
65
|
+
print(completion["choices"][0]["message"]["content"])
|
|
66
|
+
|
|
67
|
+
# Streaming (SSE). The final chunk carries the rolled-up `usage`.
|
|
68
|
+
stream = await hakim.chat.completions.stream(
|
|
69
|
+
model="hakim-chat-v1",
|
|
70
|
+
messages=[{"role": "user", "content": "مرحبا!"}],
|
|
71
|
+
)
|
|
72
|
+
async for chunk in stream:
|
|
73
|
+
delta = chunk["choices"][0]["delta"]
|
|
74
|
+
if "content" in delta:
|
|
75
|
+
print(delta["content"], end="", flush=True)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Reasoning / chain-of-thought
|
|
79
|
+
|
|
80
|
+
`hakim-chat-v1` is a thinking-capable model. CoT is **off by
|
|
81
|
+
default** — it adds 10–50× latency and burns completion tokens
|
|
82
|
+
that don't reach your UI in time. Opt in on **non-streaming**
|
|
83
|
+
requests only:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
completion = await hakim.chat.completions.create(
|
|
87
|
+
model="hakim-chat-v1",
|
|
88
|
+
messages=[{"role": "user", "content": "حلّ ٢٤ × ١٧ خطوة بخطوة."}],
|
|
89
|
+
reasoning={"enabled": True},
|
|
90
|
+
)
|
|
91
|
+
print(completion["choices"][0]["message"]["reasoning"]) # chain-of-thought
|
|
92
|
+
print(completion["choices"][0]["message"]["content"]) # final answer
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Streaming requests with `reasoning={"enabled": True}` are rejected
|
|
96
|
+
with a `400 invalid_request_error` — real-time agents cannot afford
|
|
97
|
+
the latency cost. The `stream()` method intentionally omits the
|
|
98
|
+
`reasoning` parameter so a misuse fails at edit time rather than
|
|
99
|
+
on the wire.
|
|
100
|
+
|
|
101
|
+
## Streaming TTS
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
105
|
+
stream = await hakim.audio.speech.stream(
|
|
106
|
+
model="hakim-fast-v1",
|
|
107
|
+
input="salam",
|
|
108
|
+
voice="omar",
|
|
109
|
+
)
|
|
110
|
+
async for chunk in stream:
|
|
111
|
+
write_to_speaker(chunk)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Speech-to-text (sync upload)
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
118
|
+
result = await hakim.audio.transcriptions.create(
|
|
119
|
+
file=open("clip.wav", "rb"),
|
|
120
|
+
model="hakim-arab-v2",
|
|
121
|
+
response_format="json",
|
|
122
|
+
)
|
|
123
|
+
print(result["text"])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Speech-to-text (realtime)
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
async with AsyncHakim(api_key="hk_...") as hakim:
|
|
130
|
+
async with hakim.audio.transcriptions.stream(
|
|
131
|
+
model="hakim-arab-v2",
|
|
132
|
+
language="ar",
|
|
133
|
+
sample_rate=16000,
|
|
134
|
+
) as handle:
|
|
135
|
+
async def pump_audio() -> None:
|
|
136
|
+
async for chunk in mic_iter():
|
|
137
|
+
await handle.send_audio(chunk)
|
|
138
|
+
await handle.commit()
|
|
139
|
+
|
|
140
|
+
asyncio.create_task(pump_audio())
|
|
141
|
+
|
|
142
|
+
async for event in handle:
|
|
143
|
+
if event["type"] == "partial":
|
|
144
|
+
print("~", event["text"])
|
|
145
|
+
elif event["type"] == "final":
|
|
146
|
+
print("✔", event["text"])
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Voice cloning
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
with open("my_voice.wav", "rb") as f:
|
|
153
|
+
voice = await hakim.voices.create(
|
|
154
|
+
sample=f,
|
|
155
|
+
name="My Voice",
|
|
156
|
+
language="ar",
|
|
157
|
+
consent_confirmed=True,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
while voice["status"] == "processing":
|
|
161
|
+
await asyncio.sleep(2.0)
|
|
162
|
+
voice = await hakim.voices.retrieve(voice["id"])
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Webhooks
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from hakim import verify_webhook_signature
|
|
169
|
+
|
|
170
|
+
def handle_webhook(request) -> None:
|
|
171
|
+
body = request.body
|
|
172
|
+
result = verify_webhook_signature(
|
|
173
|
+
secret="whsec_...",
|
|
174
|
+
body=body,
|
|
175
|
+
signature=request.headers["hakim-signature"],
|
|
176
|
+
)
|
|
177
|
+
if not result.valid:
|
|
178
|
+
raise ValueError(f"bad signature: {result.reason}")
|
|
179
|
+
event = json.loads(body)
|
|
180
|
+
...
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Errors
|
|
184
|
+
|
|
185
|
+
Every HTTP-level failure raises a :class:`HakimError` subclass you can
|
|
186
|
+
branch on:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from hakim import Hakim, RateLimitError, HakimError
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
hakim.tts_create(...)
|
|
193
|
+
except RateLimitError as err:
|
|
194
|
+
retry_after = (err.retry_after_ms or 1000) / 1000
|
|
195
|
+
time.sleep(retry_after)
|
|
196
|
+
except HakimError as err:
|
|
197
|
+
print(err.status, err.code, err.request_id)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Observability
|
|
201
|
+
|
|
202
|
+
Every request auto-attaches:
|
|
203
|
+
|
|
204
|
+
- `Authorization: Bearer <api_key>`
|
|
205
|
+
- `User-Agent: hakim-python/<version> (python/<py_version>; <platform>)`
|
|
206
|
+
- `X-Request-Id: sdk-<uuid>` (echoed back by the server — attach to your
|
|
207
|
+
logs for end-to-end tracing).
|
|
208
|
+
- `Idempotency-Key: <uuid>` on mutating JSON calls (auto-generated;
|
|
209
|
+
override via `idempotency_key=` when you need deterministic keys).
|
|
210
|
+
|
|
211
|
+
Transient failures (5xx, 429, 408, 425, network errors) are retried
|
|
212
|
+
with exponential back-off (±25% jitter) up to `max_retries` (default
|
|
213
|
+
2). `Retry-After` is honoured when the server sends it.
|
|
214
|
+
|
|
215
|
+
## Development
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
pip install -e '.[dev]'
|
|
219
|
+
ruff check src tests
|
|
220
|
+
mypy src
|
|
221
|
+
pytest
|
|
222
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hakim"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Official Python SDK for the Hakim voice AI API (TTS, STT, voice cloning, webhooks, jobs)."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "Hakim" }]
|
|
9
|
+
keywords = [
|
|
10
|
+
"hakim",
|
|
11
|
+
"tts",
|
|
12
|
+
"stt",
|
|
13
|
+
"speech",
|
|
14
|
+
"voice-cloning",
|
|
15
|
+
"arabic",
|
|
16
|
+
"ai",
|
|
17
|
+
"voice",
|
|
18
|
+
"webhooks",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 5 - Production/Stable",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
"Typing :: Typed",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"httpx>=0.27",
|
|
36
|
+
"websockets>=13",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8",
|
|
42
|
+
"pytest-asyncio>=0.24",
|
|
43
|
+
"ruff>=0.7",
|
|
44
|
+
"mypy>=1.13",
|
|
45
|
+
"build>=1.2",
|
|
46
|
+
"twine>=5",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.urls]
|
|
50
|
+
Homepage = "https://tryhakim.ai"
|
|
51
|
+
Documentation = "https://tryhakim.ai/docs"
|
|
52
|
+
Source = "https://github.com/tryHakimAI/hakim-python"
|
|
53
|
+
Issues = "https://github.com/tryHakimAI/hakim-python/issues"
|
|
54
|
+
Changelog = "https://github.com/tryHakimAI/hakim-python/blob/main/CHANGELOG.md"
|
|
55
|
+
|
|
56
|
+
[build-system]
|
|
57
|
+
requires = ["hatchling"]
|
|
58
|
+
build-backend = "hatchling.build"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["src/hakim"]
|
|
62
|
+
|
|
63
|
+
[tool.hatch.build.targets.sdist]
|
|
64
|
+
include = [
|
|
65
|
+
"src/hakim",
|
|
66
|
+
"tests",
|
|
67
|
+
"README.md",
|
|
68
|
+
"pyproject.toml",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
line-length = 100
|
|
73
|
+
target-version = "py310"
|
|
74
|
+
|
|
75
|
+
[tool.ruff.lint]
|
|
76
|
+
# Keep the default set + pull in the "bugbear" / naming / simplify rules
|
|
77
|
+
# we rely on for API hygiene.
|
|
78
|
+
select = ["E", "F", "W", "I", "B", "N", "UP", "SIM", "RUF"]
|
|
79
|
+
ignore = [
|
|
80
|
+
# Ambiguous naming (e.g. `l` vs `1`). We never clash with these.
|
|
81
|
+
"E741",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
[tool.mypy]
|
|
85
|
+
strict = true
|
|
86
|
+
python_version = "3.10"
|
|
87
|
+
|
|
88
|
+
[[tool.mypy.overrides]]
|
|
89
|
+
# `websockets` publishes stubs-in-wheel which sometimes lag the runtime.
|
|
90
|
+
# Don't fail strict mypy on the optional realtime helper's import.
|
|
91
|
+
module = ["websockets", "websockets.*"]
|
|
92
|
+
ignore_missing_imports = true
|
|
93
|
+
|
|
94
|
+
[tool.pytest.ini_options]
|
|
95
|
+
testpaths = ["tests"]
|
|
96
|
+
addopts = "-ra --strict-markers"
|
|
97
|
+
# Anchor the editable install so tests work even on Python 3.13+, which
|
|
98
|
+
# skips `.pth` files whose filenames start with `_` (the layout hatchling
|
|
99
|
+
# writes for `pip install -e .`).
|
|
100
|
+
pythonpath = ["src"]
|
|
101
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Hakim — official Python SDK for the Hakim voice AI API.
|
|
2
|
+
|
|
3
|
+
Quickstart (async):
|
|
4
|
+
|
|
5
|
+
from hakim import AsyncHakim
|
|
6
|
+
|
|
7
|
+
async with AsyncHakim(api_key=...) as hakim:
|
|
8
|
+
audio = await hakim.tts_create(
|
|
9
|
+
model="hakim-fast-v1",
|
|
10
|
+
input="مرحبا بالعالم",
|
|
11
|
+
voice="omar",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
Quickstart (sync):
|
|
15
|
+
|
|
16
|
+
from hakim import Hakim
|
|
17
|
+
|
|
18
|
+
with Hakim(api_key=...) as hakim:
|
|
19
|
+
audio = hakim.tts_create(
|
|
20
|
+
model="hakim-fast-v1", input="مرحبا بالعالم", voice="omar",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
Every namespace from the Node SDK (`audio.speech`, `audio.transcriptions`,
|
|
24
|
+
`voices`, `webhooks`, `jobs`, `usage`) is available on both clients.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from ._version import SDK_NAME, SDK_VERSION
|
|
28
|
+
from .client import AsyncHakim, Hakim
|
|
29
|
+
from .errors import (
|
|
30
|
+
APIError,
|
|
31
|
+
AuthenticationError,
|
|
32
|
+
ConnectionError,
|
|
33
|
+
HakimError,
|
|
34
|
+
IdempotencyConflictError,
|
|
35
|
+
InvalidRequestError,
|
|
36
|
+
NotFoundError,
|
|
37
|
+
PermissionError,
|
|
38
|
+
QuotaExceededError,
|
|
39
|
+
RateLimitError,
|
|
40
|
+
ServiceUnavailableError,
|
|
41
|
+
)
|
|
42
|
+
from .webhooks import VerifyResult, verify_webhook_signature
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"SDK_NAME",
|
|
46
|
+
"SDK_VERSION",
|
|
47
|
+
"APIError",
|
|
48
|
+
"AsyncHakim",
|
|
49
|
+
"AuthenticationError",
|
|
50
|
+
"ConnectionError",
|
|
51
|
+
"Hakim",
|
|
52
|
+
"HakimError",
|
|
53
|
+
"IdempotencyConflictError",
|
|
54
|
+
"InvalidRequestError",
|
|
55
|
+
"NotFoundError",
|
|
56
|
+
"PermissionError",
|
|
57
|
+
"QuotaExceededError",
|
|
58
|
+
"RateLimitError",
|
|
59
|
+
"ServiceUnavailableError",
|
|
60
|
+
"VerifyResult",
|
|
61
|
+
"verify_webhook_signature",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
__version__ = SDK_VERSION
|