DEV Community

Harpreet Singh Seehra
Harpreet Singh Seehra

Posted on

Build an AI Price Quote Phone Agent — Real-Time Custom Quotes with Telnyx Voice AI

Imagine a customer calls your business and asks for a quote. Instead of putting them on hold, transferring to sales, or promising a callback, an AI agent picks up — asks 3–4 qualifying questions, builds a line-item quote with quantities and unit prices, and delivers the total on the call. No forms, no waiting, no friction.

This is the AI Price Quote Phone Agent — a 102-line Python app built with Telnyx Call Control and AI Inference. No call center, no CRM integration, no pre-built pricing engine. The AI has your product catalog and pricing, asks the right questions, and generates a structured quote in real time.

In this walkthrough, you'll build it from scratch. Clone the repo, configure a phone number, and deploy in minutes.

What You'll Build

A phone number that anyone can call to get a custom price quote:

  1. Caller dials in — Telnyx answers and greets them
  2. AI asks what they need — "What kind of communication services are you looking for?"
  3. Conversation — caller describes their needs, AI asks 3–4 follow-up questions to estimate quantities
  4. Quote delivered — AI summarizes with line items and monthly total
  5. Structured extraction — on hangup, AI extracts the full quote as JSON (line items, quantities, unit prices, subtotals, monthly total, notes)
  6. Quote stored — accessible via GET /quotes endpoint

The whole interaction is voice-driven: Text-to-Speech reads each question and the final quote, and the caller responds with natural speech. The AI model (Llama 3.3 70B via Telnyx AI Inference) handles the conversation and quote generation, and Call Control handles the telephony.

Why This Is Interesting

Most price quote flows are static: fill out a web form, wait for a sales email. This one is dynamic — a real-time conversation where the AI adapts its questions based on what the caller says. Need voice minutes and SMS? The AI asks about expected volume. Just want a toll-free number? The AI skips irrelevant questions and gets straight to the quote.

It also demonstrates a pattern that goes beyond chat: the AI doesn't just converse — it extracts structured data from the conversation. After the call ends, the app sends the full transcript back to the model with a "extract the quote as JSON" prompt. You get a clean, machine-readable quote object with line items and a monthly total — ready to store, email, or pipe into your billing system.

Prerequisites

The Architecture

  Phone Call
      │
      ▼
  Telnyx Call Control (webhook events)
      │
      ▼
  Flask app (app.py, 102 lines)
      │
      ├──► call.initiated  → answer the call
      ├──► call.answered   → TTS greeting
      ├──► call.gather.ended → speech → AI Inference → TTS response
      ├──► call.speak.ended → gather next input
      └──► call.hangup     → extract quote as JSON → store
      │
      ▼
  Telnyx AI Inference (Llama 3.3 70B)
      │
      ▼
  Structured quote (JSON with line items)
Enter fullscreen mode Exit fullscreen mode

The 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 handles two jobs: conversing with the caller to build the quote, and extracting a structured JSON quote after the call ends.

Step 1: Clone and Configure

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/ai-price-quote-phone-agent-python
cp .env.example .env
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Edit .env with your credentials:

TELNYX_API_KEY=KEY019C8F8C3D268774F8F560946B984D9E_...
TELNYX_PUBLIC_KEY=...
AI_MODEL=meta-llama/Llama-3.3-70B-Instruct
QUOTE_NUMBER=+13105551234
Enter fullscreen mode Exit fullscreen mode

Step 2: Understand the Code

Everything lives in app.py (102 lines). Here's what each piece does.

The Pricing Catalog

The AI has a built-in product catalog with per-unit pricing:

PRICING = {"voice_minutes": 0.005, "sms_messages": 0.004, "local_numbers": 1.00, "toll_free_numbers": 2.00,
    "sip_trunking_channel": 2.50, "ai_inference_1k_tokens": 0.01, "cloud_storage_gb": 0.05, "fax_pages": 0.07}
Enter fullscreen mode Exit fullscreen mode

This gets injected into the system prompt so the AI knows exactly what to charge per unit:

SYSTEM_PROMPT = f"You are a pricing specialist. Available products and per-unit pricing: {json.dumps(PRICING)}. Ask what the caller needs, estimate quantities, build a quote. Keep responses under 2 sentences. After gathering requirements (usually 3-4 questions), summarize the quote with line items and monthly total."
Enter fullscreen mode Exit fullscreen mode

The prompt is the control surface. You can swap the product catalog, change the questioning style, or adjust how many questions the AI asks before summarizing.

Handling Webhooks

The webhook handler is the core state machine. Each Telnyx event triggers the next action:

if event_type == "call.initiated" and p.get("direction") == "incoming":
    active_calls[ccid] = {"caller": p.get("from"), "conversation": [{"role": "system", "content": SYSTEM_PROMPT}], "start": time.time()}
    client.calls.actions.answer(ccid)
elif event_type == "call.answered":
    client.calls.actions.speak(ccid, payload="Hi! I can put together a custom quote for you right now. What kind of communication services are you looking for?", voice="female", language_code="en-US")
elif event_type == "call.speak.ended" and call:
    client.calls.actions.gather(ccid, input_type="speech", end_silence_timeout_secs=2, timeout_secs=20, language_code="en-US")
elif event_type == "call.gather.ended" and call:
    speech = p.get("speech", {}).get("result", "")
    call["conversation"].append({"role": "user", "content": speech})
    response = call_inference(call["conversation"])
    call["conversation"].append({"role": "assistant", "content": response})
    client.calls.actions.speak(ccid, payload=response, voice="female", language_code="en-US")
Enter fullscreen mode Exit fullscreen mode

The conversation loop: speak → gather → infer → speak. Each turn, the AI sees the full conversation history, so it maintains context across all exchanges.

The Inference Helper

def call_inference(messages, max_tokens=250):
    resp = requests.post(INFERENCE_URL, headers={"Authorization": f"Bearer {TELNYX_API_KEY}", "Content-Type": "application/json"},
        json={"model": AI_MODEL, "messages": messages, "max_tokens": max_tokens, "temperature": 0.5}, timeout=15)
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]
Enter fullscreen mode Exit fullscreen mode

Straightforward OpenAI-compatible chat completions call against Telnyx AI Inference. Temperature 0.5 for consistency — you want quotes to be predictable, not creative.

Post-Call Quote Extraction

This is the interesting part. When the caller hangs up, the app sends the full conversation back to the AI with a different prompt — extract the structured quote:

elif event_type == "call.hangup":
    call = active_calls.pop(ccid, None)
    if call and len(call.get("conversation", [])) > 3:
        extract = [{"role": "system", "content": "Extract the price quote. Return JSON: line_items (list of {product, quantity, unit_price, subtotal}), monthly_total (number), notes (string)."},
            {"role": "user", "content": chr(10).join(f"{m['role']}: {m['content']}" for m in call["conversation"] if m["role"] != "system")}]
        quote = json.loads(call_inference(extract, max_tokens=400))
        quote["caller"] = call["caller"]
        quote["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ")
        quotes.append(quote)
Enter fullscreen mode Exit fullscreen mode

The AI returns clean JSON with line items, quantities, unit prices, subtotals, and a monthly total. The app attaches the caller's number and timestamp, then stores it.

Accessing Quotes

@app.route("/quotes", methods=["GET"])
def list_quotes():
    return jsonify({"quotes": quotes[-20:]}), 200
Enter fullscreen mode Exit fullscreen mode

Hit GET /quotes to see the last 20 quotes as JSON — ready to pipe into a CRM, billing system, or analytics dashboard.

Step 3: Run the App

Start the Flask server:

python app.py
Enter fullscreen mode Exit fullscreen mode

In a separate terminal, expose your local server with ngrok:

ngrok http 5000
Enter fullscreen mode Exit fullscreen mode

Copy the HTTPS URL and configure it in the Telnyx Portal:

  • Go to Call Control Applications
  • Create or edit your application
  • Set the Webhook URL to https://<your-ngrok-url>.ngrok.app/webhooks/voice

Assign your Telnyx phone number to this Call Control Application if you haven't already.

Step 4: Call and Get a Quote

Call your Telnyx number from any phone. You'll hear:

"Hi! I can put together a custom quote for you right now. What kind of communication services are you looking for?"

