Metadata-Version: 2.4
Name: getpatter
Version: 0.5.0
Summary: Open-source voice AI SDK — connect any AI agent to real phone calls in 4 lines of code
Author-email: PatterAI <hello@getpatter.com>
License: MIT
Project-URL: Homepage, https://www.getpatter.com
Project-URL: Documentation, https://docs.getpatter.com
Project-URL: Repository, https://github.com/PatterAI/Patter
Project-URL: Bug Tracker, https://github.com/PatterAI/Patter/issues
Keywords: voice,ai,voice-ai,phone,telephony,speech,tts,stt,twilio,telnyx,openai,elevenlabs,llm,agent,ai-agent,conversational-ai,realtime,call,ivr,chatbot
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Telephony
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Framework :: AsyncIO
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: websockets<16,>=14.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: uvicorn[standard]>=0.30.0
Requires-Dist: python-multipart>=0.0.20
Requires-Dist: twilio>=9.0.0
Requires-Dist: audioop-lts>=0.2.1; python_version >= "3.13"
Requires-Dist: openai>=1.0.0
Requires-Dist: cryptography>=42.0.0
Provides-Extra: local
Provides-Extra: soniox
Requires-Dist: aiohttp>=3.10; extra == "soniox"
Provides-Extra: speechmatics
Requires-Dist: speechmatics-voice[smart]>=0.2.8; extra == "speechmatics"
Provides-Extra: assemblyai
Requires-Dist: aiohttp>=3.10; extra == "assemblyai"
Provides-Extra: cartesia
Requires-Dist: aiohttp>=3.10; extra == "cartesia"
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
Requires-Dist: psutil>=5.9.0; extra == "dev"
Requires-Dist: aiohttp>=3.10; extra == "dev"
Provides-Extra: silero
Requires-Dist: onnxruntime>=1.18; extra == "silero"
Requires-Dist: numpy>=1.26; extra == "silero"
Provides-Extra: krisp
Requires-Dist: krisp-audio>=2.0; extra == "krisp"
Requires-Dist: numpy>=1.26; extra == "krisp"
Provides-Extra: deepfilternet
Requires-Dist: deep-filter>=0.5; extra == "deepfilternet"
Requires-Dist: torch>=2.0; extra == "deepfilternet"
Requires-Dist: numpy>=1.26; extra == "deepfilternet"
Provides-Extra: ivr
Requires-Dist: scikit-learn>=1.3; extra == "ivr"
Requires-Dist: numpy>=1.26; extra == "ivr"
Provides-Extra: telnyx-ai
Requires-Dist: aiohttp>=3.10; extra == "telnyx-ai"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.41; extra == "anthropic"
Requires-Dist: httpx>=0.27; extra == "anthropic"
Provides-Extra: groq
Requires-Dist: openai>=1.0; extra == "groq"
Provides-Extra: cerebras
Requires-Dist: openai>=1.0; extra == "cerebras"
Requires-Dist: msgpack>=1.0; extra == "cerebras"
Provides-Extra: google
Requires-Dist: google-genai>=1.55; extra == "google"
Provides-Extra: background-audio
Requires-Dist: numpy>=1.26; extra == "background-audio"
Requires-Dist: soundfile>=0.12; extra == "background-audio"
Provides-Extra: rime
Requires-Dist: aiohttp>=3.10; extra == "rime"
Provides-Extra: lmnt
Requires-Dist: aiohttp>=3.10; extra == "lmnt"
Provides-Extra: gemini-live
Requires-Dist: google-genai>=1.55; extra == "gemini-live"
Provides-Extra: ultravox
Requires-Dist: aiohttp>=3.10; extra == "ultravox"
Provides-Extra: evals
Requires-Dist: pyyaml>=6.0; extra == "evals"
Requires-Dist: openai>=1.0; extra == "evals"
Provides-Extra: tracing
Requires-Dist: opentelemetry-api>=1.27; extra == "tracing"
Requires-Dist: opentelemetry-sdk>=1.27; extra == "tracing"
Requires-Dist: opentelemetry-exporter-otlp>=1.27; extra == "tracing"
Provides-Extra: scheduling
Requires-Dist: apscheduler>=3.10; extra == "scheduling"
Provides-Extra: tunnel
Requires-Dist: cloudflared>=0.7; extra == "tunnel"
Dynamic: license-file

<p align="center">
  <h1 align="center">Patter Python SDK</h1>
  <p align="center">Connect AI agents to phone numbers in four lines of code</p>
</p>

<p align="center">
  <a href="https://pypi.org/project/getpatter/"><img src="https://img.shields.io/pypi/v/getpatter?logo=pypi&logoColor=white&label=pip%20install%20getpatter" alt="PyPI" /></a>
  <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" /></a>
  <img src="https://img.shields.io/badge/python-3.11%2B-blue?logo=python&logoColor=white" alt="Python 3.11+" />
