Skip to content

Add outbound-sales example: batch dial-out bot with text-mode evals - #224

Draft
jamsea wants to merge 7 commits into
mainfrom
jh/outbound-sales
Draft

Add outbound-sales example: batch dial-out bot with text-mode evals#224
jamsea wants to merge 7 commits into
mainfrom
jh/outbound-sales

Conversation

@jamsea

@jamsea jamsea commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

SUPER DUPER DRAFTY DRAFT

What this adds

Meet Hailey, an outbound sales agent built with Pipecat. She calls a list of leads in batches of 5 over Daily PSTN, tries to reach the IT decision maker, collects their contact info, and reports the outcome to the webhook server.

Key pieces:

  • server/bot.py: Hailey's pipeline, save_contact_info and end_call tools, dial-out retry logic, canned greeting gate, latency observer
  • server/server.py: FastAPI server; POST /dialout starts calls, POST /call_result records outcomes (in-memory demo store), GET /results is polled by the dialer
  • server/dialer.py: batch dialer, 5 calls at a time, resumable
  • server/scenarios/ + evals.yaml: text-mode Pipecat evals so the bot's behavior can be tested in seconds with no phone calls or audio

Design notes:

  • Call outcomes go to the server over HTTP and live in memory. The README calls out that a real app would use a database; the demo logs each row to the terminal.
  • Audio-only cloud recording starts from the bot's meeting token, so it begins before the phone leg connects and the callee never hears a recording announcement.
  • The first greeting is canned (no LLM round-trip) and spoken via TTSSpeakFrame. In eval mode it's pushed as LLM response frames instead, since text-mode evals only see LLM text events.
  • Smart Turn silence fallback is capped at 1s to keep the call snappy.

How to test

See the README: run server.py and dialer.py in two terminals with a leads.csv, or run the evals with uv run pipecat evals run.

🤖 Generated with Claude Code

jamsea added 7 commits June 12, 2026 14:07
Hailey calls leads from leads.csv in batches of 5 over Daily PSTN, asks
for the IT decision maker, saves contact info to results.csv via a
save_contact_info tool, and hangs up via an end_call tool.

Scaffolded with the new Pipecat CLI (pipecat init --eval). The same
bot.py runs real calls on DailyTransport with full audio, or in
text-only eval mode (-t eval) for fast behavior testing with
'pipecat eval suite evals.yaml' (3 scenarios: happy path, transfer,
refusal; all passing).

Uses pipecat-ai from git main until the 1.4.0 release ships the CLI
and evals on PyPI.
…fixes

- CannedGreetingGate: first reply skips the LLM round-trip; the greeting
  is emitted as LLM-shaped frames so TTS, context, and evals all see a
  normal response (plain TTSSpeakFrame never surfaces in text-mode evals)
- DialoutManager passes callerId; server defaults it from CALLER_ID env
- Default voice: Cartesia Sierra (California Girl)
- Dockerfile installs git for the pipecat git dependency (drop after 1.4.0)
- pcc-deploy.toml: image reference for Pipecat Cloud deploys
…server

PCC call logs showed Smart Turn judging short utterances (a bare
"Hello?") as incomplete and falling back to its 3s silence timeout,
which read as a long pause before Hailey answered. Keep Smart Turn's
don't-interrupt judgement but cap the fallback at 1s, and add
UserBotLatencyObserver so calls log exact user-stop to bot-speak times.
Latency breakdowns showed tool-call turns pay two LLM TTFBs (the call,
then the spoken reply). Speak a short filler on on_function_calls_started
for save_contact_info to cover the gap. Tried gpt-4.1-mini for the
~0.3s faster TTFB but the eval suite rejected it (missed end_call tool
calls in 2 of 6 runs), so the model stays gpt-4.1.
The dial-out room is created with enable_recording="cloud" and the bot
starts the recording on on_dialout_answered. Recordings are listed and
downloaded afterwards via Daily's REST API (/recordings + access-link).
Downloaded files land in server/recordings/ (gitignored).
Replace the shared results.csv file with a POST /call_result endpoint on
server.py that logs each outcome and keeps it in memory; the dialer polls
GET /results. This matches how a real deployment works (bots in separate
containers can't share a file) and drops results.py entirely.

Also: start audio-only cloud recording from the bot's meeting token so it
begins before the phone leg connects, speak the canned greeting via
TTSSpeakFrame (LLM-response frames only in eval mode so the harness can
see it), add deploy notes to pcc-deploy.toml, and add a CLAUDE.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant