avartha-python-sdk 0.0.6__py3-none-any.whl → 0.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avartha/__init__.py +7 -1
- avartha/_conversation_resources.py +555 -0
- avartha/_openai_http.py +4 -1
- avartha/_realtime_asr.py +134 -5
- avartha/_realtime_openai.py +13 -1
- avartha/_realtime_sampling.py +77 -0
- avartha/_realtime_tts.py +137 -28
- avartha/_speech_http.py +62 -6
- avartha/control.py +528 -3
- avartha/conversational_ai/__init__.py +21 -3
- avartha/conversational_ai/conversation.py +747 -0
- avartha/elevenlabs.py +32 -13
- avartha/realtime/speech.py +9 -1
- avartha/types/conversational_ai.py +77 -0
- avartha/types/realtime/realtime_response_create_params.py +8 -0
- avartha/types/realtime/realtime_response_create_params_param.py +9 -0
- avartha/types/realtime/realtime_session_create_request.py +8 -0
- avartha/types/realtime/realtime_session_create_request_param.py +9 -0
- avartha/types/voice_agents.py +213 -0
- {avartha_python_sdk-0.0.6.dist-info → avartha_python_sdk-0.0.7.dist-info}/METADATA +6 -4
- {avartha_python_sdk-0.0.6.dist-info → avartha_python_sdk-0.0.7.dist-info}/RECORD +25 -20
- {avartha_python_sdk-0.0.6.dist-info → avartha_python_sdk-0.0.7.dist-info}/WHEEL +1 -1
- {avartha_python_sdk-0.0.6.dist-info → avartha_python_sdk-0.0.7.dist-info}/licenses/LICENSE +0 -0
- {avartha_python_sdk-0.0.6.dist-info → avartha_python_sdk-0.0.7.dist-info}/licenses/THIRD_PARTY_NOTICES +0 -0
- {avartha_python_sdk-0.0.6.dist-info → avartha_python_sdk-0.0.7.dist-info}/top_level.txt +0 -0
avartha/__init__.py
CHANGED
|
@@ -30,7 +30,12 @@ from ._openai_http_errors import UnprocessableEntityError as OpenAIUnprocessable
|
|
|
30
30
|
from ._pacing import WebSocketLimits
|
|
31
31
|
from ._realtime_asr import AudioFormat, CommitStrategy, RealtimeAudioOptions, RealtimeEvents
|
|
32
32
|
from ._realtime_tts import DEFAULT_OUTPUT_FORMAT as DEFAULT_TTS_OUTPUT_FORMAT
|
|
33
|
-
from ._realtime_tts import
|
|
33
|
+
from ._realtime_tts import (
|
|
34
|
+
AsyncMultiContextSession,
|
|
35
|
+
MultiContextSession,
|
|
36
|
+
TextToSpeechOutputFormat,
|
|
37
|
+
audio_bytes,
|
|
38
|
+
)
|
|
34
39
|
from ._sentinels import NOT_GIVEN, NotGiven, Omit, not_given, omit
|
|
35
40
|
from .client import AsyncAvartha, Avartha
|
|
36
41
|
from .control import AsyncControl, Control
|
|
@@ -91,6 +96,7 @@ __all__ = [
|
|
|
91
96
|
"PlatformAPIError",
|
|
92
97
|
"RealtimeAudioOptions",
|
|
93
98
|
"RealtimeEvents",
|
|
99
|
+
"TextToSpeechOutputFormat",
|
|
94
100
|
"VoiceSettings",
|
|
95
101
|
"WebSocketLimits",
|
|
96
102
|
"audio_bytes",
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""Typed resources for Avartha's conversation routes.
|
|
2
|
+
|
|
3
|
+
Their parameters are the ones the platform accepts, which is a small subset of ElevenLabs
|
|
4
|
+
2.68.0. It refuses unknown query parameters, so the vendor's other filters would only fail.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from typing import Any, Never
|
|
10
|
+
|
|
11
|
+
from ._exceptions import UnsupportedOperationError
|
|
12
|
+
from ._speech_config import RequestOptions
|
|
13
|
+
from ._speech_http import (
|
|
14
|
+
AsyncHttpResponse,
|
|
15
|
+
AsyncSpeechHTTPClient,
|
|
16
|
+
HttpResponse,
|
|
17
|
+
SpeechHTTPClient,
|
|
18
|
+
segment,
|
|
19
|
+
)
|
|
20
|
+
from .types.conversational_ai import (
|
|
21
|
+
ConversationInitiationClientDataRequestInput,
|
|
22
|
+
ConversationSignedUrlResponseModel,
|
|
23
|
+
GetConversationResponseModel,
|
|
24
|
+
GetConversationsPageResponseModel,
|
|
25
|
+
TelephonyCallConfigInput,
|
|
26
|
+
TokenResponseModel,
|
|
27
|
+
TwilioOutboundCallResponse,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
_BASE = "v1/convai"
|
|
31
|
+
_OUTBOUND_CALL = f"{_BASE}/twilio/outbound-call"
|
|
32
|
+
_IDEMPOTENCY_HEADER = "Idempotency-Key"
|
|
33
|
+
# A 409 is left to the caller: the key was reused for a different call, or the workspace is at
|
|
34
|
+
# its call limit, and ringing someone moments later wouldn't be expected.
|
|
35
|
+
_DIAL_RETRY_STATUSES = (408, 429)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _encoded(value: Mapping[str, Any] | None) -> str | None:
|
|
39
|
+
return None if not value else json.dumps(value, separators=(",", ":"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _mint_params(
|
|
43
|
+
agent_id: str,
|
|
44
|
+
origin: str | None,
|
|
45
|
+
conversation_config_override: Mapping[str, Any] | None,
|
|
46
|
+
dynamic_variables: Mapping[str, Any] | None,
|
|
47
|
+
) -> dict[str, Any]:
|
|
48
|
+
return {
|
|
49
|
+
"agent_id": agent_id,
|
|
50
|
+
"origin": origin,
|
|
51
|
+
"conversation_config_override": _encoded(conversation_config_override),
|
|
52
|
+
"dynamic_variables": _encoded(dynamic_variables),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _dial_body(
|
|
57
|
+
agent_id: str,
|
|
58
|
+
agent_phone_number_id: str,
|
|
59
|
+
to_number: str,
|
|
60
|
+
conversation_initiation_client_data: ConversationInitiationClientDataRequestInput | None,
|
|
61
|
+
call_recording_enabled: bool | None,
|
|
62
|
+
telephony_call_config: TelephonyCallConfigInput | None,
|
|
63
|
+
) -> dict[str, Any]:
|
|
64
|
+
optional = {
|
|
65
|
+
"conversation_initiation_client_data": conversation_initiation_client_data,
|
|
66
|
+
"call_recording_enabled": call_recording_enabled,
|
|
67
|
+
"telephony_call_config": telephony_call_config,
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
"agent_id": agent_id,
|
|
71
|
+
"agent_phone_number_id": agent_phone_number_id,
|
|
72
|
+
"to_number": to_number,
|
|
73
|
+
**{name: value for name, value in optional.items() if value is not None},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _dial_options(
|
|
78
|
+
idempotency_key: str | None, request_options: RequestOptions | None
|
|
79
|
+
) -> RequestOptions:
|
|
80
|
+
"""Attach the key, and never retry without one: each keyless request rings someone."""
|
|
81
|
+
options: RequestOptions = {**(request_options or {})}
|
|
82
|
+
headers = dict(options.get("additional_headers") or {})
|
|
83
|
+
key_header = _IDEMPOTENCY_HEADER.lower()
|
|
84
|
+
if idempotency_key is not None:
|
|
85
|
+
headers = {name: value for name, value in headers.items() if name.lower() != key_header}
|
|
86
|
+
headers[_IDEMPOTENCY_HEADER] = idempotency_key
|
|
87
|
+
options["additional_headers"] = headers
|
|
88
|
+
if not any(name.lower() == key_header and value is not None for name, value in headers.items()):
|
|
89
|
+
options["max_retries"] = 0
|
|
90
|
+
return options
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _refuse_register_call() -> Never:
|
|
94
|
+
raise UnsupportedOperationError(
|
|
95
|
+
"conversational_ai.twilio.register_call is not supported. Avartha answers calls to a "
|
|
96
|
+
"connected number with the agent assigned to it, and outbound_call places calls."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class RawConversations:
|
|
101
|
+
def __init__(self, client: SpeechHTTPClient) -> None:
|
|
102
|
+
self._client = client
|
|
103
|
+
|
|
104
|
+
def get_signed_url(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
agent_id: str,
|
|
108
|
+
origin: str | None = None,
|
|
109
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
110
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
111
|
+
request_options: RequestOptions | None = None,
|
|
112
|
+
) -> HttpResponse[ConversationSignedUrlResponseModel]:
|
|
113
|
+
return self._client.get(
|
|
114
|
+
f"{_BASE}/conversation/get-signed-url",
|
|
115
|
+
ConversationSignedUrlResponseModel,
|
|
116
|
+
params=_mint_params(agent_id, origin, conversation_config_override, dynamic_variables),
|
|
117
|
+
request_options=request_options,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def get_webrtc_token(
|
|
121
|
+
self,
|
|
122
|
+
*,
|
|
123
|
+
agent_id: str,
|
|
124
|
+
origin: str | None = None,
|
|
125
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
126
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
127
|
+
request_options: RequestOptions | None = None,
|
|
128
|
+
) -> HttpResponse[TokenResponseModel]:
|
|
129
|
+
return self._client.get(
|
|
130
|
+
f"{_BASE}/conversation/token",
|
|
131
|
+
TokenResponseModel,
|
|
132
|
+
params=_mint_params(agent_id, origin, conversation_config_override, dynamic_variables),
|
|
133
|
+
request_options=request_options,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def list(
|
|
137
|
+
self, *, cursor: str | None = None, request_options: RequestOptions | None = None
|
|
138
|
+
) -> HttpResponse[GetConversationsPageResponseModel]:
|
|
139
|
+
return self._client.get(
|
|
140
|
+
f"{_BASE}/conversations",
|
|
141
|
+
GetConversationsPageResponseModel,
|
|
142
|
+
params={"cursor": cursor},
|
|
143
|
+
request_options=request_options,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def get(
|
|
147
|
+
self,
|
|
148
|
+
conversation_id: str,
|
|
149
|
+
*,
|
|
150
|
+
format: str | None = None,
|
|
151
|
+
request_options: RequestOptions | None = None,
|
|
152
|
+
) -> HttpResponse[GetConversationResponseModel]:
|
|
153
|
+
return self._client.get(
|
|
154
|
+
f"{_BASE}/conversations/{segment(conversation_id)}",
|
|
155
|
+
GetConversationResponseModel,
|
|
156
|
+
params={"format": format},
|
|
157
|
+
request_options=request_options,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def end(
|
|
161
|
+
self, conversation_id: str, *, request_options: RequestOptions | None = None
|
|
162
|
+
) -> HttpResponse[GetConversationResponseModel]:
|
|
163
|
+
return self._client.post(
|
|
164
|
+
f"{_BASE}/conversations/{segment(conversation_id)}/end",
|
|
165
|
+
GetConversationResponseModel,
|
|
166
|
+
params={},
|
|
167
|
+
request_options=request_options,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class Conversations:
|
|
172
|
+
"""Mint connection grants and read or end conversations."""
|
|
173
|
+
|
|
174
|
+
def __init__(self, client: SpeechHTTPClient) -> None:
|
|
175
|
+
self.with_raw_response = RawConversations(client)
|
|
176
|
+
|
|
177
|
+
def get_signed_url(
|
|
178
|
+
self,
|
|
179
|
+
*,
|
|
180
|
+
agent_id: str,
|
|
181
|
+
origin: str | None = None,
|
|
182
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
183
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
184
|
+
request_options: RequestOptions | None = None,
|
|
185
|
+
) -> ConversationSignedUrlResponseModel:
|
|
186
|
+
"""Mint a single-use WebSocket URL that expires in 30 seconds."""
|
|
187
|
+
return self.with_raw_response.get_signed_url(
|
|
188
|
+
agent_id=agent_id,
|
|
189
|
+
origin=origin,
|
|
190
|
+
conversation_config_override=conversation_config_override,
|
|
191
|
+
dynamic_variables=dynamic_variables,
|
|
192
|
+
request_options=request_options,
|
|
193
|
+
).data
|
|
194
|
+
|
|
195
|
+
def get_webrtc_token(
|
|
196
|
+
self,
|
|
197
|
+
*,
|
|
198
|
+
agent_id: str,
|
|
199
|
+
origin: str | None = None,
|
|
200
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
201
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
202
|
+
request_options: RequestOptions | None = None,
|
|
203
|
+
) -> TokenResponseModel:
|
|
204
|
+
"""Mint a grant for a browser's WebRTC connection."""
|
|
205
|
+
return self.with_raw_response.get_webrtc_token(
|
|
206
|
+
agent_id=agent_id,
|
|
207
|
+
origin=origin,
|
|
208
|
+
conversation_config_override=conversation_config_override,
|
|
209
|
+
dynamic_variables=dynamic_variables,
|
|
210
|
+
request_options=request_options,
|
|
211
|
+
).data
|
|
212
|
+
|
|
213
|
+
def list(
|
|
214
|
+
self, *, cursor: str | None = None, request_options: RequestOptions | None = None
|
|
215
|
+
) -> GetConversationsPageResponseModel:
|
|
216
|
+
"""Return up to 100 conversations; pass next_cursor as cursor for the next page."""
|
|
217
|
+
return self.with_raw_response.list(cursor=cursor, request_options=request_options).data
|
|
218
|
+
|
|
219
|
+
def get(
|
|
220
|
+
self,
|
|
221
|
+
conversation_id: str,
|
|
222
|
+
*,
|
|
223
|
+
format: str | None = None,
|
|
224
|
+
request_options: RequestOptions | None = None,
|
|
225
|
+
) -> GetConversationResponseModel:
|
|
226
|
+
"""Return a conversation's status, retained transcript, and billing."""
|
|
227
|
+
return self.with_raw_response.get(
|
|
228
|
+
conversation_id, format=format, request_options=request_options
|
|
229
|
+
).data
|
|
230
|
+
|
|
231
|
+
def end(
|
|
232
|
+
self, conversation_id: str, *, request_options: RequestOptions | None = None
|
|
233
|
+
) -> GetConversationResponseModel:
|
|
234
|
+
"""End a conversation; ending one that has finished is safe. Avartha extension."""
|
|
235
|
+
return self.with_raw_response.end(conversation_id, request_options=request_options).data
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class AsyncRawConversations:
|
|
239
|
+
def __init__(self, client: AsyncSpeechHTTPClient) -> None:
|
|
240
|
+
self._client = client
|
|
241
|
+
|
|
242
|
+
async def get_signed_url(
|
|
243
|
+
self,
|
|
244
|
+
*,
|
|
245
|
+
agent_id: str,
|
|
246
|
+
origin: str | None = None,
|
|
247
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
248
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
249
|
+
request_options: RequestOptions | None = None,
|
|
250
|
+
) -> AsyncHttpResponse[ConversationSignedUrlResponseModel]:
|
|
251
|
+
return await self._client.get(
|
|
252
|
+
f"{_BASE}/conversation/get-signed-url",
|
|
253
|
+
ConversationSignedUrlResponseModel,
|
|
254
|
+
params=_mint_params(agent_id, origin, conversation_config_override, dynamic_variables),
|
|
255
|
+
request_options=request_options,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
async def get_webrtc_token(
|
|
259
|
+
self,
|
|
260
|
+
*,
|
|
261
|
+
agent_id: str,
|
|
262
|
+
origin: str | None = None,
|
|
263
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
264
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
265
|
+
request_options: RequestOptions | None = None,
|
|
266
|
+
) -> AsyncHttpResponse[TokenResponseModel]:
|
|
267
|
+
return await self._client.get(
|
|
268
|
+
f"{_BASE}/conversation/token",
|
|
269
|
+
TokenResponseModel,
|
|
270
|
+
params=_mint_params(agent_id, origin, conversation_config_override, dynamic_variables),
|
|
271
|
+
request_options=request_options,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
async def list(
|
|
275
|
+
self, *, cursor: str | None = None, request_options: RequestOptions | None = None
|
|
276
|
+
) -> AsyncHttpResponse[GetConversationsPageResponseModel]:
|
|
277
|
+
return await self._client.get(
|
|
278
|
+
f"{_BASE}/conversations",
|
|
279
|
+
GetConversationsPageResponseModel,
|
|
280
|
+
params={"cursor": cursor},
|
|
281
|
+
request_options=request_options,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
async def get(
|
|
285
|
+
self,
|
|
286
|
+
conversation_id: str,
|
|
287
|
+
*,
|
|
288
|
+
format: str | None = None,
|
|
289
|
+
request_options: RequestOptions | None = None,
|
|
290
|
+
) -> AsyncHttpResponse[GetConversationResponseModel]:
|
|
291
|
+
return await self._client.get(
|
|
292
|
+
f"{_BASE}/conversations/{segment(conversation_id)}",
|
|
293
|
+
GetConversationResponseModel,
|
|
294
|
+
params={"format": format},
|
|
295
|
+
request_options=request_options,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
async def end(
|
|
299
|
+
self, conversation_id: str, *, request_options: RequestOptions | None = None
|
|
300
|
+
) -> AsyncHttpResponse[GetConversationResponseModel]:
|
|
301
|
+
return await self._client.post(
|
|
302
|
+
f"{_BASE}/conversations/{segment(conversation_id)}/end",
|
|
303
|
+
GetConversationResponseModel,
|
|
304
|
+
params={},
|
|
305
|
+
request_options=request_options,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class AsyncConversations:
|
|
310
|
+
"""Mint connection grants and read or end conversations."""
|
|
311
|
+
|
|
312
|
+
def __init__(self, client: AsyncSpeechHTTPClient) -> None:
|
|
313
|
+
self.with_raw_response = AsyncRawConversations(client)
|
|
314
|
+
|
|
315
|
+
async def get_signed_url(
|
|
316
|
+
self,
|
|
317
|
+
*,
|
|
318
|
+
agent_id: str,
|
|
319
|
+
origin: str | None = None,
|
|
320
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
321
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
322
|
+
request_options: RequestOptions | None = None,
|
|
323
|
+
) -> ConversationSignedUrlResponseModel:
|
|
324
|
+
"""Mint a single-use WebSocket URL that expires in 30 seconds."""
|
|
325
|
+
response = await self.with_raw_response.get_signed_url(
|
|
326
|
+
agent_id=agent_id,
|
|
327
|
+
origin=origin,
|
|
328
|
+
conversation_config_override=conversation_config_override,
|
|
329
|
+
dynamic_variables=dynamic_variables,
|
|
330
|
+
request_options=request_options,
|
|
331
|
+
)
|
|
332
|
+
return response.data
|
|
333
|
+
|
|
334
|
+
async def get_webrtc_token(
|
|
335
|
+
self,
|
|
336
|
+
*,
|
|
337
|
+
agent_id: str,
|
|
338
|
+
origin: str | None = None,
|
|
339
|
+
conversation_config_override: Mapping[str, Any] | None = None,
|
|
340
|
+
dynamic_variables: Mapping[str, Any] | None = None,
|
|
341
|
+
request_options: RequestOptions | None = None,
|
|
342
|
+
) -> TokenResponseModel:
|
|
343
|
+
"""Mint a grant for a browser's WebRTC connection."""
|
|
344
|
+
response = await self.with_raw_response.get_webrtc_token(
|
|
345
|
+
agent_id=agent_id,
|
|
346
|
+
origin=origin,
|
|
347
|
+
conversation_config_override=conversation_config_override,
|
|
348
|
+
dynamic_variables=dynamic_variables,
|
|
349
|
+
request_options=request_options,
|
|
350
|
+
)
|
|
351
|
+
return response.data
|
|
352
|
+
|
|
353
|
+
async def list(
|
|
354
|
+
self, *, cursor: str | None = None, request_options: RequestOptions | None = None
|
|
355
|
+
) -> GetConversationsPageResponseModel:
|
|
356
|
+
"""Return up to 100 conversations; pass next_cursor as cursor for the next page."""
|
|
357
|
+
response = await self.with_raw_response.list(cursor=cursor, request_options=request_options)
|
|
358
|
+
return response.data
|
|
359
|
+
|
|
360
|
+
async def get(
|
|
361
|
+
self,
|
|
362
|
+
conversation_id: str,
|
|
363
|
+
*,
|
|
364
|
+
format: str | None = None,
|
|
365
|
+
request_options: RequestOptions | None = None,
|
|
366
|
+
) -> GetConversationResponseModel:
|
|
367
|
+
"""Return a conversation's status, retained transcript, and billing."""
|
|
368
|
+
response = await self.with_raw_response.get(
|
|
369
|
+
conversation_id, format=format, request_options=request_options
|
|
370
|
+
)
|
|
371
|
+
return response.data
|
|
372
|
+
|
|
373
|
+
async def end(
|
|
374
|
+
self, conversation_id: str, *, request_options: RequestOptions | None = None
|
|
375
|
+
) -> GetConversationResponseModel:
|
|
376
|
+
"""End a conversation; ending one that has finished is safe. Avartha extension."""
|
|
377
|
+
response = await self.with_raw_response.end(
|
|
378
|
+
conversation_id, request_options=request_options
|
|
379
|
+
)
|
|
380
|
+
return response.data
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class RawTwilio:
|
|
384
|
+
def __init__(self, client: SpeechHTTPClient) -> None:
|
|
385
|
+
self._client = client
|
|
386
|
+
|
|
387
|
+
def outbound_call(
|
|
388
|
+
self,
|
|
389
|
+
*,
|
|
390
|
+
agent_id: str,
|
|
391
|
+
agent_phone_number_id: str,
|
|
392
|
+
to_number: str,
|
|
393
|
+
conversation_initiation_client_data: ConversationInitiationClientDataRequestInput
|
|
394
|
+
| None = None,
|
|
395
|
+
call_recording_enabled: bool | None = None,
|
|
396
|
+
telephony_call_config: TelephonyCallConfigInput | None = None,
|
|
397
|
+
idempotency_key: str | None = None,
|
|
398
|
+
request_options: RequestOptions | None = None,
|
|
399
|
+
) -> HttpResponse[TwilioOutboundCallResponse]:
|
|
400
|
+
return self._client.post(
|
|
401
|
+
_OUTBOUND_CALL,
|
|
402
|
+
TwilioOutboundCallResponse,
|
|
403
|
+
params={},
|
|
404
|
+
body=_dial_body(
|
|
405
|
+
agent_id,
|
|
406
|
+
agent_phone_number_id,
|
|
407
|
+
to_number,
|
|
408
|
+
conversation_initiation_client_data,
|
|
409
|
+
call_recording_enabled,
|
|
410
|
+
telephony_call_config,
|
|
411
|
+
),
|
|
412
|
+
retry_statuses=_DIAL_RETRY_STATUSES,
|
|
413
|
+
request_options=_dial_options(idempotency_key, request_options),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class Twilio:
|
|
418
|
+
"""Place phone calls from one of the workspace's Twilio numbers."""
|
|
419
|
+
|
|
420
|
+
def __init__(self, client: SpeechHTTPClient) -> None:
|
|
421
|
+
self.with_raw_response = RawTwilio(client)
|
|
422
|
+
|
|
423
|
+
def outbound_call(
|
|
424
|
+
self,
|
|
425
|
+
*,
|
|
426
|
+
agent_id: str,
|
|
427
|
+
agent_phone_number_id: str,
|
|
428
|
+
to_number: str,
|
|
429
|
+
conversation_initiation_client_data: ConversationInitiationClientDataRequestInput
|
|
430
|
+
| None = None,
|
|
431
|
+
call_recording_enabled: bool | None = None,
|
|
432
|
+
telephony_call_config: TelephonyCallConfigInput | None = None,
|
|
433
|
+
idempotency_key: str | None = None,
|
|
434
|
+
request_options: RequestOptions | None = None,
|
|
435
|
+
) -> TwilioOutboundCallResponse:
|
|
436
|
+
"""Ring to_number and connect the agent when someone answers.
|
|
437
|
+
|
|
438
|
+
Pass idempotency_key, an Avartha extension, so a repeated request returns the first
|
|
439
|
+
attempt instead of ringing again; only then does the SDK retry. Check success: False
|
|
440
|
+
means Twilio refused the call or didn't confirm it, and an unconfirmed call may still
|
|
441
|
+
ring.
|
|
442
|
+
"""
|
|
443
|
+
return self.with_raw_response.outbound_call(
|
|
444
|
+
agent_id=agent_id,
|
|
445
|
+
agent_phone_number_id=agent_phone_number_id,
|
|
446
|
+
to_number=to_number,
|
|
447
|
+
conversation_initiation_client_data=conversation_initiation_client_data,
|
|
448
|
+
call_recording_enabled=call_recording_enabled,
|
|
449
|
+
telephony_call_config=telephony_call_config,
|
|
450
|
+
idempotency_key=idempotency_key,
|
|
451
|
+
request_options=request_options,
|
|
452
|
+
).data
|
|
453
|
+
|
|
454
|
+
def register_call(self, **kwargs: Any) -> Never:
|
|
455
|
+
_refuse_register_call()
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class AsyncRawTwilio:
|
|
459
|
+
def __init__(self, client: AsyncSpeechHTTPClient) -> None:
|
|
460
|
+
self._client = client
|
|
461
|
+
|
|
462
|
+
async def outbound_call(
|
|
463
|
+
self,
|
|
464
|
+
*,
|
|
465
|
+
agent_id: str,
|
|
466
|
+
agent_phone_number_id: str,
|
|
467
|
+
to_number: str,
|
|
468
|
+
conversation_initiation_client_data: ConversationInitiationClientDataRequestInput
|
|
469
|
+
| None = None,
|
|
470
|
+
call_recording_enabled: bool | None = None,
|
|
471
|
+
telephony_call_config: TelephonyCallConfigInput | None = None,
|
|
472
|
+
idempotency_key: str | None = None,
|
|
473
|
+
request_options: RequestOptions | None = None,
|
|
474
|
+
) -> AsyncHttpResponse[TwilioOutboundCallResponse]:
|
|
475
|
+
return await self._client.post(
|
|
476
|
+
_OUTBOUND_CALL,
|
|
477
|
+
TwilioOutboundCallResponse,
|
|
478
|
+
params={},
|
|
479
|
+
body=_dial_body(
|
|
480
|
+
agent_id,
|
|
481
|
+
agent_phone_number_id,
|
|
482
|
+
to_number,
|
|
483
|
+
conversation_initiation_client_data,
|
|
484
|
+
call_recording_enabled,
|
|
485
|
+
telephony_call_config,
|
|
486
|
+
),
|
|
487
|
+
retry_statuses=_DIAL_RETRY_STATUSES,
|
|
488
|
+
request_options=_dial_options(idempotency_key, request_options),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class AsyncTwilio:
|
|
493
|
+
"""Place phone calls from one of the workspace's Twilio numbers."""
|
|
494
|
+
|
|
495
|
+
def __init__(self, client: AsyncSpeechHTTPClient) -> None:
|
|
496
|
+
self.with_raw_response = AsyncRawTwilio(client)
|
|
497
|
+
|
|
498
|
+
async def outbound_call(
|
|
499
|
+
self,
|
|
500
|
+
*,
|
|
501
|
+
agent_id: str,
|
|
502
|
+
agent_phone_number_id: str,
|
|
503
|
+
to_number: str,
|
|
504
|
+
conversation_initiation_client_data: ConversationInitiationClientDataRequestInput
|
|
505
|
+
| None = None,
|
|
506
|
+
call_recording_enabled: bool | None = None,
|
|
507
|
+
telephony_call_config: TelephonyCallConfigInput | None = None,
|
|
508
|
+
idempotency_key: str | None = None,
|
|
509
|
+
request_options: RequestOptions | None = None,
|
|
510
|
+
) -> TwilioOutboundCallResponse:
|
|
511
|
+
"""Ring to_number and connect the agent when someone answers.
|
|
512
|
+
|
|
513
|
+
Pass idempotency_key, an Avartha extension, so a repeated request returns the first
|
|
514
|
+
attempt instead of ringing again; only then does the SDK retry. Check success: False
|
|
515
|
+
means Twilio refused the call or didn't confirm it, and an unconfirmed call may still
|
|
516
|
+
ring.
|
|
517
|
+
"""
|
|
518
|
+
response = await self.with_raw_response.outbound_call(
|
|
519
|
+
agent_id=agent_id,
|
|
520
|
+
agent_phone_number_id=agent_phone_number_id,
|
|
521
|
+
to_number=to_number,
|
|
522
|
+
conversation_initiation_client_data=conversation_initiation_client_data,
|
|
523
|
+
call_recording_enabled=call_recording_enabled,
|
|
524
|
+
telephony_call_config=telephony_call_config,
|
|
525
|
+
idempotency_key=idempotency_key,
|
|
526
|
+
request_options=request_options,
|
|
527
|
+
)
|
|
528
|
+
return response.data
|
|
529
|
+
|
|
530
|
+
def register_call(self, **kwargs: Any) -> Never:
|
|
531
|
+
_refuse_register_call()
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class _ConversationalAi:
|
|
535
|
+
def __getattr__(self, name: str) -> Never:
|
|
536
|
+
if name.startswith("_"):
|
|
537
|
+
raise AttributeError(name)
|
|
538
|
+
raise NotImplementedError(
|
|
539
|
+
f"conversational_ai.{name} is not implemented. ElevenLabs-schema agents, tools, and "
|
|
540
|
+
"knowledge bases will be implemented in a future release; "
|
|
541
|
+
"conversational_ai.conversations and conversational_ai.twilio are available, "
|
|
542
|
+
"and Control.voice_agents manages Avartha voice agents."
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class ConversationalAi(_ConversationalAi):
|
|
547
|
+
def __init__(self, client: SpeechHTTPClient) -> None:
|
|
548
|
+
self.conversations = Conversations(client)
|
|
549
|
+
self.twilio = Twilio(client)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class AsyncConversationalAi(_ConversationalAi):
|
|
553
|
+
def __init__(self, client: AsyncSpeechHTTPClient) -> None:
|
|
554
|
+
self.conversations = AsyncConversations(client)
|
|
555
|
+
self.twilio = AsyncTwilio(client)
|
avartha/_openai_http.py
CHANGED
|
@@ -133,7 +133,10 @@ def _check_status(response: httpx.Response) -> None:
|
|
|
133
133
|
body = response.json()
|
|
134
134
|
except ValueError:
|
|
135
135
|
body = response.text
|
|
136
|
-
|
|
136
|
+
# OpenAI nests error details; the platform also returns flat envelopes whose
|
|
137
|
+
# string `error` is a message, with code/type/param alongside it.
|
|
138
|
+
nested = body.get("error") if isinstance(body, dict) else None
|
|
139
|
+
error_body = nested if isinstance(nested, dict) else body
|
|
137
140
|
cls = {
|
|
138
141
|
400: BadRequestError,
|
|
139
142
|
401: AuthenticationError,
|