Model adapters for the AgentDuet VoiceAgent layer: Gemini Live, xAI Grok Voice, Alibaba Qwen-Omni, Amazon Nova Sonic
Project description
agentduet-adapters
Model adapters for the AgentDuet Python SDK's VoiceAgent layer.
AgentDuet connects an AI agent to real phone calls: it owns the carrier side (SIP trunking, numbers, WhatsApp, web chat) and hands your code a bidirectional PCM audio stream. These adapters connect that stream to a speech-to-speech model, so a working voice agent is three lines:
from agentduet import VoiceAgent
from agentduet_adapters.gemini import GeminiLive
VoiceAgent.from_env().run(
GeminiLive(instruction="You are May, a warm phone concierge. Keep replies short.")
)
Call your number and the agent picks up.
Install
The SDK comes as a dependency; pick the provider you actually call:
pip install "agentduet-adapters[gemini]" # Google Gemini Live
pip install "agentduet-adapters[grok]" # xAI Grok Voice
pip install "agentduet-adapters[qwen]" # Alibaba Qwen-Omni Realtime
pip install "agentduet-adapters[nova-sonic]" # Amazon Nova Sonic
Nothing vendor-specific installs by default, so a Nova Sonic deployment never ships
google-genai. Each adapter module raises ImportError naming its extra if you import it
without one.
The adapters
| Import | Class | Default model | Credentials |
|---|---|---|---|
agentduet_adapters.gemini |
GeminiLive |
models/gemini-3.1-flash-live-preview |
GEMINI_API_KEY |
agentduet_adapters.grok_voice |
GrokVoice |
grok-voice-think-fast-1.0 |
XAI_API_KEY, or the raw key in ~/.x.ai |
agentduet_adapters.qwen |
QwenVoice |
qwen3.5-omni-flash-realtime |
DASHSCOPE_API_KEY, or the raw key in ~/.qwen; DASHSCOPE_REGION selects intl (default) or cn |
agentduet_adapters.nova_sonic |
NovaSonic |
amazon.nova-2-sonic-v1:0 |
standard AWS environment credentials, AWS_REGION |
Every adapter takes the same four arguments: instruction=, tools=, voice= and model=.
Swapping providers is a one-line change. Credentials are the one deliberate exception:
Gemini, Grok and Qwen take api_key=, while Nova Sonic follows the AWS credential chain and
takes region= (plus an optional credentials_resolver=).
Two provider quirks worth knowing, because they are not bugs in the adapters:
- Qwen runs at 16 kHz input and 24 kHz output while the call runs at 24 kHz, so the
adapter resamples inbound audio (that is what the
soxrandnumpydependencies are for). - Qwen tool calling needs a
qwen3.5-omni-*model. The olderqwen3-omni-*models do not support it, which is why the default is 3.5.
Tools, transcripts and usage
VoiceAgent handles these, not the adapters: pass callbacks and every adapter reports through
the same path.
async def tools(name: str, args: dict) -> dict:
if name == "get_balance":
return {"balance": 402.15, "currency": "SGD"}
return {"error": f"unknown tool {name}"}
async def on_transcript(ev): # ev.role is "user" or "agent"
print(f"{ev.role}: {ev.text}")
async def on_usage(ev): # cumulative for this call
print(f"tokens: {ev.total} (in {ev.input} / out {ev.output})")
VoiceAgent.from_env(tools=tools, on_transcript=on_transcript, on_usage=on_usage).run(
GrokVoice(instruction="You are a bank concierge.", tools=[
{"name": "get_balance", "description": "Balance for the caller",
"input_schema": {"type": "object", "properties": {}}},
])
)
Tool declarations are passed in a neutral shape (name, description, input_schema) and
each adapter translates it to its provider's format. A tool that raises, or one the model
calls with no handler registered, returns an error result to the model rather than ending the
call.
Writing your own adapter
You do not need this package for that. The seam is in the SDK: implement VoiceModel and
ModelSession from agentduet and pass your object to VoiceAgent.
from agentduet import AudioOut, Interrupted, ToolCall, TranscriptDelta, Usage
class MyModel:
async def open(self): # once per call
return MySession(await connect_to_my_provider())
class MySession:
async def push_audio(self, pcm: bytes) -> None: # caller audio, 24 kHz PCM16
...
def events(self): # async iterator
# yield AudioOut(pcm=...) to speak, Interrupted() on barge-in,
# ToolCall(id=..., name=..., args={...}), TranscriptDelta(text=..., role=...),
# Usage(total=..., input=..., output=...)
...
async def send_tool_result(self, call_id: str, result: dict) -> None:
...
async def close(self) -> None:
...
The four adapters here are the worked examples: gemini.py is the shortest, nova_sonic.py
the most involved. VoiceAgent's side of the contract (what it guarantees to call, and when)
is specified in the SDK's specs/wire-protocol-spec.md, section 10.5.
Why this is a separate package
The SDK is a stable transport library: its only dependencies are websockets, httpx and
abxbus. Adapters are the opposite kind of code. They track fast-moving provider APIs, they
each drag in a vendor SDK, and they are the part you are most likely to want to read, fork or
fix yourself. Splitting them out means a provider's breaking change ships as a patch here
instead of forcing a core SDK release, and these can be Apache-2.0 while the SDK is not.
Examples
examples/ has a runnable quickstart per provider. Each needs AGENTDUET_API_KEY and
AGENTDUET_CONNECTOR_UUID (from B3) plus that provider's key:
pip install "agentduet-adapters[gemini,examples]"
python examples/voice_agent_gemini.py # then call your number
License
Apache-2.0. The agentduet SDK itself is separately licensed.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentduet_adapters-0.1.0b1.tar.gz.
File metadata
- Download URL: agentduet_adapters-0.1.0b1.tar.gz
- Upload date:
- Size: 41.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49eced01e98e34f0105c531592840d98a19040f2095a8f96fe91fe4cd80c0352
|
|
| MD5 |
0ba5577c75cb0c903c71711c801eccb5
|
|
| BLAKE2b-256 |
fe1358d7c9de012ee093c9a3265cbb7bd43fb304a948cd7c27fcdf42803ad502
|
File details
Details for the file agentduet_adapters-0.1.0b1-py3-none-any.whl.
File metadata
- Download URL: agentduet_adapters-0.1.0b1-py3-none-any.whl
- Upload date:
- Size: 44.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a36608bb3839a69a10f80a8aac6f346cfe484de8cc83fe0ce957266c80433dd
|
|
| MD5 |
39a2118905cb249495da280acb76e7c7
|
|
| BLAKE2b-256 |
370b46331c73385001130dd2028882bddb76ac2f38ab399dcf52b47e8f64f7f5
|