sixtydb 1.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.
sixtydb-1.1.0/PKG-INFO ADDED
@@ -0,0 +1,224 @@
1
+ Metadata-Version: 2.4
2
+ Name: sixtydb
3
+ Version: 1.1.0
4
+ Summary: Official 60db SDK for Python
5
+ Author: 60db
6
+ License: MIT
7
+ Keywords: 60db,tts,stt,voice,ai
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: requests>=2.28.0
19
+ Requires-Dist: typing-extensions>=4.0.0
20
+
21
+ # 60db
22
+
23
+ Official 60db SDK for Python
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install sixtydb
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ from sixtydb import SixtyDBClient
35
+
36
+
37
+ def fetch_and_display_voices(client: SixtyDBClient) -> str:
38
+ """
39
+ Fetch all available voices and print their details.
40
+
41
+ Returns:
42
+ str: Selected voice_id
43
+ """
44
+ try:
45
+ response = client.get_voices()
46
+ except Exception as exc:
47
+ print("Error fetching voices:", exc)
48
+ raise
49
+
50
+ if not response.get("success"):
51
+ raise Exception(response.get("message"))
52
+
53
+ voices = response["data"]["built_in_voices"]
54
+
55
+ if not voices:
56
+ raise Exception("No voices available.")
57
+
58
+ print("\nAvailable Voices:\n" + "-" * 40)
59
+
60
+ for voice in voices:
61
+ print(
62
+ f"Name: {voice.get('name')} | "
63
+ f"Voice ID: {voice.get('voice_id')} | "
64
+ f"Language: {voice.get('language_name')} | "
65
+ f"Gender: {voice.get('gender')}"
66
+ )
67
+
68
+ return voices[0]["voice_id"]
69
+
70
+
71
+ def generate_speech(client: SixtyDBClient, voice_id: str) -> None:
72
+ """
73
+ Generate speech and save to file.
74
+ """
75
+ try:
76
+ audio = client.text_to_speech(
77
+ text="Hello, world!",
78
+ voice_id=voice_id,
79
+ speed=1.0
80
+ )
81
+
82
+ with open("output.mp3", "wb") as file:
83
+ file.write(audio)
84
+
85
+ print("\nAudio saved to output.mp3")
86
+
87
+ except Exception as exc:
88
+ print("Error generating speech:", exc)
89
+ raise
90
+
91
+
92
+ def main() -> None:
93
+ try:
94
+ client = SixtyDBClient("your-api-key")
95
+ except Exception as exc:
96
+ print("Error creating client:", exc)
97
+ return
98
+
99
+ try:
100
+ voice_id = fetch_and_display_voices(client)
101
+ generate_speech(client, voice_id)
102
+ except Exception as exc:
103
+ print("Operation failed:", exc)
104
+
105
+
106
+ if __name__ == "__main__":
107
+ main()
108
+ ```
109
+
110
+ ---
111
+
112
+ ## API Methods
113
+
114
+ ### Text-to-Speech
115
+ - `text_to_speech(text, voice_id=None, **params)` – Convert text to speech
116
+
117
+ ### Speech-to-Text
118
+ - `speech_to_text(audio_file, language=None)` – Transcribe audio
119
+ - `get_languages()` – Get supported languages
120
+
121
+ ### Voices
122
+ - `get_voices()` – List all voices
123
+
124
+ ### Music
125
+ Free during beta (no credit check, nothing charged). Each `create()` call
126
+ makes **1 song**, and only **one generation runs at a time per workspace**
127
+ (a second `create()` while one is running returns 429). A song takes about
128
+ 1.5-2.5 minutes.
129
+ - `music.create(prompt, lyrics_file=None, **params)` – Create a song (async)
130
+ - `music.list(**params)` – List songs (limit, offset, q, status, type, liked, sort)
131
+ - `music.get(song_id)` – Get a song (includes lyrics)
132
+ - `music.update(song_id, liked=None, played=None)` – Update flags
133
+ - `music.delete(song_id)` – Move a song to trash
134
+ - `music.download(song_id)` – Download the MP3 (bytes)
135
+ - `music.wait_for_completion(song_id, interval=4, timeout=600)` – Poll until succeeded/failed
136
+ - `music.list_voices()` – List catalog + saved voices
137
+ - `music.create_voice(name, file)` – Save a voice from an audio clip
138
+ - `music.health()` – Song service status
139
+
140
+ ### Judge
141
+
142
+ Scores content against a rubric you define, and parses conversational turns.
143
+ A classifier, not a generator — answers are probability distributions over
144
+ your own options.
145
+
146
+ ```python
147
+ run = client.judge.evaluate(
148
+ state=call_transcript,
149
+ questions={
150
+ "tone": {"type": "choice",
151
+ "criteria": {"professional": "Calm", "dismissive": "Brushes off",
152
+ "unknown": "Not clear"}}, # always add an escape hatch
153
+ "satisfaction": {"type": "score", "criteria": ["Angry", "Neutral", "Happy"]},
154
+ "resolved": {"type": "noul"},
155
+ },
156
+ )
157
+ run["answers"]["tone"]["choice"] # 'dismissive'
158
+ run["answers"]["satisfaction"]["score"] # 1.87 — weighted, not an index
159
+ run["answers"]["resolved"]["noul"] # 0.28 — probability of true, not a confidence
160
+ run["min_confidence"] # under 0.85 → send to a human
161
+
162
+ rubric = client.judge.rubrics.create(name="Call QA", questions=questions)
163
+ client.judge.evaluate(state=transcript, rubric_id=rubric["id"])
164
+
165
+ turn = client.judge.extract(
166
+ text="move my 10am appointment to friday",
167
+ schema={"intents": {"booking": "Wants to change a booking"},
168
+ "operations": {"reschedule": "Move it"},
169
+ "entities": {"date": "A calendar date"}},
170
+ )
171
+ turn["intent"]["label"] # 'booking'
172
+ turn["entities"] # [{'label': 'date', 'text': 'friday', 'start': 28, 'end': 34, ...}]
173
+
174
+ client.judge.runs.list(needs_review=True) # the low-confidence review queue
175
+ client.judge.models() # authoritative model list
176
+ client.judge.usage("current_month") # spend and run counts
177
+ ```
178
+
179
+ **Entity offsets are Unicode code points** — slice `list(text)[start:end]`,
180
+ not `text[start:end]`, or an emoji earlier in the turn shifts every span.
181
+
182
+ **Pricing:** per input token; the shared state is re-encoded once per
183
+ question. Context is 8K tokens **per question**. Failed runs are refunded.
184
+
185
+ ### Dialer
186
+ Not free: a number costs **$6.00/month**, calls cost **$0.003/min**. Most
187
+ calls need a finished trunk setup (dashboard) and approved dialer KYC
188
+ (also done in the dashboard, not exposed by this SDK). `calls.reserve()`
189
+ does **not** dial — it only pre-authorizes a call id for a SIP client
190
+ (softphone/PBX) to use.
191
+ - `dialer.status()` – Provisioning status
192
+ - `dialer.numbers.search()` / `.buy(number=None, **params)` / `.list()` / `.release(e164)` / `.set_caller_id(e164)` – `buy()` requires approved KYC done in the 60db app, otherwise 403 `DIALER_KYC_REQUIRED`/`DIALER_KYC_PENDING`/`DIALER_KYC_REJECTED`; charges $6.00 now, then $6.00/month
193
+ - `dialer.calls.list(limit=None, offset=None)` / `.get(call_id)` / `.live()` / `.line_status()` / `.reserve(to, from_number, sip_call_id, direction="outbound")` – `reserve()` does not dial
194
+ - `dialer.recordings.list(**params)` / `.playback_url(id)` / `.transcript(id)` / `.delete(id)` / `.set_enabled(enabled)`
195
+ - `dialer.billing.subscriptions()` / `.usage(limit=None)`
196
+ - `dialer.webhooks.list()` / `.create(**params)` / `.update(id, **params)` / `.delete(id)` / `.rotate_secret(id)` / `.test(id)` / `.deliveries(id)`
197
+
198
+ ---
199
+
200
+ ## Example: Speech to Text
201
+
202
+ ```python
203
+ from sixtydb import SixtyDBClient
204
+
205
+ client = SixtyDBClient("your-api-key")
206
+
207
+ try:
208
+ with open("output.mp3", "rb") as file:
209
+ result = client.speech_to_text(file, language="en")
210
+
211
+ if result.get("success"):
212
+ print("Transcription:", result["data"]["text"])
213
+ else:
214
+ print("STT Error:", result.get("message"))
215
+
216
+ except Exception as exc:
217
+ print("Error during transcription:", exc)
218
+ ```
219
+
220
+ ---
221
+
222
+ ## License
223
+
224
+ MIT
@@ -0,0 +1,204 @@
1
+ # 60db
2
+
3
+ Official 60db SDK for Python
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install sixtydb
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from sixtydb import SixtyDBClient
15
+
16
+
17
+ def fetch_and_display_voices(client: SixtyDBClient) -> str:
18
+ """
19
+ Fetch all available voices and print their details.
20
+
21
+ Returns:
22
+ str: Selected voice_id
23
+ """
24
+ try:
25
+ response = client.get_voices()
26
+ except Exception as exc:
27
+ print("Error fetching voices:", exc)
28
+ raise
29
+
30
+ if not response.get("success"):
31
+ raise Exception(response.get("message"))
32
+
33
+ voices = response["data"]["built_in_voices"]
34
+
35
+ if not voices:
36
+ raise Exception("No voices available.")
37
+
38
+ print("\nAvailable Voices:\n" + "-" * 40)
39
+
40
+ for voice in voices:
41
+ print(
42
+ f"Name: {voice.get('name')} | "
43
+ f"Voice ID: {voice.get('voice_id')} | "
44
+ f"Language: {voice.get('language_name')} | "
45
+ f"Gender: {voice.get('gender')}"
46
+ )
47
+
48
+ return voices[0]["voice_id"]
49
+
50
+
51
+ def generate_speech(client: SixtyDBClient, voice_id: str) -> None:
52
+ """
53
+ Generate speech and save to file.
54
+ """
55
+ try:
56
+ audio = client.text_to_speech(
57
+ text="Hello, world!",
58
+ voice_id=voice_id,
59
+ speed=1.0
60
+ )
61
+
62
+ with open("output.mp3", "wb") as file:
63
+ file.write(audio)
64
+
65
+ print("\nAudio saved to output.mp3")
66
+
67
+ except Exception as exc:
68
+ print("Error generating speech:", exc)
69
+ raise
70
+
71
+
72
+ def main() -> None:
73
+ try:
74
+ client = SixtyDBClient("your-api-key")
75
+ except Exception as exc:
76
+ print("Error creating client:", exc)
77
+ return
78
+
79
+ try:
80
+ voice_id = fetch_and_display_voices(client)
81
+ generate_speech(client, voice_id)
82
+ except Exception as exc:
83
+ print("Operation failed:", exc)
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()
88
+ ```
89
+
90
+ ---
91
+
92
+ ## API Methods
93
+
94
+ ### Text-to-Speech
95
+ - `text_to_speech(text, voice_id=None, **params)` – Convert text to speech
96
+
97
+ ### Speech-to-Text
98
+ - `speech_to_text(audio_file, language=None)` – Transcribe audio
99
+ - `get_languages()` – Get supported languages
100
+
101
+ ### Voices
102
+ - `get_voices()` – List all voices
103
+
104
+ ### Music
105
+ Free during beta (no credit check, nothing charged). Each `create()` call
106
+ makes **1 song**, and only **one generation runs at a time per workspace**
107
+ (a second `create()` while one is running returns 429). A song takes about
108
+ 1.5-2.5 minutes.
109
+ - `music.create(prompt, lyrics_file=None, **params)` – Create a song (async)
110
+ - `music.list(**params)` – List songs (limit, offset, q, status, type, liked, sort)
111
+ - `music.get(song_id)` – Get a song (includes lyrics)
112
+ - `music.update(song_id, liked=None, played=None)` – Update flags
113
+ - `music.delete(song_id)` – Move a song to trash
114
+ - `music.download(song_id)` – Download the MP3 (bytes)
115
+ - `music.wait_for_completion(song_id, interval=4, timeout=600)` – Poll until succeeded/failed
116
+ - `music.list_voices()` – List catalog + saved voices
117
+ - `music.create_voice(name, file)` – Save a voice from an audio clip
118
+ - `music.health()` – Song service status
119
+
120
+ ### Judge
121
+
122
+ Scores content against a rubric you define, and parses conversational turns.
123
+ A classifier, not a generator — answers are probability distributions over
124
+ your own options.
125
+
126
+ ```python
127
+ run = client.judge.evaluate(
128
+ state=call_transcript,
129
+ questions={
130
+ "tone": {"type": "choice",
131
+ "criteria": {"professional": "Calm", "dismissive": "Brushes off",
132
+ "unknown": "Not clear"}}, # always add an escape hatch
133
+ "satisfaction": {"type": "score", "criteria": ["Angry", "Neutral", "Happy"]},
134
+ "resolved": {"type": "noul"},
135
+ },
136
+ )
137
+ run["answers"]["tone"]["choice"] # 'dismissive'
138
+ run["answers"]["satisfaction"]["score"] # 1.87 — weighted, not an index
139
+ run["answers"]["resolved"]["noul"] # 0.28 — probability of true, not a confidence
140
+ run["min_confidence"] # under 0.85 → send to a human
141
+
142
+ rubric = client.judge.rubrics.create(name="Call QA", questions=questions)
143
+ client.judge.evaluate(state=transcript, rubric_id=rubric["id"])
144
+
145
+ turn = client.judge.extract(
146
+ text="move my 10am appointment to friday",
147
+ schema={"intents": {"booking": "Wants to change a booking"},
148
+ "operations": {"reschedule": "Move it"},
149
+ "entities": {"date": "A calendar date"}},
150
+ )
151
+ turn["intent"]["label"] # 'booking'
152
+ turn["entities"] # [{'label': 'date', 'text': 'friday', 'start': 28, 'end': 34, ...}]
153
+
154
+ client.judge.runs.list(needs_review=True) # the low-confidence review queue
155
+ client.judge.models() # authoritative model list
156
+ client.judge.usage("current_month") # spend and run counts
157
+ ```
158
+
159
+ **Entity offsets are Unicode code points** — slice `list(text)[start:end]`,
160
+ not `text[start:end]`, or an emoji earlier in the turn shifts every span.
161
+
162
+ **Pricing:** per input token; the shared state is re-encoded once per
163
+ question. Context is 8K tokens **per question**. Failed runs are refunded.
164
+
165
+ ### Dialer
166
+ Not free: a number costs **$6.00/month**, calls cost **$0.003/min**. Most
167
+ calls need a finished trunk setup (dashboard) and approved dialer KYC
168
+ (also done in the dashboard, not exposed by this SDK). `calls.reserve()`
169
+ does **not** dial — it only pre-authorizes a call id for a SIP client
170
+ (softphone/PBX) to use.
171
+ - `dialer.status()` – Provisioning status
172
+ - `dialer.numbers.search()` / `.buy(number=None, **params)` / `.list()` / `.release(e164)` / `.set_caller_id(e164)` – `buy()` requires approved KYC done in the 60db app, otherwise 403 `DIALER_KYC_REQUIRED`/`DIALER_KYC_PENDING`/`DIALER_KYC_REJECTED`; charges $6.00 now, then $6.00/month
173
+ - `dialer.calls.list(limit=None, offset=None)` / `.get(call_id)` / `.live()` / `.line_status()` / `.reserve(to, from_number, sip_call_id, direction="outbound")` – `reserve()` does not dial
174
+ - `dialer.recordings.list(**params)` / `.playback_url(id)` / `.transcript(id)` / `.delete(id)` / `.set_enabled(enabled)`
175
+ - `dialer.billing.subscriptions()` / `.usage(limit=None)`
176
+ - `dialer.webhooks.list()` / `.create(**params)` / `.update(id, **params)` / `.delete(id)` / `.rotate_secret(id)` / `.test(id)` / `.deliveries(id)`
177
+
178
+ ---
179
+
180
+ ## Example: Speech to Text
181
+
182
+ ```python
183
+ from sixtydb import SixtyDBClient
184
+
185
+ client = SixtyDBClient("your-api-key")
186
+
187
+ try:
188
+ with open("output.mp3", "rb") as file:
189
+ result = client.speech_to_text(file, language="en")
190
+
191
+ if result.get("success"):
192
+ print("Transcription:", result["data"]["text"])
193
+ else:
194
+ print("STT Error:", result.get("message"))
195
+
196
+ except Exception as exc:
197
+ print("Error during transcription:", exc)
198
+ ```
199
+
200
+ ---
201
+
202
+ ## License
203
+
204
+ MIT
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sixtydb"
7
+ version = "1.1.0"
8
+ description = "Official 60db SDK for Python"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "MIT"}
12
+ keywords = ["60db", "tts", "stt", "voice", "ai"]
13
+ authors = [
14
+ {name = "60db"}
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ ]
26
+ dependencies = [
27
+ "requests>=2.28.0",
28
+ "typing-extensions>=4.0.0"
29
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,31 @@
1
+ """
2
+ 60db Python SDK
3
+ Official Python client for the 60db API
4
+ """
5
+
6
+ from .client import SixtyDBClient
7
+ from .music import MusicResource
8
+ from .judge import JudgeResource, JudgeRubricsResource, JudgeRunsResource
9
+ from .dialer import (
10
+ DialerResource,
11
+ DialerNumbersResource,
12
+ DialerCallsResource,
13
+ DialerRecordingsResource,
14
+ DialerBillingResource,
15
+ DialerWebhooksResource,
16
+ )
17
+
18
+ __version__ = "1.1.0"
19
+ __all__ = [
20
+ "SixtyDBClient",
21
+ "MusicResource",
22
+ "JudgeResource",
23
+ "JudgeRubricsResource",
24
+ "JudgeRunsResource",
25
+ "DialerResource",
26
+ "DialerNumbersResource",
27
+ "DialerCallsResource",
28
+ "DialerRecordingsResource",
29
+ "DialerBillingResource",
30
+ "DialerWebhooksResource",
31
+ ]