</p>

<p align="center">
  <a href="#quickstart">Quickstart</a> •
  <a href="#features">Features</a> •
  <a href="#configuration">Configuration</a> •
  <a href="#voice-modes">Voice Modes</a> •
  <a href="#api-reference">API Reference</a> •
  <a href="#contributing">Contributing</a>
</p>

---

Patter is the open-source SDK that gives your AI agent a phone number. Point it at any function that returns a string, and Patter handles the rest: telephony, speech-to-text, text-to-speech, and real-time audio streaming. You build the agent — we connect it to the phone.

## Quickstart

```bash
pip install getpatter
```

Set the env vars your carrier and engine need:

```bash
export TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export TWILIO_AUTH_TOKEN=your_auth_token
export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx
```

Four lines of Python:

```python
from getpatter import Patter, Twilio, OpenAIRealtime

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(engine=OpenAIRealtime(), system_prompt="You are a friendly receptionist for Acme Corp.", first_message="Hello! How can I help?")
await phone.serve(agent, tunnel=True)
```

`tunnel=True` spawns a Cloudflare tunnel and points your Twilio number at it. In production, pass `webhook_url="api.prod.example.com"` to the constructor instead.

## Features

| Feature | Method | Example |
|---|---|---|
| Inbound calls | `phone.serve(agent)` | Answer calls as an AI |
| Outbound calls + AMD | `phone.call(to, machine_detection=True)` | Place calls with voicemail detection |
| Tool calling | `agent(tools=[Tool(...)])` | Agent calls external APIs mid-conversation |
| Custom STT + TTS | `agent(stt=DeepgramSTT(), tts=ElevenLabsTTS())` | Bring your own voice providers |
| Dynamic variables | `agent(variables={...})` | Personalize prompts per caller |
| Custom LLM (any model) | `serve(on_message=handler)` | Claude, Mistral, LLaMA, etc. |
| Call recording | `serve(recording=True)` | Record all calls |
| Call transfer | `transfer_call` (auto-injected) | Transfer to a human |
| Voicemail drop | `call(voicemail_message="...")` | Play message on voicemail |

## Configuration

### Environment variables

Every provider reads its credentials from the environment by default. Pass `api_key="..."` to any constructor to override.

| Variable | Used by |
|---|---|
| `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN` | `Twilio()` carrier |
| `TELNYX_API_KEY`, `TELNYX_CONNECTION_ID`, `TELNYX_PUBLIC_KEY` (optional) | `Telnyx()` carrier |
| `OPENAI_API_KEY` | `OpenAIRealtime`, `getpatter.stt.whisper.STT`, `getpatter.tts.openai.TTS` |
| `ELEVENLABS_API_KEY`, `ELEVENLABS_AGENT_ID` | `ElevenLabsConvAI`, `getpatter.tts.elevenlabs.TTS` |
| `DEEPGRAM_API_KEY` | `getpatter.stt.deepgram.STT` |
| `CARTESIA_API_KEY` | `getpatter.stt.cartesia.STT`, `getpatter.tts.cartesia.TTS` |
| `RIME_API_KEY` | `getpatter.tts.rime.TTS` |
| `LMNT_API_KEY` | `getpatter.tts.lmnt.TTS` |
| `SONIOX_API_KEY` | `getpatter.stt.soniox.STT` |
| `SPEECHMATICS_API_KEY` | `getpatter.stt.speechmatics.STT` |
| `ASSEMBLYAI_API_KEY` | `getpatter.stt.assemblyai.STT` |

```bash
cp .env.example .env
# Edit .env with your API keys
```

> **Telnyx:** Telnyx is a fully supported telephony provider alternative to Twilio. Both carriers receive equal support for DTMF, transfer, recording, and metrics.

## Voice Modes

| Mode | Latency | Quality | Best For |
|---|---|---|---|
| **OpenAI Realtime** | Lowest | High | Fluid, low-latency conversations |
| **Pipeline** (STT + LLM + TTS) | Low | High | Independent control over STT and TTS |
| **ElevenLabs ConvAI** | Low | High | ElevenLabs-managed conversation flow |

## API Reference

### `Patter` constructor

```python
Patter(
    carrier: Twilio | Telnyx,
    phone_number: str,
    webhook_url: str = "",         # Public hostname (no scheme). Mutually exclusive with tunnel=...
    tunnel: CloudflareTunnel | Static | Ngrok | None = None,
    pricing: dict | None = None,
)
```

| Parameter | Type | Description |
|---|---|---|
| `carrier` | `Twilio` / `Telnyx` | Carrier instance. Reads env vars by default. |
| `phone_number` | `str` | Your phone number in E.164 format. |
| `webhook_url` | `str` | Public hostname your local server is reachable on. Use instead of `tunnel=`. |
| `tunnel` | instance | `CloudflareTunnel()`, `Static(hostname=...)`, or `Ngrok()`. |

