T
Telephony SOPKnowledge Base
Search
← All topics

SWML ai verb — Voice AI Agent Configuration

runnable

The `ai` verb creates a real-time voice AI agent: ASR, LLM, TTS in one block. Covers prompt, params, post_prompt, hints, languages, pronounce, SWAIG, global_data, and the post_prompt_url callback contract.

signalwireswmlai-verbvoice-aipost-prompt
Agent trigger phrases: SWML ai verb · post_prompt_url callback · AI agent prompt config · end_of_speech_timeout · asr_diarize · save_conversation · AI params in SWML

SWML ai Verb

The ai verb spins up an AI voice agent inside a SignalWire call. ASR (automatic speech recognition) + LLM + TTS all in one block, with a function-call layer (SWAIG) for tool use during the conversation.

Minimum viable ai

version: 1.0.0
sections:
  main:
    - answer: {}
    - ai:
        prompt:
          text: |
            You are a friendly receptionist for Acme Plumbing.
            Greet the caller, ask how you can help, and book an appointment.
        post_prompt:
          text: |
            Return JSON only:
            { "caller_intent": string, "appointment_booked": boolean }
        post_prompt_url: "https://your.api/webhooks/post-prompt"

That's the complete shape. Everything else below is optional.

Top-level ai properties

| Property | Type | Notes | |---|---|---| | prompt | object (required) | Persona, goals, instructions. | | post_prompt | object | Final instructions sent after the call ends. Best used to extract structured JSON. | | post_prompt_url | string | Webhook to receive the post-call payload. | | params | object | AI behavior tuning. See params table below. | | languages | object[] | Supported languages and TTS voices. | | hints | string[] or object[] | Boost ASR recognition on specific words. | | pronounce | object[] | Override pronunciation of specific words. | | SWAIG | object | Tool-call functions. See SWAIG functions. | | global_data | object | Session-wide data, accessible via ${global_data.key}. |

prompt configuration

prompt.text is the persona block. LLMs respond well to markdown headers, so structure it.

ai:
  prompt:
    text: |
      ## Role
      You are a receptionist for Acme Plumbing.

      ## Guidelines
      - Be concise.
      - Never quote prices.
      - If unsure, transfer to a human.

      ## Tools
      - book_appointment: Use when caller asks to schedule.
      - transfer_to_human: Use when caller asks for a person.
    temperature: 0.7
    top_p: 0.9
    confidence: 0.6
    presence_penalty: 0.0
    frequency_penalty: 0.0

| Field | Range | Notes | |---|---|---| | temperature | 0.0 - 1.5 | Higher = more random. Default 1.0. | | top_p | 0.0 - 1.0 | Alternative to temperature. Lower = less random. Default 1.0. | | confidence | 0.0 - 1.0 | Speech-detect end-of-utterance threshold. Lower = quicker turn-end but more false positives. | | presence_penalty | -2.0 - 2.0 | Positive = more new topics. | | frequency_penalty | -2.0 - 2.0 | Positive = less repetition. |

params — runtime behavior

Everything in params is optional. Most useful ones:

| Param | Type | Notes | |---|---|---| | end_of_speech_timeout | int (ms) | Silence after caller speech before AI replies. Default 1000. | | attention_timeout | int (ms) | Idle-caller reminder timeout. Default 10000. | | max_speech_timeout | int (ms) | Max single utterance length. Default 30000. | | hard_stop_time | string | Hard cap on session duration, e.g. "30m". | | asr_diarize | bool | Speaker labels in transcript. | | asr_smart_format | bool | Clean number/date formatting in transcripts. | | save_conversation | bool | Auto-send conversation summary to post_prompt_url. | | energy_level | int 0-100 | Mic sensitivity. | | debug_webhook_url | string | Streams each AI turn in real time. | | debug_webhook_level | 0|1|2 | 0 off, 1 basic, 2 verbose. |

languages and hints

