avartha-python-sdk 0.0.7__py3-none-any.whl → 0.0.8__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/_conversation_resources.py +7 -207
- avartha/_realtime_tts.py +97 -39
- avartha/_speech_http.py +9 -22
- avartha/types/conversational_ai.py +1 -26
- avartha/types/voice_agents.py +0 -6
- {avartha_python_sdk-0.0.7.dist-info → avartha_python_sdk-0.0.8.dist-info}/METADATA +13 -10
- {avartha_python_sdk-0.0.7.dist-info → avartha_python_sdk-0.0.8.dist-info}/RECORD +11 -11
- {avartha_python_sdk-0.0.7.dist-info → avartha_python_sdk-0.0.8.dist-info}/WHEEL +0 -0
- {avartha_python_sdk-0.0.7.dist-info → avartha_python_sdk-0.0.8.dist-info}/licenses/LICENSE +0 -0
- {avartha_python_sdk-0.0.7.dist-info → avartha_python_sdk-0.0.8.dist-info}/licenses/THIRD_PARTY_NOTICES +0 -0
- {avartha_python_sdk-0.0.7.dist-info → avartha_python_sdk-0.0.8.dist-info}/top_level.txt +0 -0
|
@@ -8,7 +8,6 @@ import json
|
|
|
8
8
|
from collections.abc import Mapping
|
|
9
9
|
from typing import Any, Never
|
|
10
10
|
|
|
11
|
-
from ._exceptions import UnsupportedOperationError
|
|
12
11
|
from ._speech_config import RequestOptions
|
|
13
12
|
from ._speech_http import (
|
|
14
13
|
AsyncHttpResponse,
|
|
@@ -18,21 +17,13 @@ from ._speech_http import (
|
|
|
18
17
|
segment,
|
|
19
18
|
)
|
|
20
19
|
from .types.conversational_ai import (
|
|
21
|
-
ConversationInitiationClientDataRequestInput,
|
|
22
20
|
ConversationSignedUrlResponseModel,
|
|
23
21
|
GetConversationResponseModel,
|
|
24
22
|
GetConversationsPageResponseModel,
|
|
25
|
-
TelephonyCallConfigInput,
|
|
26
23
|
TokenResponseModel,
|
|
27
|
-
TwilioOutboundCallResponse,
|
|
28
24
|
)
|
|
29
25
|
|
|
30
26
|
_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
27
|
|
|
37
28
|
|
|
38
29
|
def _encoded(value: Mapping[str, Any] | None) -> str | None:
|
|
@@ -53,50 +44,6 @@ def _mint_params(
|
|
|
53
44
|
}
|
|
54
45
|
|
|
55
46
|
|
|
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
47
|
class RawConversations:
|
|
101
48
|
def __init__(self, client: SpeechHTTPClient) -> None:
|
|
102
49
|
self._client = client
|
|
@@ -380,165 +327,20 @@ class AsyncConversations:
|
|
|
380
327
|
return response.data
|
|
381
328
|
|
|
382
329
|
|
|
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
330
|
class _ConversationalAi:
|
|
535
331
|
def __getattr__(self, name: str) -> Never:
|
|
536
332
|
if name.startswith("_"):
|
|
537
333
|
raise AttributeError(name)
|
|
334
|
+
if name == "twilio":
|
|
335
|
+
# Phone calls need workspace features that aren't available to every workspace.
|
|
336
|
+
raise NotImplementedError(
|
|
337
|
+
"conversational_ai.twilio is not available in this SDK. "
|
|
338
|
+
"conversational_ai.conversations is available."
|
|
339
|
+
)
|
|
538
340
|
raise NotImplementedError(
|
|
539
341
|
f"conversational_ai.{name} is not implemented. ElevenLabs-schema agents, tools, and "
|
|
540
342
|
"knowledge bases will be implemented in a future release; "
|
|
541
|
-
"conversational_ai.conversations
|
|
343
|
+
"conversational_ai.conversations is available, "
|
|
542
344
|
"and Control.voice_agents manages Avartha voice agents."
|
|
543
345
|
)
|
|
544
346
|
|
|
@@ -546,10 +348,8 @@ class _ConversationalAi:
|
|
|
546
348
|
class ConversationalAi(_ConversationalAi):
|
|
547
349
|
def __init__(self, client: SpeechHTTPClient) -> None:
|
|
548
350
|
self.conversations = Conversations(client)
|
|
549
|
-
self.twilio = Twilio(client)
|
|
550
351
|
|
|
551
352
|
|
|
552
353
|
class AsyncConversationalAi(_ConversationalAi):
|
|
553
354
|
def __init__(self, client: AsyncSpeechHTTPClient) -> None:
|
|
554
355
|
self.conversations = AsyncConversations(client)
|
|
555
|
-
self.twilio = AsyncTwilio(client)
|
avartha/_realtime_tts.py
CHANGED
|
@@ -8,6 +8,8 @@ text fragments unchanged, preserve errors, and use owned multi-context message t
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import base64
|
|
10
10
|
import json
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
11
13
|
from collections.abc import (
|
|
12
14
|
AsyncGenerator,
|
|
13
15
|
AsyncIterable,
|
|
@@ -53,6 +55,8 @@ from .types.speech import (
|
|
|
53
55
|
VoiceSettings,
|
|
54
56
|
)
|
|
55
57
|
|
|
58
|
+
log = logging.getLogger(__name__)
|
|
59
|
+
|
|
56
60
|
ReceiveMessageMulti = AudioOutputMulti | FinalOutputMulti
|
|
57
61
|
SendMessageMulti = (
|
|
58
62
|
InitializeConnectionMulti | SendTextMulti | FlushContext | CloseContext | CloseSocket
|
|
@@ -106,38 +110,57 @@ def _query(
|
|
|
106
110
|
*,
|
|
107
111
|
request_options: RequestOptions | None = None,
|
|
108
112
|
) -> dict[str, Any]:
|
|
109
|
-
"""The connection query; refuses option values the runtime always rejects.
|
|
110
|
-
|
|
111
|
-
query
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if inactivity_timeout is not None:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
or inactivity_timeout not in _INACTIVITY_TIMEOUT_SECONDS
|
|
121
|
-
):
|
|
122
|
-
raise ValueError("inactivity_timeout must be a whole number of seconds from 1 to 180")
|
|
123
|
-
query["inactivity_timeout"] = inactivity_timeout
|
|
124
|
-
if sync_alignment is not None:
|
|
125
|
-
if not isinstance(sync_alignment, bool):
|
|
126
|
-
raise ValueError("sync_alignment must be True or False")
|
|
127
|
-
if sync_alignment and output_format.startswith("mp3_"):
|
|
128
|
-
raise ValueError(
|
|
129
|
-
"sync_alignment=True requires PCM or G.711 output; "
|
|
130
|
-
"MP3 packets do not share the alignment clock"
|
|
131
|
-
)
|
|
132
|
-
query["sync_alignment"] = str(sync_alignment).lower()
|
|
133
|
-
# Match HTTP request_options precedence: explicit extra query values override
|
|
134
|
-
# defaults and named arguments, while None omits a parameter.
|
|
113
|
+
"""The connection query; refuses option values the runtime always rejects.
|
|
114
|
+
|
|
115
|
+
Extra query values in `request_options` override defaults and named arguments,
|
|
116
|
+
as for HTTP requests, and None omits a parameter. Named arguments are checked
|
|
117
|
+
as given, then the merged values are checked again, so an override cannot send
|
|
118
|
+
a value the runtime refuses.
|
|
119
|
+
"""
|
|
120
|
+
if inactivity_timeout is not None and not _valid_inactivity_timeout(inactivity_timeout):
|
|
121
|
+
raise ValueError(_INACTIVITY_TIMEOUT_ERROR)
|
|
122
|
+
if sync_alignment is not None and not isinstance(sync_alignment, bool):
|
|
123
|
+
raise ValueError("sync_alignment must be True or False")
|
|
135
124
|
extra = request_options.get("additional_query_parameters", {}) if request_options else {}
|
|
136
|
-
|
|
137
|
-
|
|
125
|
+
merged = remove_none_from_dict(
|
|
126
|
+
{
|
|
127
|
+
"model_id": model_id,
|
|
128
|
+
"output_format": output_format or DEFAULT_OUTPUT_FORMAT,
|
|
129
|
+
"auto_mode": "true",
|
|
130
|
+
"inactivity_timeout": inactivity_timeout,
|
|
131
|
+
"sync_alignment": sync_alignment,
|
|
132
|
+
**extra,
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
query = {
|
|
138
136
|
key: str(value).lower() if isinstance(value, bool) else value
|
|
139
|
-
for key, value in
|
|
137
|
+
for key, value in merged.items()
|
|
140
138
|
}
|
|
139
|
+
if "inactivity_timeout" in query and not _valid_inactivity_timeout(query["inactivity_timeout"]):
|
|
140
|
+
raise ValueError(_INACTIVITY_TIMEOUT_ERROR)
|
|
141
|
+
alignment = query.get("sync_alignment")
|
|
142
|
+
if alignment is not None and alignment not in ("true", "false"):
|
|
143
|
+
raise ValueError("sync_alignment must be True or False")
|
|
144
|
+
if alignment == "true" and str(query.get("output_format", "")).startswith("mp3_"):
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"sync_alignment=True requires PCM or G.711 output; "
|
|
147
|
+
"MP3 packets do not share the alignment clock"
|
|
148
|
+
)
|
|
149
|
+
return query
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
_INACTIVITY_TIMEOUT_ERROR = "inactivity_timeout must be a whole number of seconds from 1 to 180"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _valid_inactivity_timeout(value: Any) -> bool:
|
|
156
|
+
"""An int, or the runtime's digits-only query spelling, from 1 to 180."""
|
|
157
|
+
if isinstance(value, str) and value.isascii() and value.isdigit():
|
|
158
|
+
value = int(value)
|
|
159
|
+
return (
|
|
160
|
+
isinstance(value, int)
|
|
161
|
+
and not isinstance(value, bool)
|
|
162
|
+
and value in _INACTIVITY_TIMEOUT_SECONDS
|
|
163
|
+
)
|
|
141
164
|
|
|
142
165
|
|
|
143
166
|
def _headers(base: Mapping[str, str], request_options: RequestOptions | None) -> dict[str, str]:
|
|
@@ -158,6 +181,25 @@ def _audio(payload: Any) -> bytes | None:
|
|
|
158
181
|
return base64.b64decode(encoded) if encoded else None
|
|
159
182
|
|
|
160
183
|
|
|
184
|
+
def _warning(payload: Any) -> bool:
|
|
185
|
+
"""Log a runtime warning; True when the frame carries nothing else to deliver.
|
|
186
|
+
|
|
187
|
+
The runtime sends one, for example, when voice_settings it accepts for
|
|
188
|
+
compatibility have no effect. A warning never fails the utterance.
|
|
189
|
+
"""
|
|
190
|
+
if not isinstance(payload, Mapping) or not isinstance(payload.get("warning"), str):
|
|
191
|
+
return False
|
|
192
|
+
context_id = payload.get("contextId") or payload.get("context_id")
|
|
193
|
+
where = f" for context {context_id!r}" if isinstance(context_id, str) else ""
|
|
194
|
+
log.warning("Text-to-speech warning%s: %s", where, payload["warning"])
|
|
195
|
+
return not (
|
|
196
|
+
payload.get("audio")
|
|
197
|
+
or payload.get("isFinal")
|
|
198
|
+
or payload.get("is_final")
|
|
199
|
+
or payload.get("error") is not None
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
161
203
|
def _raise_stream_error(payload: Any) -> None:
|
|
162
204
|
"""A failed utterance is terminal even if the socket remains open."""
|
|
163
205
|
if isinstance(payload, Mapping) and payload.get("error") is not None:
|
|
@@ -275,8 +317,10 @@ class AvarthaRealtimeTextToSpeechClient:
|
|
|
275
317
|
|
|
276
318
|
`output_format` defaults to `DEFAULT_OUTPUT_FORMAT`; see
|
|
277
319
|
`TextToSpeechOutputFormat` for PCM, MP3 and G.711 delivery formats.
|
|
278
|
-
`voice_settings` is forwarded when supplied
|
|
279
|
-
|
|
320
|
+
`voice_settings` is forwarded when supplied. Valid `stability`,
|
|
321
|
+
`similarity_boost`, `style` and `use_speaker_boost` values are accepted for
|
|
322
|
+
compatibility but do not change synthesis; the runtime's warning is logged.
|
|
323
|
+
`speed` must be omitted or 1.0.
|
|
280
324
|
|
|
281
325
|
`inactivity_timeout` is an Avartha extension: the seconds (1 to 180) the
|
|
282
326
|
runtime waits for more text before ending the utterance; omission uses
|
|
@@ -338,6 +382,7 @@ class AvarthaRealtimeTextToSpeechClient:
|
|
|
338
382
|
while True:
|
|
339
383
|
received = json.loads(socket.recv())
|
|
340
384
|
_raise_stream_error(received)
|
|
385
|
+
_warning(received)
|
|
341
386
|
if audio := _audio(received):
|
|
342
387
|
yield audio
|
|
343
388
|
except websockets.exceptions.ConnectionClosed as closed:
|
|
@@ -481,6 +526,7 @@ class AvarthaAsyncRealtimeTextToSpeechClient:
|
|
|
481
526
|
raise error
|
|
482
527
|
received = json.loads(await receiver)
|
|
483
528
|
_raise_stream_error(received)
|
|
529
|
+
_warning(received)
|
|
484
530
|
if audio := _audio(received):
|
|
485
531
|
yield audio
|
|
486
532
|
except websockets.exceptions.ConnectionClosed as closed:
|
|
@@ -633,7 +679,10 @@ class _MultiContextProtocol:
|
|
|
633
679
|
def close_socket(self) -> dict[str, Any]:
|
|
634
680
|
return _frame(CloseSocket(close_socket=True))
|
|
635
681
|
|
|
636
|
-
def receive(self, payload: Mapping[str, Any]) -> ReceiveMessageMulti:
|
|
682
|
+
def receive(self, payload: Mapping[str, Any]) -> ReceiveMessageMulti | None:
|
|
683
|
+
"""The typed message, or None for a warning that carries nothing else."""
|
|
684
|
+
if _warning(payload):
|
|
685
|
+
return None
|
|
637
686
|
context_id = payload.get("contextId") or payload.get("context_id")
|
|
638
687
|
context = self._contexts.get(context_id) if isinstance(context_id, str) else None
|
|
639
688
|
if payload.get("error") is not None or ("code" in payload and "message" in payload):
|
|
@@ -719,9 +768,15 @@ class MultiContextSession:
|
|
|
719
768
|
"""Return the next message, or None after a complete, cleanly closed stream.
|
|
720
769
|
|
|
721
770
|
Raises `ApiError` for gateway errors or closure before all finals arrive.
|
|
771
|
+
Runtime warnings are logged and skipped; `timeout` covers the whole wait.
|
|
722
772
|
"""
|
|
773
|
+
deadline = None if timeout is None else time.monotonic() + timeout
|
|
723
774
|
try:
|
|
724
|
-
|
|
775
|
+
while True:
|
|
776
|
+
remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
|
|
777
|
+
message = self._protocol.receive(json.loads(self._socket.recv(remaining)))
|
|
778
|
+
if message is not None:
|
|
779
|
+
return message
|
|
725
780
|
except websockets.exceptions.ConnectionClosed as closed:
|
|
726
781
|
if closed.code == 1000 and not self._protocol.draining:
|
|
727
782
|
return None
|
|
@@ -897,14 +952,17 @@ class AsyncMultiContextSession:
|
|
|
897
952
|
raise
|
|
898
953
|
|
|
899
954
|
async def receive(self, timeout: float | None = None) -> ReceiveMessageMulti | None:
|
|
900
|
-
"""Return the next message, or None after a complete, cleanly closed stream.
|
|
955
|
+
"""Return the next message, or None after a complete, cleanly closed stream.
|
|
956
|
+
|
|
957
|
+
Runtime warnings are logged and skipped; `timeout` covers the whole wait.
|
|
958
|
+
"""
|
|
901
959
|
self._check_send_failure()
|
|
902
960
|
try:
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
961
|
+
async with asyncio.timeout(timeout):
|
|
962
|
+
while True:
|
|
963
|
+
message = self._protocol.receive(json.loads(await self._socket.recv()))
|
|
964
|
+
if message is not None:
|
|
965
|
+
return message
|
|
908
966
|
except websockets.exceptions.ConnectionClosed as closed:
|
|
909
967
|
if closed.code == 1000 and not self._protocol.draining:
|
|
910
968
|
return None
|
avartha/_speech_http.py
CHANGED
|
@@ -64,9 +64,6 @@ class AsyncHttpResponse[T]:
|
|
|
64
64
|
return AsyncHttpResponse(self._response, transform(self.data))
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
_RETRY_STATUSES = (408, 409, 429)
|
|
68
|
-
|
|
69
|
-
|
|
70
67
|
def segment(value: str) -> str:
|
|
71
68
|
if value in {".", ".."}:
|
|
72
69
|
raise ValueError("Resource identifiers cannot be path traversal segments")
|
|
@@ -157,19 +154,17 @@ class _Settings:
|
|
|
157
154
|
path: str,
|
|
158
155
|
params: Mapping[str, Any],
|
|
159
156
|
options: RequestOptions,
|
|
160
|
-
body: Mapping[str, Any] | None = None,
|
|
161
157
|
) -> httpx.Request:
|
|
162
158
|
url = not_vendor_host(f"{self.base_url.rstrip('/')}/{path}", field="base_url")
|
|
163
159
|
query = {**params, **(options.get("additional_query_parameters") or {})}
|
|
164
160
|
headers = {**self.headers, **(options.get("additional_headers") or {})}
|
|
165
|
-
content = {**(body or {}), **(options.get("additional_body_parameters") or {})}
|
|
166
161
|
timeout = options.get("timeout_in_seconds")
|
|
167
162
|
return client.build_request(
|
|
168
163
|
method,
|
|
169
164
|
url,
|
|
170
165
|
params=_query(jsonable_encoder(query)) or None,
|
|
171
166
|
headers={key: value for key, value in headers.items() if value is not None},
|
|
172
|
-
json=jsonable_encoder(
|
|
167
|
+
json=jsonable_encoder(options.get("additional_body_parameters")) or None,
|
|
173
168
|
timeout=self.timeout if timeout is None else timeout,
|
|
174
169
|
)
|
|
175
170
|
|
|
@@ -207,12 +202,10 @@ class SpeechHTTPClient:
|
|
|
207
202
|
schema: type[T],
|
|
208
203
|
*,
|
|
209
204
|
params: Mapping[str, Any],
|
|
210
|
-
body: Mapping[str, Any] | None = None,
|
|
211
|
-
retry_statuses: tuple[int, ...] = _RETRY_STATUSES,
|
|
212
205
|
request_options: RequestOptions | None = None,
|
|
213
206
|
) -> HttpResponse[T]:
|
|
214
|
-
"""Send a POST; retried like GET
|
|
215
|
-
return self._send("POST", path, schema, params, request_options
|
|
207
|
+
"""Send a body-less POST; retried like GET, so use it only for idempotent routes."""
|
|
208
|
+
return self._send("POST", path, schema, params, request_options)
|
|
216
209
|
|
|
217
210
|
def _send[T](
|
|
218
211
|
self,
|
|
@@ -221,8 +214,6 @@ class SpeechHTTPClient:
|
|
|
221
214
|
schema: type[T],
|
|
222
215
|
params: Mapping[str, Any],
|
|
223
216
|
request_options: RequestOptions | None,
|
|
224
|
-
body: Mapping[str, Any] | None = None,
|
|
225
|
-
retry_statuses: tuple[int, ...] = _RETRY_STATUSES,
|
|
226
217
|
) -> HttpResponse[T]:
|
|
227
218
|
if self._closed:
|
|
228
219
|
raise RuntimeError("Cannot send a request, as the client has been closed.")
|
|
@@ -230,7 +221,7 @@ class SpeechHTTPClient:
|
|
|
230
221
|
self._client = httpx.Client()
|
|
231
222
|
options = request_options or {}
|
|
232
223
|
for attempt in range(max(0, options.get("max_retries", 2)) + 1):
|
|
233
|
-
request = self._settings.request(self._client, method, path, params, options
|
|
224
|
+
request = self._settings.request(self._client, method, path, params, options)
|
|
234
225
|
with wrap_httpx_errors():
|
|
235
226
|
for redirects in range(self._client.max_redirects + 1):
|
|
236
227
|
response = self._client.send(request, follow_redirects=False)
|
|
@@ -246,7 +237,7 @@ class SpeechHTTPClient:
|
|
|
246
237
|
finally:
|
|
247
238
|
response.close()
|
|
248
239
|
if (
|
|
249
|
-
response.status_code >= 500 or response.status_code in
|
|
240
|
+
response.status_code >= 500 or response.status_code in (408, 409, 429)
|
|
250
241
|
) and attempt < options.get("max_retries", 2):
|
|
251
242
|
delay = _retry_delay(response, attempt)
|
|
252
243
|
response.close()
|
|
@@ -298,12 +289,10 @@ class AsyncSpeechHTTPClient:
|
|
|
298
289
|
schema: type[T],
|
|
299
290
|
*,
|
|
300
291
|
params: Mapping[str, Any],
|
|
301
|
-
body: Mapping[str, Any] | None = None,
|
|
302
|
-
retry_statuses: tuple[int, ...] = _RETRY_STATUSES,
|
|
303
292
|
request_options: RequestOptions | None = None,
|
|
304
293
|
) -> AsyncHttpResponse[T]:
|
|
305
|
-
"""Send a POST; retried like GET
|
|
306
|
-
return await self._send("POST", path, schema, params, request_options
|
|
294
|
+
"""Send a body-less POST; retried like GET, so use it only for idempotent routes."""
|
|
295
|
+
return await self._send("POST", path, schema, params, request_options)
|
|
307
296
|
|
|
308
297
|
async def _send[T](
|
|
309
298
|
self,
|
|
@@ -312,8 +301,6 @@ class AsyncSpeechHTTPClient:
|
|
|
312
301
|
schema: type[T],
|
|
313
302
|
params: Mapping[str, Any],
|
|
314
303
|
request_options: RequestOptions | None,
|
|
315
|
-
body: Mapping[str, Any] | None = None,
|
|
316
|
-
retry_statuses: tuple[int, ...] = _RETRY_STATUSES,
|
|
317
304
|
) -> AsyncHttpResponse[T]:
|
|
318
305
|
if self._closed:
|
|
319
306
|
raise RuntimeError("Cannot send a request, as the client has been closed.")
|
|
@@ -321,7 +308,7 @@ class AsyncSpeechHTTPClient:
|
|
|
321
308
|
self._client = httpx.AsyncClient()
|
|
322
309
|
options = request_options or {}
|
|
323
310
|
for attempt in range(max(0, options.get("max_retries", 2)) + 1):
|
|
324
|
-
request = self._settings.request(self._client, method, path, params, options
|
|
311
|
+
request = self._settings.request(self._client, method, path, params, options)
|
|
325
312
|
with wrap_httpx_errors():
|
|
326
313
|
for redirects in range(self._client.max_redirects + 1):
|
|
327
314
|
response = await self._client.send(request, follow_redirects=False)
|
|
@@ -337,7 +324,7 @@ class AsyncSpeechHTTPClient:
|
|
|
337
324
|
finally:
|
|
338
325
|
await response.aclose()
|
|
339
326
|
if (
|
|
340
|
-
response.status_code >= 500 or response.status_code in
|
|
327
|
+
response.status_code >= 500 or response.status_code in (408, 409, 429)
|
|
341
328
|
) and attempt < options.get("max_retries", 2):
|
|
342
329
|
delay = _retry_delay(response, attempt)
|
|
343
330
|
await response.aclose()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
"""Avartha's conversation
|
|
1
|
+
"""Responses from Avartha's conversation routes; unlisted server fields stay available."""
|
|
2
2
|
|
|
3
3
|
import typing
|
|
4
|
-
from collections.abc import Mapping
|
|
5
4
|
|
|
6
5
|
import pydantic
|
|
7
6
|
|
|
@@ -51,27 +50,3 @@ class GetConversationResponseModel(ConversationSummaryResponseModel):
|
|
|
51
50
|
has_audio: bool | None = pydantic.Field(default=None)
|
|
52
51
|
has_user_audio: bool | None = pydantic.Field(default=None)
|
|
53
52
|
has_response_audio: bool | None = pydantic.Field(default=None)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class ConversationInitiationClientDataRequestInput(typing.TypedDict, total=False):
|
|
57
|
-
"""What a placed call accepts of the ElevenLabs model of this name."""
|
|
58
|
-
|
|
59
|
-
dynamic_variables: Mapping[str, typing.Any] | None
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class TelephonyCallConfigInput(typing.TypedDict, total=False):
|
|
63
|
-
"""What a placed call accepts of the ElevenLabs model of this name.
|
|
64
|
-
|
|
65
|
-
The platform refuses `True` for recording and any object for machine detection.
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
ringing_timeout_secs: int | None
|
|
69
|
-
twilio_call_recording_enabled: bool | None
|
|
70
|
-
twilio_machine_detection: Mapping[str, typing.Any] | None
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class TwilioOutboundCallResponse(_Response):
|
|
74
|
-
success: bool = pydantic.Field()
|
|
75
|
-
message: str = pydantic.Field()
|
|
76
|
-
conversation_id: str | None = pydantic.Field(default=None)
|
|
77
|
-
call_sid: str | None = pydantic.Field(default=None, alias="callSid")
|
avartha/types/voice_agents.py
CHANGED
|
@@ -85,11 +85,6 @@ class VoiceAgentToolBinding(TypedDict):
|
|
|
85
85
|
tool_settings: NotRequired[dict[str, VoiceToolSettings]]
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
class VoiceAgentKnowledgeBinding(TypedDict):
|
|
89
|
-
kind: Literal["document", "folder"]
|
|
90
|
-
id: str
|
|
91
|
-
|
|
92
|
-
|
|
93
88
|
class VoiceAgentConfig(TypedDict):
|
|
94
89
|
"""Versioned agent configuration, always sent and replaced as a whole."""
|
|
95
90
|
|
|
@@ -111,7 +106,6 @@ class VoiceAgentConfig(TypedDict):
|
|
|
111
106
|
built_in_tools: NotRequired[dict[str, VoiceAgentBuiltInTool]]
|
|
112
107
|
tools: NotRequired[list[str]]
|
|
113
108
|
tool_bindings: NotRequired[list[VoiceAgentToolBinding]]
|
|
114
|
-
knowledge_bindings: NotRequired[list[VoiceAgentKnowledgeBinding]]
|
|
115
109
|
capabilities: NotRequired[list[str]]
|
|
116
110
|
chat_artifacts_enabled: NotRequired[bool]
|
|
117
111
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avartha-python-sdk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: Python clients for Avartha Realtime text, streaming speech, and platform management
|
|
5
5
|
Author-email: "Avartha Inc." <team@avartha.ai>
|
|
6
6
|
License-Expression: LicenseRef-Avartha-Proprietary
|
|
@@ -33,11 +33,12 @@ Dynamic: license-file
|
|
|
33
33
|
|
|
34
34
|
Synchronous and asynchronous Python clients for Avartha Realtime text,
|
|
35
35
|
streaming speech, and platform management. Requires Python 3.12+.
|
|
36
|
+
The guides and examples below use SDK 0.0.7 or later.
|
|
36
37
|
|
|
37
38
|
## Install
|
|
38
39
|
|
|
39
40
|
```sh
|
|
40
|
-
python -m pip install avartha-python-sdk
|
|
41
|
+
python -m pip install --upgrade 'avartha-python-sdk>=0.0.7'
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Configure your Avartha API key and platform root:
|
|
@@ -55,15 +56,18 @@ to your workspace. Unsupported tier values and dedicated inference URLs raise
|
|
|
55
56
|
## Discover models
|
|
56
57
|
|
|
57
58
|
```python
|
|
58
|
-
from avartha import
|
|
59
|
+
from avartha import Avartha
|
|
59
60
|
|
|
60
|
-
with
|
|
61
|
+
with Avartha() as client:
|
|
61
62
|
for model in client.models.list():
|
|
62
63
|
print(model.id, model.to_dict().get("protocols", []))
|
|
64
|
+
for model in client.elevenlabs.models.list():
|
|
65
|
+
print(model.model_id, getattr(model, "protocols", []))
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
speech
|
|
68
|
+
`client.models` lists Voice LLMs; choose `openai_realtime` for text.
|
|
69
|
+
`client.elevenlabs.models` lists speech models; choose `elevenlabs_tts` for
|
|
70
|
+
speech synthesis or `elevenlabs_asr` for speech recognition.
|
|
67
71
|
|
|
68
72
|
## Generate text
|
|
69
73
|
|
|
@@ -111,11 +115,10 @@ Inference consumes credits.
|
|
|
111
115
|
| Workspaces, members, invitations | `Control` / `AsyncControl` |
|
|
112
116
|
| Voice agents: create, edit, publish, restore, delete | `Control` / `AsyncControl`: `voice_agents` |
|
|
113
117
|
| Live conversations with published agents | `avartha.conversational_ai`: `Conversation`, `AsyncConversation` |
|
|
114
|
-
| Placing phone calls from Twilio numbers | `ElevenLabs` / `AsyncElevenLabs`: `conversational_ai.twilio.outbound_call` |
|
|
115
118
|
| Combined inference and management | `Avartha` / `AsyncAvartha` |
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
This SDK provides model inference over WebSockets; it does not wrap HTTP
|
|
121
|
+
Chat Completions, Responses, speech synthesis, or file transcription. ElevenLabs-schema agent management is not
|
|
119
122
|
implemented, and accessing it under `conversational_ai` raises `NotImplementedError`.
|
|
120
123
|
|
|
121
124
|
## Migrating and handling errors
|
|
@@ -140,4 +143,4 @@ never retried automatically.
|
|
|
140
143
|
The [source archive on PyPI](https://pypi.org/project/avartha-python-sdk/#files)
|
|
141
144
|
includes `README.md`, customer guides in `docs/`, and runnable examples in
|
|
142
145
|
`examples/`. Download and unpack the archive to read the migration, compatibility,
|
|
143
|
-
and platform-management guides and run the text, speech, and
|
|
146
|
+
and platform-management guides and run the text, speech, and voice-agent examples.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
avartha/__init__.py,sha256=Myea2lrcfw5ZmPj1ZbKOhKdi5CQY44VEKoFTCy8uqyQ,2900
|
|
2
2
|
avartha/_config.py,sha256=mzaHiqYH_7N4okLijiRbJlCNyxvZn4Ni_A_OAZDtOTA,6314
|
|
3
|
-
avartha/_conversation_resources.py,sha256=
|
|
3
|
+
avartha/_conversation_resources.py,sha256=DmMKUNBh6NPZWFGpm734tzE9HecCWsEFBa30CXTXbOM,13134
|
|
4
4
|
avartha/_exceptions.py,sha256=_QY7SEw2nqyOz3pNxDWi40GW1xqB3EZxJ0t4iJ-YBpQ,1588
|
|
5
5
|
avartha/_http_errors.py,sha256=gU8eapqYcI7Q3M8lAxwVH-qwz-TnQf2loBThFKG0ka8,406
|
|
6
6
|
avartha/_models.py,sha256=KB4PeFvktbSOKbT-qMX5tjsExEg9cwufZ3ourx8GcVI,7331
|
|
@@ -11,10 +11,10 @@ avartha/_realtime_asr.py,sha256=KIonb9PUdkKnsP_nQKJOrcsLgjIh9jUnV_ngXFWsWzM,1819
|
|
|
11
11
|
avartha/_realtime_openai.py,sha256=kqdn6K1IMQFUgu_RQXhQxmkWnq2BxVUbicF4_2vhp4o,23859
|
|
12
12
|
avartha/_realtime_resources.py,sha256=rZj8hBYuLP9kTX56hhXTGE8R3crW_Wfmn46VY_dL3TM,7489
|
|
13
13
|
avartha/_realtime_sampling.py,sha256=m4-aPCcvxCxA2l61cRCD2IRAbtKASMSadJVmHu8WkoU,3238
|
|
14
|
-
avartha/_realtime_tts.py,sha256=
|
|
14
|
+
avartha/_realtime_tts.py,sha256=gxL0mPlTnQ_J9zPinfRDXA1T-XQbBJcUa-iKW-QwlSg,40869
|
|
15
15
|
avartha/_sentinels.py,sha256=eX9pg8L8DMWOLrNLfJKWjcFHC5GZO0I_snULCum0ehg,433
|
|
16
16
|
avartha/_speech_config.py,sha256=jL0M8Z6MGXzfgf3Lm7QMMEKDtVXCW5-jrqz5WEtu9g4,1781
|
|
17
|
-
avartha/_speech_http.py,sha256=
|
|
17
|
+
avartha/_speech_http.py,sha256=86HO-ocVdGILaPRxLFi5LTzCqleFxwCWJHxMwePYLsE,12308
|
|
18
18
|
avartha/_speech_resources.py,sha256=zBJHkQkllmWG_bmTIhwmjz9Nf7w4JMNkKJimnaIL3_4,18628
|
|
19
19
|
avartha/_unsupported.py,sha256=qrbsxi2SL6co5M2uLs4fTVOoISuUm58khoayeL22gPE,637
|
|
20
20
|
avartha/_websocket.py,sha256=9R05huh1n8spuyg1n_aIJN0Uk_kLG7BbNPD88wogIKY,855
|
|
@@ -35,10 +35,10 @@ avartha/resources/models.py,sha256=49IEOptS4QztywPbR0QQ0q-TvyHcKMpqklGNZ8HNXH0,6
|
|
|
35
35
|
avartha/resources/realtime/__init__.py,sha256=-eWjNw1X8STRlDvKHgpMf4CvKiTbWgPJ6L_crxVpoTA,439
|
|
36
36
|
avartha/resources/realtime/realtime.py,sha256=zqA5c_-9bzROwXFNyQe5s-tSCu8B6N-NqiimJlmpPqk,1061
|
|
37
37
|
avartha/types/__init__.py,sha256=dTxUAbFhfjXbh9N2TtOSg0YeeWU5Lobf2gq1Nql9qZA,171
|
|
38
|
-
avartha/types/conversational_ai.py,sha256=
|
|
38
|
+
avartha/types/conversational_ai.py,sha256=v67gXcim9jzeT2bSdgV_9bWAIXHH5F8safyMTBcj5-8,2035
|
|
39
39
|
avartha/types/model.py,sha256=1bDAGerzYfk0I94TbDJriq3Z-l6W7OAqdVj2obq-ZZc,261
|
|
40
40
|
avartha/types/speech_language.py,sha256=PT0uG-fwX2dGHwc1xcoM1VSMaNkOILS1AKcSXX8eyd0,388
|
|
41
|
-
avartha/types/voice_agents.py,sha256=
|
|
41
|
+
avartha/types/voice_agents.py,sha256=GLoJW1dt-fZ328QPsfRgf59oEFUoslmTcQqciJZHASk,5753
|
|
42
42
|
avartha/types/websocket_connection_options.py,sha256=EuXoeyPKU4EMHqViOIzYZzvprE9FPPgGrUhHb_2Fcrg,816
|
|
43
43
|
avartha/types/websocket_reconnection.py,sha256=sQ9X1jp31R2Ut5YdUHAtxMORw8KV-XKsPoczmGaNU6Q,580
|
|
44
44
|
avartha/types/audio/__init__.py,sha256=dnsktnAoypmB2Zr3tll_k6q4M7_3scl-HdyJwfTsA0A,119
|
|
@@ -253,9 +253,9 @@ avartha/types/speech/voice_sharing_response.py,sha256=KRO9Rq80wTYV5of2bRrFTVJSDf
|
|
|
253
253
|
avartha/types/speech/voice_sharing_response_category.py,sha256=0yFm6b7R4piHdNTdBWTQYKpNPxW8r2HJvnMdVpT7s5U,241
|
|
254
254
|
avartha/types/speech/voice_sharing_state.py,sha256=8EBRbrRaDhUEap39kDXmt54AYeG172fnuBmRV1TSHP4,201
|
|
255
255
|
avartha/types/speech/voice_verification_response.py,sha256=IL_Z1OTIiR9mvuaWzF9-GsL7pMRZ5fJeV_W3FfjLTTY,770
|
|
256
|
-
avartha_python_sdk-0.0.
|
|
257
|
-
avartha_python_sdk-0.0.
|
|
258
|
-
avartha_python_sdk-0.0.
|
|
259
|
-
avartha_python_sdk-0.0.
|
|
260
|
-
avartha_python_sdk-0.0.
|
|
261
|
-
avartha_python_sdk-0.0.
|
|
256
|
+
avartha_python_sdk-0.0.8.dist-info/licenses/LICENSE,sha256=1ulXrK7aq7kBXX1scK6IiOgnbwqpmK6kWIB3CYznVG8,1135
|
|
257
|
+
avartha_python_sdk-0.0.8.dist-info/licenses/THIRD_PARTY_NOTICES,sha256=R62M0553miTc6E42IVFLUIYkLFim_mf_Abq1lty0IaQ,13081
|
|
258
|
+
avartha_python_sdk-0.0.8.dist-info/METADATA,sha256=Aekvr_tp9UEQaiGEZ9woczskwXm9rw2_4Yqzddvdnf4,6138
|
|
259
|
+
avartha_python_sdk-0.0.8.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
260
|
+
avartha_python_sdk-0.0.8.dist-info/top_level.txt,sha256=cj2kDXl33tO2IPWPduSTukcwozPnYKOsVUVE9QZRUr0,8
|
|
261
|
+
avartha_python_sdk-0.0.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|