### `phone.agent()`

```python
phone.agent(
    system_prompt: str,
    engine: OpenAIRealtime | ElevenLabsConvAI | None = None,   # default OpenAIRealtime()
    stt: STTProvider | None = None,                            # e.g. DeepgramSTT()
    tts: TTSProvider | None = None,                            # e.g. ElevenLabsTTS()
    voice: str = "alloy",
    model: str = "gpt-4o-mini-realtime-preview",
    language: str = "en",
    first_message: str = "",
    tools: list[Tool] | None = None,
    guardrails: list[Guardrail] | None = None,
    variables: dict | None = None,
    ...,
)
```

Pass `engine=` for end-to-end mode, `stt=` + `tts=` for pipeline mode. Both arguments may take plain adapter instances (e.g. `DeepgramSTT()`) that read their API key from the environment.

### `phone.serve()`

```python
await phone.serve(
    agent: Agent,
    port: int = 8000,
    tunnel: bool = False,          # shortcut for Patter(tunnel=CloudflareTunnel())
    dashboard: bool = True,
    recording: bool = False,
    on_call_start: Callable | None = None,
    on_call_end: Callable | None = None,
    on_transcript: Callable | None = None,
    on_message: Callable | str | None = None,
    voicemail_message: str = "",
    dashboard_token: str = "",
)
```

### `phone.call()`

```python
await phone.call(
    to: str,
    agent: Agent | None = None,            # required in local mode
    from_number: str = "",
    first_message: str = "",
    machine_detection: bool = False,
    voicemail_message: str = "",
    ring_timeout: int | None = None,
)
```

### STT / TTS catalog

Flat re-exports (short form):

```python
from getpatter import (
    Twilio, Telnyx,
    OpenAIRealtime, ElevenLabsConvAI,
    # STT / TTS classes live in namespaced modules — see below.
)
```

Namespaced imports (one module per provider):

```python
from getpatter.stt import deepgram, whisper, cartesia, soniox, speechmatics, assemblyai
from getpatter.tts import elevenlabs, openai as openai_tts, cartesia as cartesia_tts, rime, lmnt

stt = deepgram.STT()                    # reads DEEPGRAM_API_KEY
tts = elevenlabs.TTS(voice="rachel")    # reads ELEVENLABS_API_KEY
```

## Examples

### Inbound calls — default engine

```python
import asyncio
from getpatter import Patter, Twilio, OpenAIRealtime

async def main() -> None:
    phone = Patter(carrier=Twilio(), phone_number="+15550001234")
    agent = phone.agent(
        engine=OpenAIRealtime(),
        system_prompt="You are a helpful customer service agent.",
        first_message="Hello! How can I help?",
    )
    await phone.serve(
        agent,
        tunnel=True,
        on_call_start=lambda data: print(f"Call from {data['caller']}"),
        on_call_end=lambda data: print("Call ended"),
    )

asyncio.run(main())
```

### Custom voice — Deepgram STT + ElevenLabs TTS

```python
from getpatter import Patter, Twilio
from getpatter.stt import deepgram
from getpatter.tts import elevenlabs

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
    stt=deepgram.STT(),              # reads DEEPGRAM_API_KEY
    tts=elevenlabs.TTS(voice="rachel"),   # reads ELEVENLABS_API_KEY
    system_prompt="You are a helpful voice assistant.",
)
await phone.serve(agent, tunnel=True)
```

### Tool calling

```python
from getpatter import Patter, Twilio, OpenAIRealtime, Tool, tool

@tool
async def check_availability(date: str) -> dict:
    """Check appointment availability for a given ISO date."""
    return {"available": True}

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
    engine=OpenAIRealtime(),
    system_prompt="You are a booking assistant.",
    tools=[check_availability],
)
await phone.serve(agent, tunnel=True)
```

### Outbound calls

```python
from getpatter import Patter, Twilio, OpenAIRealtime

phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
    engine=OpenAIRealtime(),
    system_prompt="You are making reminder calls.",
    first_message="Hi, this is a reminder from Acme Corp.",
)

await phone.serve(agent, tunnel=True)
await phone.call(to="+14155551234", agent=agent)
```

### Dynamic variables

```python
agent = phone.agent(
    engine=OpenAIRealtime(),
    system_prompt="You are helping {customer_name}, account #{account_id}.",
    first_message="Hi {customer_name}! How can I help you today?",
    variables={"customer_name": "Jane", "account_id": "A-789"},
)
```

## Contributing

Pull requests are welcome.

```bash
cd sdk-py && pip install -e ".[dev]" && pytest tests/ -v
```

Please open an issue before submitting large changes so we can discuss the approach first.

## License

MIT — see [LICENSE](../LICENSE).
