Skip to main content
Before you can create a Voice Agent, you need to know which AI models and voices are available in your organization’s Lyzr workspace. In this scenario, we are building Nexus, a Level-1 Tech Support Agent. We need to find a fast LLM for reasoning and a clear, professional voice for speaking. We will use the Lyzr Configuration APIs to discover our options.

The Discovery Workflow

Our script will perform four tasks:
  1. Fetch Pipeline Options: Get a list of supported LLM and Speech-to-Text (STT) models.
  2. Fetch Active TTS Providers: Find out which Text-to-Speech engines (like ElevenLabs or Cartesia) are configured.
  3. Query Voices: Search the active TTS provider for a list of available voice clones.
  4. Generate a Preview: Get the URL to stream a sample of the chosen voice.

Runnable Python Script

You can copy and run this script directly in your terminal. Ensure you have the requests library installed (pip install requests) and replace "YOUR_API_KEY_HERE" with your actual Lyzr API Key.
import requests
import os

# Setup your credentials
API_KEY = os.getenv("LYZR_API_KEY", "YOUR_API_KEY_HERE")
BASE_URL = "[https://voice-livekit.studio.lyzr.ai/v1](https://voice-livekit.studio.lyzr.ai/v1)"

headers = {
    "x-api-key": API_KEY,
    "accept": "application/json"
}

def run_phase_1_discovery():
    print("--- Phase 1: Voice Agent Discovery ---\n")
    
    # 1. Check Pipeline Options (LLMs and STT)
    print("1. Fetching Pipeline Options...")
    resp = requests.get(f"{BASE_URL}/config/pipeline-options", headers=headers)
    resp.raise_for_status() # Will raise an error if the request fails
    
    pipeline_data = resp.json()
    
    # Extract the first available LLM for our Nexus agent
    llm_provider = pipeline_data.get("llm", [])[0]["providerId"]
    llm_model = pipeline_data.get("llm", [])[0]["models"][0]["id"]
    print(f"   -> Selected LLM: {llm_provider} ({llm_model})\n")

    # 2. Check TTS Voice Providers
    print("2. Fetching Active TTS Voice Providers...")
    resp = requests.get(f"{BASE_URL}/config/tts-voice-providers", headers=headers)
    resp.raise_for_status()
        
    tts_providers = resp.json().get("providers", [])
    # Find the first provider that is actively configured (defaulting to elevenlabs)
    active_provider = next((p["providerId"] for p in tts_providers if p.get("configured")), "elevenlabs")
    print(f"   -> Selected Active TTS Provider: {active_provider}\n")

    # 3. Fetch Voices for the selected TTS provider
    print(f"3. Fetching top 5 voices for '{active_provider}'...")
    resp = requests.get(f"{BASE_URL}/config/tts-voices?providerId={active_provider}&limit=5", headers=headers)
    resp.raise_for_status()
        
    voices_data = resp.json()
    voices = voices_data.get("voices", [])
    
    if not voices:
        print("   -> No voices found. Please check your provider configuration.")
        return
        
    selected_voice = voices[0] # Picking the first voice for Nexus
    voice_id = selected_voice.get("id")
    preview_url = selected_voice.get("previewUrl", "")
    print(f"   -> Selected Voice ID: '{voice_id}' (Name: {selected_voice.get('name')})\n")

    # 4. Generate the audio preview URL
    if preview_url:
        print("4. Generating Audio Preview Stream...")
        # We proxy the preview through the Lyzr API
        preview_stream_url = f"{BASE_URL}/config/tts-voice-preview?providerId={active_provider}&url={preview_url}"
        print(f"   -> Preview URL generated! (Note: Pass your x-api-key header to stream this URL)")
        
    print("\n✅ Phase 1 Complete! Save these IDs. We will use them to build Nexus in Phase 2.")

if __name__ == "__main__":
    if API_KEY == "YOUR_API_KEY_HERE":
        print("⚠️ Please replace 'YOUR_API_KEY_HERE' with your actual Lyzr API key to run this script.")
    else:
        try:
            run_phase_1_discovery()
        except requests.exceptions.RequestException as e:
            print(f"❌ API Request Failed: {e}")

Building the Nexus Voice Agent

Learn how to construct a complete Voice Agent payload, including custom LLM pipelines and immersive background audio. Now, it’s time to bring Nexus (our Level-1 Tech Support Voice Agent) to life using the POST /agents endpoint.

Deconstructing the Voice Agent Payload