Say something like: "I need about 50 phone numbers, 10,000 voice minutes a month, and a few thousand SMS messages."

The AI asks follow-up questions — how many SMS, any toll-free numbers, do you need SIP trunking — then summarizes the quote with line items and a monthly total.

After you hang up, check the extracted quote:

curl http://localhost:5000/quotes | python3 -m json.tool
Enter fullscreen mode Exit fullscreen mode

You'll see a structured JSON object like:

{
  "line_items": [
    {"product": "local_numbers", "quantity": 50, "unit_price": 1.00, "subtotal": 50.00},
    {"product": "voice_minutes", "quantity": 10000, "unit_price": 0.005, "subtotal": 50.00},
    {"product": "sms_messages", "quantity": 5000, "unit_price": 0.004, "subtotal": 20.00}
  ],
  "monthly_total": 120.00,
  "notes": "Customer indicated potential for higher SMS volume in Q3.",
  "caller": "+12125551234",
  "timestamp": "2026-07-15T14:30:00Z"
}
Enter fullscreen mode Exit fullscreen mode

Customizing the Agent

The pricing catalog and system prompt are the control surface. Small changes produce very different agents:

Swap the product catalog:

PRICING = {"consulting_hour": 200.00, "audit_report": 2500.00, "retainer_monthly": 8000.00, "training_session": 1500.00}
Enter fullscreen mode Exit fullscreen mode

Change the questioning style:

SYSTEM_PROMPT = f"You are a pricing specialist. Available products: {json.dumps(PRICING)}. Ask one question at a time. Be conversational but efficient. After exactly 3 questions, summarize the quote with line items and monthly total. If the caller asks for a discount, explain standard pricing tiers."
Enter fullscreen mode Exit fullscreen mode

Add discount logic:

SYSTEM_PROMPT = f"You are a pricing specialist. Products: {json.dumps(PRICING)}. For volumes over 10,000 units, apply a 15% discount. For over 50,000 units, apply 25%. Mention the discount in your summary."
Enter fullscreen mode Exit fullscreen mode

Send the quote via SMS after the call:

# After extracting the quote, send it as SMS
from telnyx import Message
quote_text = f"Your quote: {quote['monthly_total']}/month. {len(quote['line_items'])} line items."
Message.create(to=call["caller"], from_=QUOTE_NUMBER, text=quote_text)
Enter fullscreen mode Exit fullscreen mode

The pattern — conversational AI with a product catalog + post-call structured extraction — works for any quoting or ordering flow.

Going to Production

This example uses in-memory storage for simplicity. For a production deployment:

  • Database — replace the in-memory quotes list with PostgreSQL or Redis so quotes survive restarts
  • Concurrency — run the app behind gunicorn with multiple workers
  • Error recovery — handle inference timeouts and call failures gracefully with retry or SMS fallback
  • Authentication — add API key validation on the /quotes endpoint
  • Webhook verification — the app already validates Telnyx Ed25519 signatures; ensure your public key is current
  • Prompt tuning — test different system prompts and temperature settings for your product catalog
  • Rate limiting — protect your webhook endpoint from abuse
  • Monitoring — add structured logging and alerting on call success/failure rates
  • CRM integration — pipe extracted quotes into Salesforce, HubSpot, or your billing system

Frequently Asked Questions

Can I use a different AI model? Yes. Telnyx AI Inference is OpenAI-compatible. Set AI_MODEL in .env to any model available on the platform. Llama 3.3 70B Instruct is a good default for structured quoting.

How much does a call cost? Telnyx Call Control is priced per minute. AI Inference is priced per 1K tokens. A typical 4-question quote call uses ~1,500 tokens and ~2 minutes of call time — a few cents per call.

Can multiple people call at the same time? Yes. Each call gets its own entry in active_calls keyed by call_control_id. The app handles concurrent calls without interference.

What happens if the caller doesn't speak? The gather step times out after 20 seconds. The app re-prompts: "Could you tell me more about what you need?"

Is the quote accurate? The AI uses the exact pricing from the PRICING dict in its system prompt. Quantities are estimated from the conversation. For production, you may want to add a confirmation step or validate the extracted JSON against the catalog.

Resources

Top comments (0)