{"slug": "build-an-ai-phone-story-hotline-interactive-storytelling-with-telnyx-voice-ai", "title": "Build an AI Phone Story Hotline – Interactive Storytelling with Telnyx Voice AI", "summary": "Telnyx released an open-source Python app that turns a phone number into an AI-powered interactive storytelling hotline, where callers choose a genre and make choices via keypad or voice to shape a real-time narrated story generated by Llama 3.3 70B. The 104-line app demonstrates creative AI over voice and a reusable pattern for conversational AI applications.", "body_md": "Imagine calling a phone number, picking a genre — mystery, sci-fi, fantasy, horror, or romance — and listening to an AI narrate a story that adapts to your choices in real time. Press 1 to open the creaking door. Press 2 to check the window. The story branches, the AI continues, and every call is a new adventure.\n\nThis is the **AI Phone Story Hotline** — a 104-line Python app built with Telnyx Call Control and AI Inference. No game engine, no branching script, no pre-written dialogue trees. The AI generates the story as you go, and your phone keypad shapes what happens next.\n\nIn this walkthrough, you'll build it from scratch. Clone the repo, configure a phone number, and deploy in minutes.\n\n## What You'll Build\n\nA phone number that anyone can call to start an interactive story:\n\n**Caller dials in**— Telnyx answers and greets them with a genre menu** Caller picks a genre**— press 1–5 for mystery, sci-fi, fantasy, horror, or romance** AI generates chapter one**— a 3–4 sentence story segment ending with two choices** Caller chooses**— press 1 or 2, or speak the choice aloud** Story continues**— AI generates the next chapter based on the choice, keeping full conversation context** After 5 chapters**— the AI brings the story to a satisfying ending\n\nThe whole interaction is voice-driven: Text-to-Speech reads each chapter, and the caller responds with DTMF keypresses or speech. The AI model (Llama 3.3 70B via Telnyx AI Inference) handles the storytelling, and Call Control handles the telephony.\n\n## Why This Is Interesting\n\nMost AI phone demos are business use cases — book a table, qualify a lead, reset a password. This one is different. It's **creative AI** — the model is writing fiction in real time, branching based on human input, and delivering it over a phone call. The storytelling format (short chapters, two choices each) keeps the AI responses tight and the call engaging.\n\nIt also demonstrates a pattern that works for any conversational AI app: webhook-driven state machine + LLM with conversation memory + voice I/O. Once you see how the story hotline works, you can swap the storytelling prompt for any domain — a choose-your-own-adventure onboarding flow, an interactive quiz, a branching training simulation.\n\n## Prerequisites\n\n- Python 3.8+\n- A\n[Telnyx account](https://portal.telnyx.com)with funded balance - A\n[Telnyx API key](https://portal.telnyx.com/api-keys) - A\n[Telnyx phone number](https://portal.telnyx.com/numbers/my-numbers)with voice enabled - A\n[Call Control Application](https://portal.telnyx.com/call-control/applications)configured with your webhook URL [ngrok](https://ngrok.com)for exposing your local server to Telnyx webhooks\n\n## The Architecture\n\n```\n  Phone Call\n      │\n      ▼\n  Telnyx Call Control (webhook events)\n      │\n      ▼\n  Flask app (app.py, 104 lines)\n      │\n      ├──► call.initiated  → answer the call\n      ├──► call.answered   → TTS greeting + genre menu\n      ├──► call.gather.ended → DTMF/speech input → AI Inference\n      ├──► call.speak.ended → gather next choice → AI Inference\n      └──► call.hangup     → cleanup session\n      │\n      ▼\n  Telnyx AI Inference (Llama 3.3 70B)\n      │\n      ▼\n  Story chapter (TTS back to caller)\n```\n\nThe app is a state machine driven by Telnyx webhook events. Each event triggers the next action — answer, speak, gather, or hang up. The AI Inference call sits in the middle, generating story chapters from the running conversation history.\n\n## Step 1: Clone and Configure\n\n```\ngit clone https://github.com/team-telnyx/telnyx-code-examples.git\ncd telnyx-code-examples/ai-phone-story-hotline-python\ncp .env.example .env\npip install -r requirements.txt\n```\n\nEdit `.env`\n\nwith your credentials:\n\n```\nTELNYX_API_KEY=KEY0123456789ABCDEF    # from portal.telnyx.com/api-keys\nTELNYX_PUBLIC_KEY=                    # from portal.telnyx.com/api-keys (public key)\nSTORY_NUMBER=+13105551234              # your Telnyx phone number\nAI_MODEL=meta-llama/Llama-3.3-70B-Instruct\n```\n\n## Step 2: Understand the Code\n\nThe entire app is 104 lines in a single file: `app.py`\n\n. Here are the key pieces.\n\n### Webhook Signature Verification\n\nEvery Telnyx webhook is signed with an Ed25519 key. The app verifies the signature before trusting any event:\n\n``` python\n@app.route(\"/webhooks/voice\", methods=[\"POST\"])\ndef handle_voice():\n    try:\n        client.webhooks.unwrap(request.get_data(as_text=True), headers=dict(request.headers))\n    except Exception:\n        return jsonify({\"error\": \"invalid signature\"}), 401\n```\n\nThis prevents spoofed webhook calls from triggering call actions.\n\n### The State Machine\n\nEach call is tracked in an in-memory dict keyed by `call_control_id`\n\n:\n\n```\nactive_calls = {}\n```\n\nThe state machine handles five events:\n\n**Call initiated** — store the call state and answer:\n\n```\nif event_type == \"call.initiated\" and p.get(\"direction\") == \"incoming\":\n    active_calls[ccid] = {\"state\": \"genre_select\", \"conversation\": [], \"chapters\": 0}\n    client.calls.actions.answer(ccid)\n```\n\n**Call answered** — greet the caller with the genre menu using Text-to-Speech:\n\n```\nelif event_type == \"call.answered\":\n    client.calls.actions.speak(ccid,\n        payload=\"Welcome to Story Hotline! Choose your adventure. Press 1 for Mystery, 2 for Sci-Fi, 3 for Fantasy, 4 for Horror, 5 for Romance.\",\n        voice=\"female\", language_code=\"en-US\")\n```\n\n**Speak ended** — after TTS finishes, gather the caller's input. During genre selection, gather DTMF digits. During the story, gather speech or DTMF:\n\n```\nelif event_type == \"call.speak.ended\" and call:\n    if call[\"state\"] == \"genre_select\":\n        client.calls.actions.gather(ccid, input_type=\"dtmf\", timeout_secs=10, min_digits=1, max_digits=1)\n    else:\n        client.calls.actions.gather(ccid, input_type=\"speech dtmf\", end_silence_timeout_secs=3, timeout_secs=20, language_code=\"en-US\")\n```\n\n### The Storytelling Prompt\n\nWhen the caller picks a genre, the app builds the system prompt that guides the AI for the rest of the call:\n\n```\nGENRES = {\"1\": \"mystery\", \"2\": \"sci-fi\", \"3\": \"fantasy\", \"4\": \"horror\", \"5\": \"romance\"}\n\nif call[\"state\"] == \"genre_select\":\n    genre = GENRES.get(digits, \"mystery\")\n    call[\"state\"] = \"story\"\n    call[\"conversation\"] = [{\"role\": \"system\", \"content\":\n        f\"You are an interactive {genre} storyteller on a phone hotline. \"\n        f\"Tell a gripping story in short chapters (3-4 sentences each). \"\n        f\"End each chapter with exactly TWO choices: 'Press 1 to...' or 'Press 2 to...'. \"\n        f\"Make it vivid and cinematic. After 5 chapters, bring the story to a satisfying ending.\"\n    }]\n    story_start = call_inference(call[\"conversation\"] + [{\"role\": \"user\", \"content\": \"Begin the story.\"}])\n    call[\"conversation\"].append({\"role\": \"assistant\", \"content\": story_start})\n    client.calls.actions.speak(ccid, payload=story_start, voice=\"female\", language_code=\"en-US\")\n```\n\nThe prompt does three things:\n\n**Format constraint**— short chapters (3–4 sentences) that fit naturally in a phone call** Choice structure**— exactly two options ending with \"Press 1\" or \"Press 2\" so the gather step can capture DTMF** Ending condition**— after 5 chapters, wrap up the story\n\n### AI Inference\n\nThe `call_inference`\n\nhelper sends the full conversation history to Telnyx AI Inference and returns the model's response:\n\n``` python\ndef call_inference(messages, max_tokens=250):\n    resp = requests.post(INFERENCE_URL,\n        headers={\"Authorization\": f\"Bearer {TELNYX_API_KEY}\", \"Content-Type\": \"application/json\"},\n        json={\"model\": AI_MODEL, \"messages\": messages, \"max_tokens\": max_tokens, \"temperature\": 0.9, },\n        timeout=20)\n    resp.raise_for_status()\n    return resp.json()[\"choices\"][0][\"message\"][\"content\"]\n```\n\nTemperature is set to 0.9 — high enough for creative storytelling, low enough to keep the narrative coherent. The model is Llama 3.3 70B Instruct, available on Telnyx AI Inference with an OpenAI-compatible API.\n\n### The Conversation Loop\n\nEach time the caller makes a choice, the app appends it to the conversation and asks the AI for the next chapter:\n\n```\nelif call[\"state\"] == \"story\":\n    choice = digits or speech\n    call[\"conversation\"].append({\"role\": \"user\", \"content\": f\"I choose option {choice}\"})\n    call[\"chapters\"] += 1\n    if call[\"chapters\"] >= 5:\n        call[\"conversation\"][-1][\"content\"] += \". Bring the story to a dramatic conclusion.\"\n    continuation = call_inference(call[\"conversation\"])\n    call[\"conversation\"].append({\"role\": \"assistant\", \"content\": continuation})\n    client.calls.actions.speak(ccid, payload=continuation, voice=\"female\", language_code=\"en-US\")\n```\n\nAfter 5 chapters, the app injects a final instruction to bring the story to an end. The AI sees the full conversation history on every call, so it maintains narrative continuity across all chapters.\n\n## Step 3: Run the App\n\nStart the Flask server:\n\n```\npython app.py\n```\n\nIn a separate terminal, expose your local server with ngrok:\n\n```\nngrok http 5000\n```\n\nCopy the HTTPS URL and configure it in the [Telnx Portal](https://portal.telnyx.com):\n\n- Go to\n**Call Control Applications** - Create or edit your application\n- Set the Webhook URL to\n`https://<your-ngrok-url>.ngrok.app/webhooks/voice`\n\nAssign your Telnyx phone number to this Call Control Application if you haven't already.\n\n## Step 4: Call and Play\n\nCall your Telnyx number from any phone. You'll hear:\n\n\"Welcome to Story Hotline! Choose your adventure. Press 1 for Mystery, 2 for Sci-Fi, 3 for Fantasy, 4 for Horror, 5 for Romance.\"\n\nPress a key. The AI generates the first chapter and reads it aloud. At the end, you'll hear two choices. Press 1 or 2 — or speak your choice — and the story continues.\n\nAfter 5 chapters, the AI wraps up the story and the call ends.\n\n## Customizing the Experience\n\nThe storytelling system prompt is the control surface. Small changes produce very different experiences:\n\n**Change the genres:**\n\n```\nGENRES = {\"1\": \"noir detective\", \"2\": \"space opera\", \"3\": \"post-apocalyptic\", \"4\": \"gothic horror\", \"5\": \"cozy romance\"}\n```\n\n**Add more choices per chapter:**\n\n```\nf\"End each chapter with exactly THREE choices: 'Press 1 to...', 'Press 2 to...', or 'Press 3 to...'.\"\n```\n\n**Make it a different format — a quiz, a therapy session, a training scenario:**\n\n```\nf\"You are an interactive compliance quiz host on a phone hotline. Ask one multiple-choice question per chapter (3-4 sentences). End with 'Press 1 for A, 2 for B, 3 for C.' After 10 questions, score the caller and give feedback.\"\n```\n\nThe pattern — webhook state machine + LLM with conversation history + voice I/O — works for any branching conversational experience.\n\n## Going to Production\n\nThis example uses in-memory storage for simplicity. For a production deployment:\n\n**Database**— replace the in-memory`active_calls`\n\ndict with Redis or PostgreSQL so call state survives restarts**Concurrency**— run the app behind gunicorn with multiple workers** Error recovery**— handle inference timeouts and call failures gracefully with retry or SMS fallback** Prompt tuning**— test different system prompts and temperature settings for your genre** Rate limiting**— protect your webhook endpoint from abuse** Monitoring**— add structured logging and alerting on call success/failure rates", "url": "https://wpnews.pro/news/build-an-ai-phone-story-hotline-interactive-storytelling-with-telnyx-voice-ai", "canonical_source": "https://lowlatencyclub.ai/blog/posts/ai-phone-story-hotline-python", "published_at": "2026-07-15 10:25:42+00:00", "updated_at": "2026-07-15 10:48:17.187995+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-products", "ai-tools", "generative-ai"], "entities": ["Telnyx", "Llama 3.3 70B", "Telnyx Call Control", "Telnyx AI Inference", "Flask", "ngrok"], "alternates": {"html": "https://wpnews.pro/news/build-an-ai-phone-story-hotline-interactive-storytelling-with-telnyx-voice-ai", "markdown": "https://wpnews.pro/news/build-an-ai-phone-story-hotline-interactive-storytelling-with-telnyx-voice-ai.md", "text": "https://wpnews.pro/news/build-an-ai-phone-story-hotline-interactive-storytelling-with-telnyx-voice-ai.txt", "jsonld": "https://wpnews.pro/news/build-an-ai-phone-story-hotline-interactive-storytelling-with-telnyx-voice-ai.jsonld"}}