To successfully create a Voice Agent, we pass a JSON object containing the exact behaviors and models we want. Here are the key components of the payload:
  • The Engine (engine): We define kind: "pipeline" and map our specific STT, LLM, and TTS choices, along with the Cartesia voice_id we discovered in Phase 1.
  • The Persona (prompt): The master system instructions. We tell Nexus exactly who it is, how to behave, and what information to gather (e.g., asking about the user’s Operating System).
  • Conversation Flow (conversation_start): We configure Nexus to speak first (who: "ai") and provide the exact greeting string it should read when the call connects.
  • Immersive Experience (background_audio): To make Nexus feel like a real IT helpdesk worker, we enable ambient office sounds and keyboard typing sound effects that trigger while the agent is “thinking” or using tools.

Executing the Request

Once the POST request is successful, the Lyzr API will return an agent_id (typically a 24-character hex string). Save this ID. You will use it in your frontend application to initiate live voice sessions with users.

import requests
import json
import os

# Setup your credentials
API_KEY = "api-key"
BASE_URL = "https://voice-livekit.studio.lyzr.ai/v1"

headers = {
    "x-api-key": API_KEY,
    "accept": "application/json",
    "Content-Type": "application/json"
}

def create_nexus_agent():
    print("--- Phase 2: Building the Nexus Voice Agent ---\n")
    print("1. Constructing the payload...")
    
    # FIX: Everything must be wrapped inside the "config" object!
    payload = {
        "config": {
            "agent_name": "Nexus - IT Support",
            "agent_description": "Level 1 Technical Support Voice Agent",
            "engine": {
                "kind": "pipeline",
                "stt": "deepgram/nova-3:en",
                "tts": "cartesia/sonic",
                "llm": "openai/gpt-4o-mini",
                "voice_id": "e07c00bc-4134-4eae-9ea4-1a55fb45746b",
                "language": "en"
            },
            "prompt": "You are Nexus, a Level 1 IT Support Assistant. Keep your answers concise and easy to understand over an audio call. Always ask what Operating System they are using (Windows or Mac) before giving troubleshooting steps.",
            "conversation_start": {
                "who": "ai",
                "greeting": "Hi there! I'm Nexus, your IT support assistant. Are you calling about a Mac or Windows computer today?"
            },
            "turn_detection": "english",
            "noise_cancellation": {
                "enabled": False,
                "type": "none"
            },
            "vad_enabled": True,
            "audio_recording_enabled": True,
            "background_audio": {
                "enabled": True,
                "ambient": {
                    "enabled": True,
                    "source": "OFFICE_AMBIENCE",
                    "volume": 0.4
                },
                "tool_call": {
                    "enabled": True,
                    "sources": [
                        {
                            "source": "KEYBOARD_TYPING_TRUNC",
                            "volume": 0.6,
                            "probability": 1
                        }
                    ]
                }
            }
        }
    }

    print("2. Sending POST request to /agents...")
    create_url = f"{BASE_URL}/agents"
    
    create_resp = requests.post(create_url, headers=headers, json=payload)
    
    # Accepting both 200 (OK) and 201 (Created)
    if create_resp.status_code not in (200, 201):
        print(f"❌ Failed to create agent. Status Code: {create_resp.status_code}")
        print(json.dumps(create_resp.json(), indent=2))
        return
        
    response_data = create_resp.json()
    
    # Parsing the ID exactly as it appeared in your successful 201 response
    agent_id = response_data.get("agent", {}).get("id")
    
    if not agent_id:
        print("❌ Agent created, but couldn't parse ID from response payload.")
        print(json.dumps(response_data, indent=2))
        return

    print(f"   ✅ Success! Nexus created with Agent ID: {agent_id}")
    print("\n🎉 Phase 2 Complete! Save your Agent ID to use in Phase 3.")

if __name__ == "__main__":
    if API_KEY == "YOUR_API_KEY_HERE":
        print("⚠️ Please replace 'YOUR_API_KEY_HERE' with your actual Lyzr API key.")
    else:
        try:
            create_nexus_agent()
        except requests.exceptions.RequestException as e:
            print(f"❌ API Request Failed: {e}")

Analytics & Deep-Dive Transcripts”

Master the flow of retrieving aggregate stats, session lists, and detailed conversation events using the Session ID Monitoring your Voice Agent involves a three-step data flow. This phase walks you through programmatically navigating from high-level metrics to the granular events of a single call session.

The Analytics Workflow

  1. Aggregate Stats: Start by checking totalCalls and avgMessages to see the agent’s overall workload.
  2. Transcript Listing: Retrieve the items list for your agent. From an item, we extract the sessionId (the unique UUID for that specific call).
  3. Detailed Drill-down: Use the sessionId to fetch the full transcript. The response is returned in a transcript object containing the chatHistory and sessionReport.

