getpatter 0.4.1 → 0.4.2
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.
- package/README.md +185 -587
- package/dist/chunk-35EVXMGB.mjs +4472 -0
- package/dist/chunk-AFUYSNDH.mjs +31 -0
- package/dist/chunk-JO5C35FM.mjs +65 -0
- package/dist/chunk-OOIUSZB4.mjs +37 -0
- package/dist/cli.js +1139 -0
- package/dist/index.d.mts +1063 -85
- package/dist/index.d.ts +1063 -85
- package/dist/index.js +8969 -3904
- package/dist/index.mjs +2382 -3354
- package/dist/lib-4WCAS54J.mjs +830 -0
- package/dist/node-cron-373UVDIO.mjs +935 -0
- package/dist/persistence-CYIGNHSU.mjs +7 -0
- package/dist/resources/audio/NOTICE +2 -0
- package/dist/resources/audio/city-ambience.ogg +0 -0
- package/dist/resources/audio/crowded-room.ogg +0 -0
- package/dist/resources/audio/forest-ambience.ogg +0 -0
- package/dist/resources/audio/hold_music.ogg +0 -0
- package/dist/resources/audio/keyboard-typing.ogg +0 -0
- package/dist/resources/audio/keyboard-typing2.ogg +0 -0
- package/dist/resources/audio/office-ambience.ogg +0 -0
- package/dist/resources/silero_vad.onnx +0 -0
- package/dist/{test-mode-JMXZSAJS.mjs → test-mode-RH65MMSP.mjs} +2 -1
- package/dist/{tunnel-HYSU7EF2.mjs → tunnel-BL7A7GXW.mjs} +2 -1
- package/package.json +25 -8
- package/src/resources/audio/NOTICE +2 -0
- package/src/resources/audio/city-ambience.ogg +0 -0
- package/src/resources/audio/crowded-room.ogg +0 -0
- package/src/resources/audio/forest-ambience.ogg +0 -0
- package/src/resources/audio/hold_music.ogg +0 -0
- package/src/resources/audio/keyboard-typing.ogg +0 -0
- package/src/resources/audio/keyboard-typing2.ogg +0 -0
- package/src/resources/audio/office-ambience.ogg +0 -0
- package/dist/chunk-TAATEHKF.mjs +0 -396
- package/dist/chunk-VNU4GNW3.mjs +0 -45
package/README.md
CHANGED
|
@@ -1,112 +1,40 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<h1 align="center">Patter</h1>
|
|
3
|
-
<p align="center">Connect AI agents to phone numbers with
|
|
2
|
+
<h1 align="center">Patter TypeScript SDK</h1>
|
|
3
|
+
<p align="center">Connect AI agents to phone numbers with 4 lines of code</p>
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
<p align="center">
|
|
7
|
-
<a href="
|
|
8
|
-
<a href="
|
|
9
|
-
<
|
|
10
|
-
<a href="#documentation">Documentation</a> •
|
|
11
|
-
<a href="#self-hosting">Self-Hosting</a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/getpatter"><img src="https://img.shields.io/npm/v/getpatter?logo=npm&logoColor=white&label=npm%20install%20getpatter" alt="npm" /></a>
|
|
8
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" /></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/typescript-5.0%2B-3178c6?logo=typescript&logoColor=white" alt="TypeScript 5+" />
|
|
12
10
|
</p>
|
|
13
11
|
|
|
14
12
|
<p align="center">
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
<
|
|
13
|
+
<a href="#quickstart">Quickstart</a> •
|
|
14
|
+
<a href="#features">Features</a> •
|
|
15
|
+
<a href="#configuration">Configuration</a> •
|
|
16
|
+
<a href="#voice-modes">Voice Modes</a> •
|
|
17
|
+
<a href="#api-reference">API Reference</a> •
|
|
18
|
+
<a href="#contributing">Contributing</a>
|
|
18
19
|
</p>
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
22
|
-
Patter is
|
|
23
|
+
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.
|
|
23
24
|
|
|
24
25
|
## Quickstart
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```python
|
|
30
|
-
import asyncio
|
|
31
|
-
from patter import Patter, IncomingMessage
|
|
32
|
-
|
|
33
|
-
async def on_message(msg: IncomingMessage) -> str:
|
|
34
|
-
# Your agent logic here — return what the AI should say
|
|
35
|
-
return f"You said: {msg.text}"
|
|
36
|
-
|
|
37
|
-
async def main():
|
|
38
|
-
phone = Patter(api_key="pt_xxx")
|
|
39
|
-
await phone.connect(on_message=on_message) # starts listening for inbound calls
|
|
40
|
-
|
|
41
|
-
asyncio.run(main())
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
</details>
|
|
45
|
-
|
|
46
|
-
<details>
|
|
47
|
-
<summary><strong>TypeScript</strong></summary>
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { Patter } from "getpatter";
|
|
51
|
-
|
|
52
|
-
const phone = new Patter({ apiKey: "pt_xxx" });
|
|
53
|
-
|
|
54
|
-
await phone.connect({
|
|
55
|
-
onMessage: async (msg) => {
|
|
56
|
-
// Your agent logic here — return what the AI should say
|
|
57
|
-
return `You said: ${msg.text}`;
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
</details>
|
|
63
|
-
|
|
64
|
-
## Local Mode (No Cloud Required)
|
|
65
|
-
|
|
66
|
-
Run Patter entirely in your process — no Patter account, no cloud backend.
|
|
67
|
-
|
|
68
|
-
<details open>
|
|
69
|
-
<summary><strong>Python</strong></summary>
|
|
70
|
-
|
|
71
|
-
```python
|
|
72
|
-
import asyncio
|
|
73
|
-
from patter import Patter
|
|
74
|
-
|
|
75
|
-
async def main():
|
|
76
|
-
phone = Patter(
|
|
77
|
-
mode="local",
|
|
78
|
-
twilio_sid="AC...", twilio_token="...",
|
|
79
|
-
openai_key="sk-...",
|
|
80
|
-
phone_number="+1...",
|
|
81
|
-
webhook_url="xxx.ngrok-free.dev",
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
agent = phone.agent(
|
|
85
|
-
system_prompt="You are a friendly customer service agent for Acme Corp.",
|
|
86
|
-
voice="alloy",
|
|
87
|
-
first_message="Hello! Thanks for calling. How can I help?",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
print("Listening for calls...")
|
|
91
|
-
await phone.serve(agent=agent, port=8000)
|
|
92
|
-
|
|
93
|
-
asyncio.run(main())
|
|
27
|
+
```bash
|
|
28
|
+
npm install getpatter
|
|
94
29
|
```
|
|
95
30
|
|
|
96
|
-
</details>
|
|
97
|
-
|
|
98
|
-
<details>
|
|
99
|
-
<summary><strong>TypeScript</strong></summary>
|
|
100
|
-
|
|
101
31
|
```typescript
|
|
102
32
|
import { Patter } from "getpatter";
|
|
103
33
|
|
|
104
34
|
const phone = new Patter({
|
|
105
|
-
mode: "local",
|
|
106
35
|
twilioSid: "AC...", twilioToken: "...",
|
|
107
36
|
openaiKey: "sk-...",
|
|
108
37
|
phoneNumber: "+1...",
|
|
109
|
-
webhookUrl: "xxx.ngrok-free.dev",
|
|
110
38
|
});
|
|
111
39
|
|
|
112
40
|
const agent = phone.agent({
|
|
@@ -115,339 +43,201 @@ const agent = phone.agent({
|
|
|
115
43
|
firstMessage: "Hello! Thanks for calling. How can I help?",
|
|
116
44
|
});
|
|
117
45
|
|
|
118
|
-
console.log("Listening for calls...");
|
|
119
46
|
await phone.serve({ agent, port: 8000 });
|
|
120
47
|
```
|
|
121
48
|
|
|
122
|
-
</details>
|
|
123
|
-
|
|
124
|
-
## Local vs Cloud
|
|
125
|
-
|
|
126
|
-
| | Cloud Mode | Local Mode |
|
|
127
|
-
|---|---|---|
|
|
128
|
-
| **Setup** | Patter API key only | Twilio/Telnyx + OpenAI keys |
|
|
129
|
-
| **Infrastructure** | Managed by Patter | Runs in your process |
|
|
130
|
-
| **Backend** | `wss://api.getpatter.com` | Built-in (FastAPI / Express) |
|
|
131
|
-
| **Webhook** | Configured automatically | Requires public URL (e.g. ngrok) |
|
|
132
|
-
| **Voice modes** | All three | All three |
|
|
133
|
-
| **Best for** | Production, multi-tenant | Development, on-prem, full control |
|
|
134
|
-
|
|
135
49
|
## Features
|
|
136
50
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
- Tool calling via webhooks with automatic 3x retry
|
|
149
|
-
- Built-in tools: `transfer_call`, `end_call` (auto-injected)
|
|
150
|
-
|
|
151
|
-
### Telephony
|
|
152
|
-
- Twilio and Telnyx carriers
|
|
153
|
-
- Inbound and outbound calls
|
|
154
|
-
- Call transfer to humans (`transfer_call` system tool)
|
|
155
|
-
- Call recording (`recording: true` in `serve()`)
|
|
156
|
-
- Answering machine detection (`machineDetection: true` for outbound)
|
|
157
|
-
- Voicemail drop (`voicemailMessage: "..."` plays on voicemail detection)
|
|
158
|
-
- Custom parameters passthrough via TwiML
|
|
159
|
-
|
|
160
|
-
### Developer Experience
|
|
161
|
-
- `pip install patter` / `npm install getpatter`
|
|
162
|
-
- 10 lines of code to connect an agent to a phone
|
|
163
|
-
- Local mode (embedded, no backend) + Cloud mode
|
|
164
|
-
- Python + TypeScript SDKs with full parity
|
|
165
|
-
- MCP server for Claude Desktop
|
|
166
|
-
- Open-source (MIT)
|
|
167
|
-
|
|
168
|
-
## Complete Example
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
const phone = new Patter({
|
|
172
|
-
mode: 'local',
|
|
173
|
-
twilioSid: process.env.TWILIO_SID,
|
|
174
|
-
twilioToken: process.env.TWILIO_TOKEN,
|
|
175
|
-
openaiKey: process.env.OPENAI_KEY,
|
|
176
|
-
phoneNumber: '+16592214527',
|
|
177
|
-
webhookUrl: 'your-domain.ngrok-free.dev',
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const agent = phone.agent({
|
|
181
|
-
systemPrompt: `You are a customer service agent for Acme Corp.
|
|
182
|
-
The customer is {customer_name} with order #{order_id}.
|
|
183
|
-
Check inventory before answering stock questions.
|
|
184
|
-
Transfer to a human if the customer is upset.`,
|
|
185
|
-
voice: 'alloy',
|
|
186
|
-
language: 'en',
|
|
187
|
-
firstMessage: 'Hi {customer_name}! How can I help with order #{order_id}?',
|
|
188
|
-
variables: {
|
|
189
|
-
customer_name: 'John',
|
|
190
|
-
order_id: '12345',
|
|
191
|
-
},
|
|
192
|
-
tools: [{
|
|
193
|
-
name: 'check_inventory',
|
|
194
|
-
description: 'Check product stock',
|
|
195
|
-
parameters: { type: 'object', properties: { product: { type: 'string' } } },
|
|
196
|
-
webhookUrl: 'https://api.acme.com/inventory',
|
|
197
|
-
}],
|
|
198
|
-
// Built-in: transfer_call, end_call (auto-injected)
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await phone.serve({
|
|
202
|
-
agent,
|
|
203
|
-
port: 8000,
|
|
204
|
-
recording: true,
|
|
205
|
-
onCallStart: async (data) => console.log(`Call from ${data.caller}`),
|
|
206
|
-
onCallEnd: async (data) => console.log(`Transcript: ${data.transcript?.length} turns`),
|
|
207
|
-
onTranscript: async (data) => console.log(`${data.role}: ${data.text}`),
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// Outbound with machine detection
|
|
211
|
-
await phone.call({
|
|
212
|
-
to: '+1234567890',
|
|
213
|
-
machineDetection: true,
|
|
214
|
-
voicemailMessage: 'Hi, please call us back at 555-0123.',
|
|
215
|
-
});
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## How It Works
|
|
51
|
+
| Feature | Method | Example |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| Inbound calls | `phone.serve(agent)` | Answer calls as an AI |
|
|
54
|
+
| Outbound calls + AMD | `phone.call(to, machineDetection)` | Place calls with voicemail detection |
|
|
55
|
+
| Tool calling (webhooks) | `agent(tools=[...])` | Agent calls external APIs mid-conversation |
|
|
56
|
+
| Custom STT + TTS | `agent(provider="pipeline")` | Bring your own voice providers |
|
|
57
|
+
| Dynamic variables | `agent(variables={...})` | Personalize prompts per caller |
|
|
58
|
+
| Custom LLM (any model) | `serve(onMessage=handler)` | Claude, Mistral, LLaMA, etc. |
|
|
59
|
+
| Call recording | `serve(recording=true)` | Record all calls |
|
|
60
|
+
| Call transfer | `transfer_call` (auto-injected) | Transfer to a human |
|
|
61
|
+
| Voicemail drop | `call(voicemailMessage="...")` | Play message on voicemail |
|
|
219
62
|
|
|
220
|
-
|
|
221
|
-
Your Code (on_message handler)
|
|
222
|
-
│
|
|
223
|
-
▼
|
|
224
|
-
Patter SDK ──WebSocket──► Patter Backend ──────────────────────────────┐
|
|
225
|
-
│ │
|
|
226
|
-
┌───────┴────────┐ │
|
|
227
|
-
▼ ▼ ▼
|
|
228
|
-
STT Engine TTS Engine Telephony Provider
|
|
229
|
-
(Deepgram / (ElevenLabs / (Twilio / Telnyx)
|
|
230
|
-
Whisper / OpenAI TTS) │
|
|
231
|
-
OpenAI RT) │ │
|
|
232
|
-
│ └───────────────────────►│
|
|
233
|
-
└────────────────────────────────────────►│
|
|
234
|
-
▼
|
|
235
|
-
Phone Call
|
|
236
|
-
```
|
|
63
|
+
## Configuration
|
|
237
64
|
|
|
238
|
-
|
|
65
|
+
### Environment Variables
|
|
239
66
|
|
|
240
|
-
|
|
67
|
+
| Variable | Required | Description |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `OPENAI_API_KEY` | Yes (Realtime mode) | OpenAI API key with Realtime access |
|
|
70
|
+
| `TWILIO_ACCOUNT_SID` | Yes | Twilio account SID |
|
|
71
|
+
| `TWILIO_AUTH_TOKEN` | Yes | Twilio auth token |
|
|
72
|
+
| `TWILIO_PHONE_NUMBER` | Yes | Your Twilio phone number (E.164) |
|
|
73
|
+
| `DEEPGRAM_API_KEY` | Pipeline mode | Deepgram STT key |
|
|
74
|
+
| `ELEVENLABS_API_KEY` | Pipeline mode | ElevenLabs TTS key |
|
|
75
|
+
| `ANTHROPIC_API_KEY` | Custom LLM | For bringing your own model |
|
|
76
|
+
| `WEBHOOK_URL` | No | Public URL (auto-tunneled via Cloudflare if omitted) |
|
|
241
77
|
|
|
242
78
|
```bash
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
# TypeScript / Node.js
|
|
247
|
-
npm install getpatter
|
|
79
|
+
cp .env.example .env
|
|
80
|
+
# Edit .env with your API keys
|
|
248
81
|
```
|
|
249
82
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
### Inbound Calls (AI answers the phone)
|
|
83
|
+
> **Telnyx:** Telnyx is a fully supported telephony provider alternative to Twilio. Both carriers receive equal support for DTMF, transfer, recording, and metrics.
|
|
253
84
|
|
|
254
|
-
|
|
255
|
-
<summary><strong>Python</strong></summary>
|
|
85
|
+
## Voice Modes
|
|
256
86
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
87
|
+
| Mode | Latency | Quality | Best For |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| **OpenAI Realtime** | Lowest | High | Fluid, low-latency conversations |
|
|
90
|
+
| **Deepgram + ElevenLabs** | Low | High | Independent control over STT and TTS |
|
|
91
|
+
| **ElevenLabs ConvAI** | Low | High | ElevenLabs-managed conversation flow |
|
|
260
92
|
|
|
261
|
-
|
|
262
|
-
if "hours" in msg.text.lower():
|
|
263
|
-
return "We're open Monday through Friday, 9 to 5."
|
|
264
|
-
return "How can I help you today?"
|
|
93
|
+
## API Reference
|
|
265
94
|
|
|
266
|
-
|
|
267
|
-
phone = Patter(api_key="pt_xxx")
|
|
268
|
-
await phone.connect(
|
|
269
|
-
on_message=agent,
|
|
270
|
-
on_call_start=lambda data: print(f"Call from {data['caller']}"),
|
|
271
|
-
on_call_end=lambda data: print("Call ended"),
|
|
272
|
-
)
|
|
273
|
-
await asyncio.Event().wait() # keep the process alive
|
|
95
|
+
### `Patter` Constructor
|
|
274
96
|
|
|
275
|
-
|
|
97
|
+
```typescript
|
|
98
|
+
new Patter({
|
|
99
|
+
twilioSid: string;
|
|
100
|
+
twilioToken: string;
|
|
101
|
+
openaiKey: string;
|
|
102
|
+
phoneNumber: string;
|
|
103
|
+
webhookUrl?: string; // Optional; auto-tunneled via Cloudflare if omitted
|
|
104
|
+
})
|
|
276
105
|
```
|
|
277
106
|
|
|
278
|
-
|
|
107
|
+
| Parameter | Type | Description |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `twilioSid` | `string` | Twilio account SID |
|
|
110
|
+
| `twilioToken` | `string` | Twilio auth token |
|
|
111
|
+
| `openaiKey` | `string` | OpenAI API key |
|
|
112
|
+
| `phoneNumber` | `string` | Your Twilio phone number (E.164 format) |
|
|
113
|
+
| `webhookUrl` | `string` | Public URL for Twilio webhooks (optional) |
|
|
279
114
|
|
|
280
|
-
|
|
281
|
-
<summary><strong>TypeScript</strong></summary>
|
|
115
|
+
### `phone.agent()` Method
|
|
282
116
|
|
|
283
117
|
```typescript
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return "We're open Monday through Friday, 9 to 5.";
|
|
292
|
-
}
|
|
293
|
-
return "How can I help you today?";
|
|
294
|
-
},
|
|
295
|
-
onCallStart: (data) => console.log(`Call from ${data.caller}`),
|
|
296
|
-
onCallEnd: () => console.log("Call ended"),
|
|
297
|
-
});
|
|
118
|
+
phone.agent({
|
|
119
|
+
systemPrompt: string;
|
|
120
|
+
voice?: string;
|
|
121
|
+
firstMessage?: string;
|
|
122
|
+
variables?: Record<string, string>;
|
|
123
|
+
tools?: Array<{name, description, parameters, webhookUrl}>;
|
|
124
|
+
})
|
|
298
125
|
```
|
|
299
126
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
<summary><strong>Python</strong></summary>
|
|
308
|
-
|
|
309
|
-
```python
|
|
310
|
-
import asyncio
|
|
311
|
-
from patter import Patter, IncomingMessage
|
|
312
|
-
|
|
313
|
-
async def agent(msg: IncomingMessage) -> str:
|
|
314
|
-
return "Thanks for picking up. This is a reminder about your appointment tomorrow."
|
|
127
|
+
| Parameter | Type | Description |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `systemPrompt` | `string` | Prompt with optional `{variable}` placeholders |
|
|
130
|
+
| `voice` | `string` | TTS voice name (e.g., "alloy", "echo", "fable") |
|
|
131
|
+
| `firstMessage` | `string` | Opening message (supports `{variable}` placeholders) |
|
|
132
|
+
| `variables` | `Record<string, string>` | Values substituted into prompts |
|
|
133
|
+
| `tools` | `Array` | Tool definitions: `{name, description, parameters, webhookUrl}` |
|
|
315
134
|
|
|
316
|
-
|
|
317
|
-
phone = Patter(api_key="pt_xxx")
|
|
318
|
-
await phone.connect(on_message=agent)
|
|
319
|
-
await phone.call(
|
|
320
|
-
to="+14155551234",
|
|
321
|
-
first_message="Hi, this is an automated reminder from Acme Corp.",
|
|
322
|
-
)
|
|
135
|
+
### `phone.serve()` Method
|
|
323
136
|
|
|
324
|
-
|
|
137
|
+
```typescript
|
|
138
|
+
await phone.serve({
|
|
139
|
+
agent: Agent;
|
|
140
|
+
port?: number;
|
|
141
|
+
dashboard?: boolean;
|
|
142
|
+
recording?: boolean;
|
|
143
|
+
onCallStart?: (data: CallData) => Promise<void>;
|
|
144
|
+
onCallEnd?: (data: CallData) => Promise<void>;
|
|
145
|
+
onTranscript?: (data: TranscriptData) => Promise<void>;
|
|
146
|
+
})
|
|
325
147
|
```
|
|
326
148
|
|
|
327
|
-
|
|
149
|
+
| Parameter | Type | Description |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `agent` | `Agent` | Agent configuration to use for calls |
|
|
152
|
+
| `port` | `number` | Port to listen on (default: 8000) |
|
|
153
|
+
| `dashboard` | `boolean` | Enable the built-in monitoring dashboard |
|
|
154
|
+
| `recording` | `boolean` | Enable call recording via the telephony provider |
|
|
155
|
+
| `onCallStart` | `(data) => Promise<void>` | Called when a call connects; receives `data.caller`, `data.callId` |
|
|
156
|
+
| `onCallEnd` | `(data) => Promise<void>` | Called when a call ends; receives `data.history`, `data.transcript`, `data.duration` |
|
|
157
|
+
| `onTranscript` | `(data) => Promise<void>` | Called on each transcript turn; receives `data.role`, `data.text`, `data.history` |
|
|
328
158
|
|
|
329
|
-
|
|
330
|
-
<summary><strong>TypeScript</strong></summary>
|
|
159
|
+
### `phone.call()` Method
|
|
331
160
|
|
|
332
161
|
```typescript
|
|
333
|
-
import { Patter } from "getpatter";
|
|
334
|
-
|
|
335
|
-
const phone = new Patter({ apiKey: "pt_xxx" });
|
|
336
|
-
|
|
337
|
-
await phone.connect({
|
|
338
|
-
onMessage: async () =>
|
|
339
|
-
"Thanks for picking up. This is a reminder about your appointment tomorrow.",
|
|
340
|
-
});
|
|
341
|
-
|
|
342
162
|
await phone.call({
|
|
343
|
-
to:
|
|
344
|
-
firstMessage
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
</details>
|
|
349
|
-
|
|
350
|
-
---
|
|
351
|
-
|
|
352
|
-
### Outbound with Machine Detection + Voicemail Drop
|
|
353
|
-
|
|
354
|
-
<details open>
|
|
355
|
-
<summary><strong>Python</strong></summary>
|
|
356
|
-
|
|
357
|
-
```python
|
|
358
|
-
await phone.call(
|
|
359
|
-
to="+14155551234",
|
|
360
|
-
first_message="Hi, this is an automated reminder from Acme Corp.",
|
|
361
|
-
machine_detection=True,
|
|
362
|
-
voicemail_message="Hi, we tried to reach you. Please call us back at 555-0123.",
|
|
363
|
-
)
|
|
163
|
+
to: string;
|
|
164
|
+
firstMessage?: string;
|
|
165
|
+
machineDetection?: boolean;
|
|
166
|
+
voicemailMessage?: string;
|
|
167
|
+
})
|
|
364
168
|
```
|
|
365
169
|
|
|
366
|
-
|
|
170
|
+
| Parameter | Type | Description |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `to` | `string` | Destination phone number (E.164 format) |
|
|
173
|
+
| `firstMessage` | `string` | Opening message for the outbound call |
|
|
174
|
+
| `machineDetection` | `boolean` | Enable answering machine detection |
|
|
175
|
+
| `voicemailMessage` | `string` | Message to play when voicemail is detected |
|
|
367
176
|
|
|
368
|
-
|
|
369
|
-
<summary><strong>TypeScript</strong></summary>
|
|
177
|
+
### Static Provider Helpers
|
|
370
178
|
|
|
371
179
|
```typescript
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
voicemailMessage: "Hi, we tried to reach you. Please call us back at 555-0123.",
|
|
377
|
-
});
|
|
180
|
+
Patter.deepgram(options: { apiKey: string; language?: string }) -> STT
|
|
181
|
+
Patter.elevenlabs(options: { apiKey: string; voice?: string }) -> TTS
|
|
182
|
+
Patter.openaiTts(options: { apiKey: string; voice?: string }) -> TTS
|
|
183
|
+
Patter.whisper(options: { apiKey: string; language?: string }) -> STT
|
|
378
184
|
```
|
|
379
185
|
|
|
380
|
-
|
|
186
|
+
## Examples
|
|
381
187
|
|
|
382
|
-
|
|
188
|
+
### Inbound Calls (AI answers the phone)
|
|
383
189
|
|
|
384
|
-
|
|
190
|
+
```typescript
|
|
191
|
+
import { Patter, IncomingMessage } from "getpatter";
|
|
385
192
|
|
|
386
|
-
|
|
193
|
+
const phone = new Patter({
|
|
194
|
+
twilioSid: "AC...", twilioToken: "...",
|
|
195
|
+
openaiKey: "sk-...",
|
|
196
|
+
phoneNumber: "+1...",
|
|
197
|
+
});
|
|
387
198
|
|
|
388
|
-
<
|
|
389
|
-
|
|
199
|
+
async function agent(msg: IncomingMessage): Promise<string> {
|
|
200
|
+
if (msg.text.toLowerCase().includes("hours")) {
|
|
201
|
+
return "We're open Monday through Friday, 9 to 5.";
|
|
202
|
+
}
|
|
203
|
+
return "How can I help you today?";
|
|
204
|
+
}
|
|
390
205
|
|
|
391
|
-
|
|
392
|
-
agent
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
)
|
|
206
|
+
await phone.serve({
|
|
207
|
+
agent: phone.agent({
|
|
208
|
+
systemPrompt: "You are a helpful customer service agent.",
|
|
209
|
+
firstMessage: "Hello! How can I help?",
|
|
210
|
+
}),
|
|
211
|
+
port: 8000,
|
|
212
|
+
onCallStart: (data) => console.log(`Call from ${data.caller}`),
|
|
213
|
+
onCallEnd: (data) => console.log("Call ended"),
|
|
214
|
+
});
|
|
400
215
|
```
|
|
401
216
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
<details>
|
|
405
|
-
<summary><strong>TypeScript</strong></summary>
|
|
217
|
+
### Outbound Calls (AI calls someone)
|
|
406
218
|
|
|
407
219
|
```typescript
|
|
408
|
-
|
|
409
|
-
systemPrompt: "You are helping {customer_name}, account #{account_id}.",
|
|
410
|
-
firstMessage: "Hi {customer_name}! How can I help you today?",
|
|
411
|
-
variables: {
|
|
412
|
-
customer_name: "Jane",
|
|
413
|
-
account_id: "A-789",
|
|
414
|
-
},
|
|
415
|
-
});
|
|
416
|
-
```
|
|
220
|
+
import { Patter } from "getpatter";
|
|
417
221
|
|
|
418
|
-
|
|
222
|
+
const phone = new Patter({
|
|
223
|
+
twilioSid: "AC...", twilioToken: "...",
|
|
224
|
+
openaiKey: "sk-...",
|
|
225
|
+
phoneNumber: "+1...",
|
|
226
|
+
});
|
|
419
227
|
|
|
420
|
-
|
|
228
|
+
const agentConfig = phone.agent({
|
|
229
|
+
systemPrompt: "You are making reminder calls.",
|
|
230
|
+
firstMessage: "Hi, this is an automated reminder from Acme Corp.",
|
|
231
|
+
});
|
|
421
232
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
<summary><strong>Python</strong></summary>
|
|
428
|
-
|
|
429
|
-
```python
|
|
430
|
-
agent = phone.agent(
|
|
431
|
-
system_prompt="You are a booking assistant. Check availability before confirming.",
|
|
432
|
-
tools=[{
|
|
433
|
-
"name": "check_availability",
|
|
434
|
-
"description": "Check appointment availability for a given date",
|
|
435
|
-
"parameters": {
|
|
436
|
-
"type": "object",
|
|
437
|
-
"properties": {
|
|
438
|
-
"date": {"type": "string", "description": "ISO date, e.g. 2025-06-15"},
|
|
439
|
-
},
|
|
440
|
-
"required": ["date"],
|
|
441
|
-
},
|
|
442
|
-
"webhook_url": "https://api.example.com/availability",
|
|
443
|
-
}],
|
|
444
|
-
)
|
|
233
|
+
await phone.serve({ agent: agentConfig, port: 8000 });
|
|
234
|
+
await phone.call({
|
|
235
|
+
to: "+14155551234",
|
|
236
|
+
firstMessage: "Hi, just checking in.",
|
|
237
|
+
});
|
|
445
238
|
```
|
|
446
239
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
<details>
|
|
450
|
-
<summary><strong>TypeScript</strong></summary>
|
|
240
|
+
### Tool Calling (Agent calls external APIs)
|
|
451
241
|
|
|
452
242
|
```typescript
|
|
453
243
|
const agent = phone.agent({
|
|
@@ -467,254 +257,62 @@ const agent = phone.agent({
|
|
|
467
257
|
});
|
|
468
258
|
```
|
|
469
259
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
---
|
|
473
|
-
|
|
474
|
-
### Built-in Tools: Transfer & End Call
|
|
475
|
-
|
|
476
|
-
`transfer_call` and `end_call` are automatically injected into every agent — no configuration needed.
|
|
477
|
-
|
|
478
|
-
- The agent calls `transfer_call` when it decides to route to a human (e.g. "Let me transfer you now.")
|
|
479
|
-
- The agent calls `end_call` when the conversation is complete (e.g. after a confirmed booking.)
|
|
480
|
-
|
|
481
|
-
---
|
|
482
|
-
|
|
483
|
-
### Call Recording
|
|
484
|
-
|
|
485
|
-
<details open>
|
|
486
|
-
<summary><strong>Python</strong></summary>
|
|
487
|
-
|
|
488
|
-
```python
|
|
489
|
-
await phone.serve(agent=agent, port=8000, recording=True)
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
</details>
|
|
493
|
-
|
|
494
|
-
<details>
|
|
495
|
-
<summary><strong>TypeScript</strong></summary>
|
|
260
|
+
### Custom Voice (Deepgram STT + ElevenLabs TTS)
|
|
496
261
|
|
|
497
262
|
```typescript
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
---
|
|
504
|
-
|
|
505
|
-
### Conversation History
|
|
506
|
-
|
|
507
|
-
Every callback receives `data.history` — the full conversation so far as a list of `{role, text}` turns.
|
|
263
|
+
const phone = new Patter({
|
|
264
|
+
twilioSid: "AC...", twilioToken: "...",
|
|
265
|
+
openaiKey: "sk-...",
|
|
266
|
+
phoneNumber: "+1...",
|
|
267
|
+
});
|
|
508
268
|
|
|
509
|
-
|
|
510
|
-
|
|
269
|
+
const agent = phone.agent({
|
|
270
|
+
systemPrompt: "You are a helpful voice assistant.",
|
|
271
|
+
voice: "aria",
|
|
272
|
+
});
|
|
511
273
|
|
|
512
|
-
|
|
513
|
-
await phone.serve(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
)
|
|
274
|
+
// Use custom STT and TTS in pipeline mode
|
|
275
|
+
await phone.serve({
|
|
276
|
+
agent,
|
|
277
|
+
port: 8000,
|
|
278
|
+
stt: Patter.deepgram({ apiKey: "dg_...", language: "en" }),
|
|
279
|
+
tts: Patter.elevenlabs({ apiKey: "el_...", voice: "aria" }),
|
|
280
|
+
});
|
|
519
281
|
```
|
|
520
282
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
<details>
|
|
524
|
-
<summary><strong>TypeScript</strong></summary>
|
|
283
|
+
### Call Recording
|
|
525
284
|
|
|
526
285
|
```typescript
|
|
527
286
|
await phone.serve({
|
|
528
287
|
agent,
|
|
529
288
|
port: 8000,
|
|
530
|
-
|
|
531
|
-
onCallEnd: (data) => console.log(`Full history:`, data.history),
|
|
289
|
+
recording: true, // Records all inbound and outbound calls
|
|
532
290
|
});
|
|
533
291
|
```
|
|
534
292
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
---
|
|
538
|
-
|
|
539
|
-
### Custom Voice (choose your providers)
|
|
540
|
-
|
|
541
|
-
<details open>
|
|
542
|
-
<summary><strong>Python</strong></summary>
|
|
543
|
-
|
|
544
|
-
```python
|
|
545
|
-
phone = Patter(api_key="pt_xxx", backend_url="ws://localhost:8000")
|
|
546
|
-
|
|
547
|
-
await phone.connect(
|
|
548
|
-
on_message=agent,
|
|
549
|
-
provider="twilio",
|
|
550
|
-
provider_key="ACxxxxxxxx",
|
|
551
|
-
provider_secret="your_auth_token",
|
|
552
|
-
number="+14155550000",
|
|
553
|
-
stt=Patter.deepgram(api_key="dg_xxx", language="en"),
|
|
554
|
-
tts=Patter.elevenlabs(api_key="el_xxx", voice="rachel"),
|
|
555
|
-
)
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
</details>
|
|
559
|
-
|
|
560
|
-
<details>
|
|
561
|
-
<summary><strong>TypeScript</strong></summary>
|
|
293
|
+
### Dynamic Variables in Prompts
|
|
562
294
|
|
|
563
295
|
```typescript
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
number: "+14155550000",
|
|
572
|
-
stt: Patter.deepgram({ apiKey: "dg_xxx", language: "en" }),
|
|
573
|
-
tts: Patter.elevenlabs({ apiKey: "el_xxx", voice: "rachel" }),
|
|
296
|
+
const agent = phone.agent({
|
|
297
|
+
systemPrompt: "You are helping {customer_name}, account #{account_id}.",
|
|
298
|
+
firstMessage: "Hi {customer_name}! How can I help you today?",
|
|
299
|
+
variables: {
|
|
300
|
+
customer_name: "Jane",
|
|
301
|
+
account_id: "A-789",
|
|
302
|
+
},
|
|
574
303
|
});
|
|
575
304
|
```
|
|
576
305
|
|
|
577
|
-
</details>
|
|
578
|
-
|
|
579
|
-
## Voice Modes
|
|
580
|
-
|
|
581
|
-
| Mode | Latency | Quality | Best For |
|
|
582
|
-
|---|---|---|---|
|
|
583
|
-
| **OpenAI Realtime** | Lowest | High | Fluid, low-latency conversations |
|
|
584
|
-
| **Deepgram + ElevenLabs** | Low | High | Independent control over STT and TTS |
|
|
585
|
-
| **ElevenLabs ConvAI** | Low | High | ElevenLabs-managed conversation flow |
|
|
586
|
-
|
|
587
|
-
The voice mode is configured on the backend. Your `on_message` handler works identically regardless of mode.
|
|
588
|
-
|
|
589
|
-
## MCP Server (Claude Desktop)
|
|
590
|
-
|
|
591
|
-
Patter ships an MCP server so you can control calls directly from Claude Desktop.
|
|
592
|
-
|
|
593
|
-
```json
|
|
594
|
-
{
|
|
595
|
-
"mcpServers": {
|
|
596
|
-
"patter": {
|
|
597
|
-
"command": "patter-mcp",
|
|
598
|
-
"env": { "PATTER_API_KEY": "pt_xxx" }
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
## Self-Hosting
|
|
605
|
-
|
|
606
|
-
Run the full stack yourself — no Patter Cloud account needed.
|
|
607
|
-
|
|
608
|
-
```bash
|
|
609
|
-
# 1. Clone the repo
|
|
610
|
-
git clone https://github.com/your-org/patter
|
|
611
|
-
cd patter
|
|
612
|
-
|
|
613
|
-
# 2. Copy env and fill in your keys
|
|
614
|
-
cp .env.example .env
|
|
615
|
-
|
|
616
|
-
# 3. Start the backend
|
|
617
|
-
cd backend
|
|
618
|
-
pip install -e ".[dev]"
|
|
619
|
-
alembic upgrade head
|
|
620
|
-
uvicorn app.main:app --reload
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
Then point your SDK at your local backend:
|
|
624
|
-
|
|
625
|
-
```python
|
|
626
|
-
phone = Patter(
|
|
627
|
-
api_key="pt_xxx",
|
|
628
|
-
backend_url="ws://localhost:8000",
|
|
629
|
-
rest_url="http://localhost:8000",
|
|
630
|
-
)
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
**Required environment variables:**
|
|
634
|
-
|
|
635
|
-
| Variable | Description |
|
|
636
|
-
|---|---|
|
|
637
|
-
| `PATTER_DATABASE_URL` | PostgreSQL connection string |
|
|
638
|
-
| `PATTER_ENCRYPTION_KEY` | Key for encrypting stored provider credentials |
|
|
639
|
-
| `PATTER_SECRET_KEY` | JWT / HMAC signing secret |
|
|
640
|
-
|
|
641
|
-
See `backend/.env.example` for the full list.
|
|
642
|
-
|
|
643
|
-
## API Reference
|
|
644
|
-
|
|
645
|
-
### `Patter` (Python & TypeScript)
|
|
646
|
-
|
|
647
|
-
| Method | Description |
|
|
648
|
-
|---|---|
|
|
649
|
-
| `Patter(api_key, backend_url?, rest_url?)` | Create client. `backend_url` defaults to `wss://api.getpatter.com`. |
|
|
650
|
-
| `connect(on_message, ...)` | Connect and start receiving calls. Blocks until disconnected. |
|
|
651
|
-
| `call(to, first_message?, machine_detection?, voicemail_message?, ...)` | Place an outbound call. |
|
|
652
|
-
| `disconnect()` | Gracefully close the connection. |
|
|
653
|
-
|
|
654
|
-
**`serve()` options:**
|
|
655
|
-
|
|
656
|
-
| Option | Type | Description |
|
|
657
|
-
|---|---|---|
|
|
658
|
-
| `agent` | `Agent` | Agent configuration to use for calls |
|
|
659
|
-
| `port` | `int` | Port to listen on |
|
|
660
|
-
| `recording` | `bool` | Enable call recording via the telephony provider |
|
|
661
|
-
| `onCallStart` | `callable` | Called when a call connects; receives `data.caller`, `data.call_id` |
|
|
662
|
-
| `onCallEnd` | `callable` | Called when a call ends; receives `data.history`, `data.transcript`, `data.duration` |
|
|
663
|
-
| `onTranscript` | `callable` | Called on each transcript turn; receives `data.role`, `data.text`, `data.history` |
|
|
664
|
-
|
|
665
|
-
**`agent()` options:**
|
|
666
|
-
|
|
667
|
-
| Option | Type | Description |
|
|
668
|
-
|---|---|---|
|
|
669
|
-
| `system_prompt` | `str` | Prompt with optional `{variable}` placeholders |
|
|
670
|
-
| `variables` | `dict` | Values substituted into `system_prompt` and `first_message` |
|
|
671
|
-
| `voice` | `str` | TTS voice name |
|
|
672
|
-
| `language` | `str` | BCP-47 language code |
|
|
673
|
-
| `first_message` | `str` | Opening message (supports `{variable}` placeholders) |
|
|
674
|
-
| `tools` | `list` | Tool definitions with `name`, `description`, `parameters`, `webhook_url` |
|
|
675
|
-
|
|
676
|
-
**`call()` options:**
|
|
677
|
-
|
|
678
|
-
| Option | Type | Description |
|
|
679
|
-
|---|---|---|
|
|
680
|
-
| `to` | `str` | Destination phone number |
|
|
681
|
-
| `first_message` | `str` | Opening message for the outbound call |
|
|
682
|
-
| `machine_detection` | `bool` | Enable answering machine detection |
|
|
683
|
-
| `voicemail_message` | `str` | Message to play when voicemail is detected |
|
|
684
|
-
|
|
685
|
-
**Static provider helpers** (for self-hosted mode):
|
|
686
|
-
|
|
687
|
-
| Helper | Type | Description |
|
|
688
|
-
|---|---|---|
|
|
689
|
-
| `Patter.deepgram(api_key, language?)` | STT | Deepgram Nova |
|
|
690
|
-
| `Patter.whisper(api_key, language?)` | STT | OpenAI Whisper |
|
|
691
|
-
| `Patter.elevenlabs(api_key, voice?)` | TTS | ElevenLabs |
|
|
692
|
-
| `Patter.openai_tts(api_key, voice?)` | TTS | OpenAI TTS |
|
|
693
|
-
|
|
694
|
-
### `IncomingMessage`
|
|
695
|
-
|
|
696
|
-
| Field | Type | Description |
|
|
697
|
-
|---|---|---|
|
|
698
|
-
| `text` | `str` | Transcribed speech from the caller (includes `[DTMF: N]` for keypad presses) |
|
|
699
|
-
| `call_id` | `str` | Unique identifier for the current call |
|
|
700
|
-
| `history` | `list` | Conversation turns so far: `[{role, text}, ...]` |
|
|
701
|
-
|
|
702
306
|
## Contributing
|
|
703
307
|
|
|
704
308
|
Pull requests are welcome.
|
|
705
309
|
|
|
706
310
|
```bash
|
|
707
|
-
|
|
708
|
-
cd backend && pytest tests/ -v
|
|
709
|
-
cd sdk && pytest tests/ -v
|
|
710
|
-
|
|
711
|
-
# Install dev dependencies
|
|
712
|
-
cd backend && pip install -e ".[dev]"
|
|
713
|
-
cd sdk && pip install -e ".[dev]"
|
|
311
|
+
cd sdk-ts && npm install && npm test
|
|
714
312
|
```
|
|
715
313
|
|
|
716
314
|
Please open an issue before submitting large changes so we can discuss the approach first.
|
|
717
315
|
|
|
718
316
|
## License
|
|
719
317
|
|
|
720
|
-
MIT — see [LICENSE](
|
|
318
|
+
MIT — see [LICENSE](../LICENSE).
|