Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

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 soxr and numpy dependencies are for).
  • Qwen tool calling needs a qwen3.5-omni-* model. The older qwen3-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.

Release files for agentduet-adapters 0.1.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agentduet-adapters 0.1.0b1
File Size Uploaded
agentduet_adapters-0.1.0b1.tar.gz 41.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agentduet-adapters 0.1.0b1
File Interpreter ABI Platform
agentduet_adapters-0.1.0b1-py3-none-any.whl Python 3 none any Details

Total release size:86.0 kB

Release files / agentduet_adapters-0.1.0b1.tar.gz

Download URL agentduet_adapters-0.1.0b1.tar.gz
Size 41.4 kB
Tags Source
SHA-256 checksum
How to use checksums
49eced01e98e34f0105c531592840d98a19040f2095a8f96fe91fe4cd80c0352
BLAKE2b-256 checksum
How to use checksums
fe1358d7c9de012ee093c9a3265cbb7bd43fb304a948cd7c27fcdf42803ad502
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.12

Release files / agentduet_adapters-0.1.0b1-py3-none-any.whl

Download URL agentduet_adapters-0.1.0b1-py3-none-any.whl
Size 44.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7a36608bb3839a69a10f80a8aac6f346cfe484de8cc83fe0ce957266c80433dd
BLAKE2b-256 checksum
How to use checksums
370b46331c73385001130dd2028882bddb76ac2f38ab399dcf52b47e8f64f7f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.12

Release history Release notifications | RSS feed

This release

0.1.0b1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page