Understanding the Session Report

Inside the sessionReport, Lyzr provides millisecond-accurate metrics for every turn:
  • llm_node_ttft: Time to first token (the “Thinking” latency).
  • tts_node_ttfb: Time to first byte (the “Voice Generation” latency).
Pro Tip: In the chatHistory, the content field is returned as a list of strings. Our script automatically joins these for a clean reading experience.

import requests
import json
import os



# 1. Setup credentials and IDs
API_KEY =  "api-key"
AGENT_ID = "agent-id" 
BASE_URL = "https://voice-livekit.studio.lyzr.ai/v1"

headers = {
    "x-api-key": API_KEY,
    "accept": "application/json"
}

def fetch_uncompromised_analytics():
    print(f"--- Phase 3: Analytics for Agent {AGENT_ID} ---\n")
    
    # ---------------------------------------------------------
    # STEP 1: Fetch Aggregate Stats
    # ---------------------------------------------------------
    print("1. Fetching Aggregate Stats...")
    stats_url = f"{BASE_URL}/transcripts/agent/{AGENT_ID}/stats"
    stats_resp = requests.get(stats_url, headers=headers)
    
    if stats_resp.status_code == 200:
        s = stats_resp.json()
        print(f"   ✅ Stats: {s.get('totalCalls')} Calls | {s.get('avgMessages')} Avg Messages\n")
    else:
        print(f"   ❌ Stats API failed: {stats_resp.status_code}")

    # ---------------------------------------------------------
    # STEP 2: List Transcripts (To get the sessionId)
    # ---------------------------------------------------------
    print("2. Listing Transcripts for Agent...")
    # Sorting by desc to get the most recent call
    list_url = f"{BASE_URL}/transcripts/agent/{AGENT_ID}?sort=desc"
    list_resp = requests.get(list_url, headers=headers)
    
    if list_resp.status_code != 200:
        print(f"   ❌ List API failed: {list_resp.status_code}")
        return

    list_data = list_resp.json()
    items = list_data.get("items", [])
    
    if not items:
        print("   -> No transcripts found. Please make a call first.")
        return

    print(f"   ✅ Found {len(items)} transcript records.")
    
    # Extract the sessionId (UUID) exactly as requested
    target_session_id = items[0].get("sessionId")
    print(f"   -> Extracted Session ID: {target_session_id}\n")

    # ---------------------------------------------------------
    # STEP 3: Deep-Dive Transcript using sessionId
    # ---------------------------------------------------------
    print(f"3. Fetching Full Transcript Detail for Session: {target_session_id}...")
    detail_url = f"{BASE_URL}/transcripts/{target_session_id}"
    detail_resp = requests.get(detail_url, headers=headers)
    
    if detail_resp.status_code != 200:
        print(f"   ❌ Detail API failed: {detail_resp.status_code}")
        print(detail_resp.text)
        return

    # THE FIX: Accessing the nested 'transcript' object
    response_root = detail_resp.json()
    data = response_root.get("transcript", {})
    
    if not data:
        print("   ❌ Transcript details were empty in the response.")
        return

    # Overview Metadata
    print("\n--- SESSION OVERVIEW ---")
    print(f"Database ID: {data.get('id')}")
    print(f"Duration:    {data.get('durationMs', 0) / 1000:.2f} seconds")
    print(f"Message Count: {data.get('messageCount')}")

    # Process Chat History
    history = data.get("chatHistory", [])
    
    print("\n--- CONVERSATION TRANSCRIPT ---")
    for msg in history:
        # Ignore handoffs, only print messages
        if msg.get("type") != "message":
            continue
            
        role = msg.get("role", "SYSTEM").upper()
        # Content is a list in the schema, join it
        content_list = msg.get("content", [])
        content_text = " ".join(content_list) if isinstance(content_list, list) else content_list
        
        print(f"[{role}]: {content_text}")

    # Latency Metrics (Uncompromised Performance Data)
    print("\n--- PERFORMANCE METRICS (Last Turn) ---")
    for msg in reversed(history):
        if msg.get("role") == "assistant" and "metrics" in msg:
            m = msg["metrics"]
            print(f"   LLM Thinking Time (TTFT): {m.get('llm_node_ttft', 0):.4f}s")
            print(f"   Voice Latency (TTS TTFB): {m.get('tts_node_ttfb', 0):.4f}s")
            break

    print("\n🎉 Phase 3 Complete! Your analytics pipeline is verified.")

if __name__ == "__main__":
    try:
        fetch_uncompromised_analytics()
    except Exception as e:
        print(f"❌ Script Error: {e}")