Skip to content

add sms verification example#216

Open
vipyne wants to merge 1 commit into
mainfrom
vp-sms-example
Open

add sms verification example#216
vipyne wants to merge 1 commit into
mainfrom
vp-sms-example

Conversation

@vipyne

@vipyne vipyne commented May 28, 2026

Copy link
Copy Markdown
Member

A Pipecat voice agent that sends a 6-digit SMS verification code, asks the
caller to read the digits back, and confirms or rejects the match. Runs in two
modes from a single bot file; Browser mode & Phone mode. Both modes send
an actual SMS message via Twilio.

@markbackman markbackman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Few comments on ways to simplify this code and align with other example patterns.

# ---------------------------------------------------------------------------


async def bot_twilio(websocket: WebSocket) -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than have two entry points, we should have a single bot() method, which is required to be Pipecat Cloud compatible.

Once 1.4.0 is available, you'll be able to replace L292-L337 with:

async def bot(runner_args: RunnerArguments):
    """Main bot entry point."""

    transport_params = {
        "webrtc": lambda: TransportParams(
            audio_in_enabled=True,
            audio_out_enabled=True,
        ),
        # create_transport sets the twilio serializer and add_wav_header automatically.
        "twilio": lambda: FastAPIWebsocketParams(
            audio_in_enabled=True,
            audio_out_enabled=True,
        ),
    }

    transport = await create_transport(runner_args, transport_params)

    await run_bot(transport, runner_args)

if __name__ == "__main__":
    from pipecat.runner.run import main

    main()

Note the main being imported from pipecat.runner.run. This is the pattern in all examples. It's the local development runner, which runs a FastAPI server at port 7860. This does magic which simulates things that Pipecat Cloud does. It also makes local dev much easier. This allows you to remove most (maybe all) of what's in server.py.

Then, you can get access to the caller info via:

call_data = runner_args.call_data
call_info = await get_call_info(call_data.call_id) if call_data else None
if call_info:
    logger.info(f"Call from: {call_info.from_number} to: {call_info.to_number}")

readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"pipecat-ai[websocket,webrtc,cartesia,google,openai,silero,deepgram,runner]>=1.0.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll want 1.4.0 for the simplification I mentioned:

Suggested change
"pipecat-ai[websocket,webrtc,cartesia,google,openai,silero,deepgram,runner]>=1.0.0",
"pipecat-ai[websocket,webrtc,cartesia,google,openai,silero,deepgram,runner]>=1.4.0",

requires-python = ">=3.11"
dependencies = [
"pipecat-ai[websocket,webrtc,cartesia,google,openai,silero,deepgram,runner]>=1.0.0",
"fastapi>=0.115.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify since these are already installed by Pipecat:

    "fastapi>=0.115.0",
    "python-dotenv>=1.0.1",
    "aiohttp>=3.10.0",
    "uvicorn[standard]>=0.32.0",
  • aiohttp is installed by pipecat-ai
  • fastapi is installed by pipecat-ai[websocket]
  • python-dotenv and uvicorn are installed by pipecat-ai[runner]

[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["I"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for conformance, we're using modern Python syntax everywhere now:

Suggested change
select = ["I"]
select = ["I" , "UP"]

return StreamingResponse(event_stream(), media_type="text/event-stream")


# ---------------------------------------------------------------------------

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need this either. It's done by the runner.run. It exposes a /ws endpoint.

app.mount("/static", StaticFiles(directory=CLIENT_DIR), name="static")


@app.get("/api/config")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need this.

app.mount("/static", StaticFiles(directory=CLIENT_DIR), name="static")


@app.get("/api/config")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need this.

# ---------------------------------------------------------------------------


@app.get("/events")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this is, but seems unique to this demo, so you probably need this :)

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.

2 participants