ai:
  languages:
    - name: English
      code: en-US
      voice: rime.spore
    - name: Spanish
      code: es-MX
      voice: rime.luna
  hints:
    - SignalWire
    - SWAIG
    - HVAC
    - { hint: "Tony", pattern: "Toni", replace: "Tony", ignore_case: true }
  pronounce:
    - { replace: "GHL", with: "G H L" }
    - { replace: "SWML", with: "swimmel" }

Hint objects let you regex-rewrite ASR mis-hears. Pronounce objects fix TTS mispronunciations.

post_prompt and post_prompt_url — structured extraction

post_prompt runs after the call ends. Use it to coerce a JSON object out of the conversation.

ai:
  post_prompt:
    text: |
      Analyze the call. Return ONLY valid JSON, no prose:
      {
        "sentiment": "positive|neutral|negative",
        "sentiment_score": 0.0,
        "caller_intent": "string",
        "outcome": "sold|not_sold|follow_up|transferred|other",
        "follow_up_required": true,
        "caller_email": "string or null",
        "summary": "2-3 sentence summary"
      }
    temperature: 0.2
  post_prompt_url: "https://your.api/webhooks/post-prompt"

post_prompt_url payload contract

SignalWire POSTs JSON with these key fields:

| Field | Notes | |---|---| | action | Always "post_conversation" | | ai_session_id | UUID for this AI session | | ai_start_date, ai_end_date | Unix timestamps | | call_id | Call ID | | call_start_date, call_answer_date, call_end_date | Unix timestamps | | caller_id_num, caller_id_name | Caller info | | call_log | Full role/content log of the conversation | | post_prompt_data.raw | The AI's full response to post_prompt.text | | post_prompt_data.parsed | If valid JSON was detected, it's parsed here | | swaig_log | Every SWAIG function called during the call |

The handler should look at post_prompt_data.parsed first; fall back to parsing post_prompt_data.raw only if the AI included non-JSON prose.

global_data — session-wide state

ai:
  global_data:
    campaign: spring-sale
    agent_id: vox-001
  prompt:
    text: |
      Campaign code: ${global_data.campaign}.
      Reference number: ${global_data.agent_id}.

SWAIG handlers can mutate it via FunctionResult().update_global_data({...}). The new values are visible to subsequent prompts and SWAIG calls.

Full example — recording + AI + post-prompt extraction

version: 1.0.0
sections:
  main:
    - answer: {}
    - record_call:
        format: mp3
        stereo: true
        status_url: "https://your.api/webhooks/recording"
    - ai:
        prompt:
          text: |
            ## Role
            You are an HVAC dispatcher for Acme HVAC.
            Greet, identify problem, get address, offer same-day or next-day.
          temperature: 0.7
        params:
          end_of_speech_timeout: 700
          asr_diarize: true
          asr_smart_format: true
          save_conversation: true
          hard_stop_time: "20m"
        languages:
          - { name: English, code: en-US, voice: rime.spore }
        post_prompt:
          text: |
            Return ONLY JSON:
            { "intent": string, "address": string, "scheduled": boolean, "urgency": "high|normal|low" }
          temperature: 0.2
        post_prompt_url: "https://your.api/webhooks/post-prompt"
        SWAIG:
          defaults:
            web_hook_url: "https://your.api/webhooks/swaig"
          functions:
            - function: book_appointment
              description: Schedule an appointment slot
              parameters:
                type: object
                properties:
                  service: { type: string }
                  date: { type: string }
                  time_window: { type: string }
                required: [service, date, time_window]

Anti-patterns

  • Putting unstructured paragraphs in prompt.text — LLMs follow markdown structure much better.
  • Setting confidence too low (< 0.4) — the agent will cut callers off mid-sentence.
  • Forgetting temperature in post_prompt — leave it ≤ 0.3 to get reliable JSON.
  • Not validating post_prompt_data.parsed exists before reading — sometimes the AI ignores the JSON instruction. Always fall back to raw.
  • Skipping languages — defaults to a low-quality voice. Always specify.
  • Setting save_conversation: true AND a custom post_prompt that returns structured JSON — the two payloads collide. Pick one strategy.

See also