     1→"""Bot session state, transcript manager, chunk state, and cleanup."""
     2→
     3→import asyncio
     4→import json
     5→import threading
     6→import time
     7→from datetime import datetime
     8→from typing import Dict, List, Optional
     9→
    10→from loguru import logger
    11→from sqlmodel import Session, select
    12→
    13→from app.database import engine
    14→from app.models import Persona
    15→
    16→
    17→# --- Constants ---
    18→
    19→SAMPLE_RATE = 16000
    20→NO_USER_TIMEOUT = 30  # seconds to wait for user before closing
    21→
    22→# Fallback prompt if persona not found in DB
    23→DEFAULT_SYSTEM_PROMPT = """You are Maya, a friendly and helpful AI assistant. Keep your responses concise but warm.
    24→
    25→Remember to:
    26→- Be conversational and friendly
    27→- Handle errors gracefully
    28→- Never output asterisks or markdown formatting
    29→- You are talking to the user over a voice call
    30→
    31→Start by warmly introducing yourself and asking how you can help today.
    32→"""
    33→
    34→
    35→def load_persona(persona_id: str) -> dict:
    36→    """Load persona config from DB. Returns dict with system_prompt, tools, tts_voice, name."""
    37→    try:
    38→        with Session(engine) as session:
    39→            persona = session.exec(
    40→                select(Persona)
    41→                .where(Persona.persona_id == persona_id, Persona.active == 1)
    42→            ).first()
    43→            if persona:
    44→                return {
    45→                    "persona_id": persona.persona_id,
    46→                    "name": persona.name,
    47→                    "system_prompt": persona.system_prompt,
    48→                    "tools": json.loads(persona.tools or "[]"),
    49→                    "tts_voice": persona.tts_voice or "aura-2-helena-en",
    50→                }
    51→    except Exception as e:
    52→        logger.error(f"Failed to load persona {persona_id}: {e}")
    53→    return {
    54→        "persona_id": persona_id,
    55→        "name": "Maya",
    56→        "system_prompt": DEFAULT_SYSTEM_PROMPT,
    57→        "tools": ["cf_image", "generate_video", "generate_video_from_image"],
    58→        "tts_voice": "aura-2-helena-en",
    59→    }
    60→
    61→
    62→class ChunkState:
    63→    """Tracks audio recording chunks for a session."""
    64→
    65→    def __init__(self):
    66→        self.counter: int = 0
    67→        self.chunks: List[str] = []
    68→        self.session_id: Optional[str] = None
    69→
    70→    def reset(self, session_id: str):
    71→        self.session_id = session_id
    72→        self.counter = 0
    73→        self.chunks = []
    74→
    75→    def clear(self):
    76→        self.counter = 0
    77→        self.chunks.clear()
    78→        self.session_id = None
    79→
    80→    def get_chunk_key(self, session_id: str, chunk_num: int) -> str:
    81→        return f"sessions/{session_id}/chunks/chunk_{chunk_num:03d}.wav"
    82→
    83→    def add_chunk(self, chunk_key: str) -> int:
    84→        chunk_num = self.counter
    85→        self.counter += 1
    86→        self.chunks.append(chunk_key)
    87→        return chunk_num
    88→
    89→    @property
    90→    def total_chunks(self) -> int:
    91→        return self.counter
    92→
    93→    @property
    94→    def chunk_list(self) -> List[str]:
    95→        return self.chunks.copy()
    96→
    97→
    98→class TranscriptManager:
    99→    """Collects all messages during a session for saving."""
   100→
   101→    def __init__(self):
   102→        self._messages: List[Dict] = []
   103→
   104→    def clear(self):
   105→        self._messages.clear()
   106→
   107→    def add_message(self, role: str, content: str, msg_type: str = None, **kwargs):
   108→        entry = {
   109→            "timestamp": datetime.now().isoformat(),
   110→            "role": role,
   111→            "content": content,
   112→        }
   113→        if msg_type:
   114→            entry["type"] = msg_type
   115→        entry.update(kwargs)
   116→        self._messages.append(entry)
   117→
   118→    def add_generation(self, generation_type: str, content: Dict):
   119→        self._messages.append({
   120→            "timestamp": datetime.now().isoformat(),
   121→            "role": "system",
   122→            "type": "generation",
   123→            "generation_type": generation_type,
   124→            "content": content,
   125→        })
   126→
   127→    def add_image_upload(self, mime_type: str, size_bytes: int, r2_url: str = None):
   128→        self._messages.append({
   129→            "timestamp": datetime.now().isoformat(),
   130→            "role": "user",
   131→            "type": "image_upload",
   132→            "content": {"mime_type": mime_type, "size_bytes": size_bytes, "r2_url": r2_url},
   133→        })
   134→
   135→    @property
   136→    def messages(self) -> List[Dict]:
   137→        return self._messages
   138→
   139→    @property
   140→    def count(self) -> int:
   141→        return len(self._messages)
   142→
   143→    def get_user_messages(self) -> List[Dict]:
   144→        return [m for m in self._messages if m.get("role") == "user"]
   145→
   146→    def get_assistant_messages(self) -> List[Dict]:
   147→        return [m for m in self._messages if m.get("role") == "assistant"]
   148→
   149→    def get_generations(self) -> List[Dict]:
   150→        return [m for m in self._messages if m.get("type") == "generation"]
   151→
   152→    def get_image_generations_count(self) -> int:
   153→        return len([m for m in self._messages if m.get("generation_type") == "image"])
   154→
   155→    def get_image_edits_count(self) -> int:
   156→        return len([m for m in self._messages if m.get("generation_type") == "image_edit"])
   157→
   158→
   159→class BotSessionState:
   160→    """Tracks all state for a single bot session."""
   161→
   162→    def __init__(self, session_id: str, user_id: str):
   163→        self.session_id = session_id
   164→        self.user_id = user_id
   165→        self.bot_start_time = time.time()
   166→        self.user_joined = False
   167→        self.current_user_id: Optional[str] = None
   168→        self.no_user_timeout_task: Optional[asyncio.Task] = None
   169→        # Audio
   170→        self.audio_buffer = None
   171→
   172→        # Images
   173→        self.uploaded_image: Optional[Dict] = None
   174→        self.last_image: Optional[Dict] = None
   175→        self.last_generated_image: Optional[Dict] = None
   176→        self.is_processing = False
   177→        self._last_upload_time: float = 0
   178→
   179→        # Videos
   180→        self.generated_videos: List[Dict] = []
   181→
   182→        # Pipeline references for cleanup
   183→        self.transport = None
   184→        self.pipeline = None
   185→        self._background_tasks: List[asyncio.Task] = []
   186→
   187→        # Character
   188→        self.character_id: Optional[str] = None
   189→        self.character_name: Optional[str] = None
   190→
   191→        # Subscription
   192→        self.is_premium: bool = False
   193→        self.plan: str = ""
   194→        # Usage counters (loaded once at session start)
   195→        self.plan_images_remaining: int = 0
   196→        self.plan_videos_remaining: int = 0
   197→        self.plan_ppts_remaining: int = 0
   198→        self.topup_images_remaining: int = 0
   199→        self.topup_videos_remaining: int = 0
   200→        self._topup_images_used_this_session: int = 0
   201→        self._topup_videos_used_this_session: int = 0
   202→
   203→    def get_session_info(self) -> str:
   204→        info = f"session_id={self.session_id} user_id={self.user_id}"
   205→        if self.character_id:
   206→            info += f" character={self.character_id}"
   207→        return info
   208→
   209→    def can_generate_image(self) -> str:
   210→        """Returns '' if allowed, or a message explaining why not."""
   211→        if not self.is_premium:
   212→            if self.plan_images_remaining <= 0:
   213→                return "subscribe"
   214→            return ""
   215→        # Premium user
   216→        if self.plan_images_remaining > 0:
   217→            return ""
   218→        if self.topup_images_remaining > 0:
   219→            return ""
   220→        return "topup"
   221→
   222→    def can_generate_video(self) -> str:
   223→        """Returns '' if allowed, or a message explaining why not."""
   224→        if not self.is_premium:
   225→            if self.plan_videos_remaining <= 0:
   226→                return "subscribe"
   227→            return ""
   228→        if self.plan_videos_remaining > 0:
   229→            return ""
   230→        if self.topup_videos_remaining > 0:
   231→            return ""
   232→        return "topup"
   233→
   234→    def can_generate_ppt(self) -> str:
   235→        """Returns '' if allowed, or a message explaining why not."""
   236→        if not self.is_premium:
   237→            if self.plan_ppts_remaining <= 0:
   238→                return "subscribe"
   239→            return ""
   240→        if self.plan_ppts_remaining > 0:
   241→            return ""
   242→        return "topup"
   243→
   244→    def use_ppt(self):
   245→        """Decrement PPT counter."""
   246→        if self.plan_ppts_remaining > 0:
   247→            self.plan_ppts_remaining -= 1
   248→
   249→    def use_image(self):
   250→        """Decrement image counter. Plan first, then topup."""
   251→        if self.plan_images_remaining > 0:
   252→            self.plan_images_remaining -= 1
   253→        elif self.topup_images_remaining > 0:
   254→            self.topup_images_remaining -= 1
   255→            self._topup_images_used_this_session += 1
   256→
   257→    def use_video(self):
   258→        """Decrement video counter. Plan first, then topup."""
   259→        if self.plan_videos_remaining > 0:
   260→            self.plan_videos_remaining -= 1
   261→        elif self.topup_videos_remaining > 0:
   262→            self.topup_videos_remaining -= 1
   263→            self._topup_videos_used_this_session += 1
   264→
   265→    def can_upload_image(self, cooldown: float = 2.0) -> bool:
   266→        now = time.time()
   267→        if now - self._last_upload_time < cooldown:
   268→            return False
   269→        self._last_upload_time = now
   270→        return True
   271→
   272→    def set_uploaded_image(self, image_info: Dict):
   273→        self.last_generated_image = None
   274→        self.uploaded_image = image_info
   275→        self.last_image = image_info
   276→
   277→    def set_generated_image(self, image_info: Dict):
   278→        self.last_generated_image = image_info
   279→        self.last_image = image_info
   280→
   281→    def set_edited_image(self, image_info: Dict):
   282→        self.last_generated_image = image_info
   283→        self.last_image = image_info
   284→        self.uploaded_image = None
   285→
   286→    def get_image_to_edit(self) -> Optional[Dict]:
   287→        return self.last_image or self.uploaded_image or self.last_generated_image
   288→
   289→    def get_uploaded_image(self) -> Optional[Dict]:
   290→        return self.uploaded_image or self.last_generated_image
   291→
   292→    def clear_all_images(self):
   293→        self.uploaded_image = None
   294→        self.last_image = None
   295→        self.last_generated_image = None
   296→
   297→    def add_generated_video(self, video_type: str, metadata: Dict, prompt: str = None):
   298→        self.generated_videos.append({
   299→            "video_type": video_type, "metadata": metadata, "prompt": prompt,
   300→        })
   301→
   302→    def cancel_timeout_task(self):
   303→        if self.no_user_timeout_task and not self.no_user_timeout_task.done():
   304→            self.no_user_timeout_task.cancel()
   305→
   306→    async def cancel_background_tasks(self):
   307→        tasks = list(self._background_tasks)
   308→        for t in tasks:
   309→            if not t.done():
   310→                t.cancel()
   311→        if tasks:
   312→            await asyncio.gather(*tasks, return_exceptions=True)
   313→            logger.debug(f"CLEANUP: cancelled {len(tasks)} background tasks session_id={self.session_id}")
   314→        self._background_tasks.clear()
   315→
   316→
   317→async def cleanup_session(
   318→    bot_session: BotSessionState,
   319→    session_id: str,
   320→    transcript_manager: Optional[TranscriptManager] = None,
   321→    chunk_state: Optional[ChunkState] = None,
   322→):
   323→    """Full memory cleanup after session ends."""
   324→    threads_start = threading.active_count()
   325→    logger.info(f"CLEANUP_START: session_id={session_id} threads={threads_start}")
   326→
   327→    try:
   328→        await bot_session.cancel_background_tasks()
   329→    except Exception as e:
   330→        logger.error(f"CLEANUP_ERROR: cancel_background_tasks: {e}")
   331→
   332→    # Pipeline cleanup releases transport + native threads
   333→    try:
   334→        if bot_session.pipeline:
   335→            if hasattr(bot_session.pipeline, "cleanup"):
   336→                await bot_session.pipeline.cleanup()
   337→            bot_session.pipeline = None
   338→    except Exception as e:
   339→        logger.error(f"CLEANUP_ERROR: pipeline: {e}")
   340→
   341→    try:
   342→        if bot_session.transport:
   343→            if hasattr(bot_session.transport, "cleanup"):
   344→                await bot_session.transport.cleanup()
   345→            bot_session.transport = None
   346→    except Exception as e:
   347→        logger.error(f"CLEANUP_ERROR: transport: {e}")
   348→
   349→    bot_session.clear_all_images()
   350→    bot_session.audio_buffer = None
   351→    bot_session.generated_videos.clear()
   352→
   353→    if transcript_manager:
   354→        transcript_manager.clear()
   355→    if chunk_state:
   356→        chunk_state.clear()
   357→
   358→    threads_end = threading.active_count()
   359→    logger.info(f"CLEANUP_END: session_id={session_id} threads={threads_start}->{threads_end}")
   360→

"""Bot session state, transcript manager, chunk state, and cleanup."""

import asyncio
import json
import threading
import time
from datetime import datetime
from typing import Dict, List, Optional

from loguru import logger
from sqlmodel import Session, select

from app.database import engine
from app.models import Persona

# --- Constants ---

SAMPLE_RATE = 16000
NO_USER_TIMEOUT = 30  # seconds to wait for user before closing

# Fallback prompt if persona not found in DB
DEFAULT_SYSTEM_PROMPT = """You are Maya, a friendly and helpful AI assistant. Keep your responses concise but warm.

Remember to:
- Be conversational and friendly
- Handle errors gracefully
- Never output asterisks or markdown formatting
- You are talking to the user over a voice call

Start by warmly introducing yourself and asking how you can help today.
"""

def load_persona(persona_id: str) -> dict:
    """Load persona config from DB. Returns dict with system_prompt, tools, tts_voice, name."""
    try:
        with Session(engine) as session:
            persona = session.exec(
                select(Persona)
                .where(Persona.persona_id == persona_id, Persona.active == 1)
            ).first()
            if persona:
                return {
                    "persona_id": persona.persona_id,
                    "name": persona.name,
                    "system_prompt": persona.system_prompt,
                    "tools": json.loads(persona.tools or "[]"),
                    "tts_voice": persona.tts_voice or "aura-2-helena-en",
                }
    except Exception as e:
        logger.error(f"Failed to load persona {persona_id}: {e}")
    return {
        "persona_id": persona_id,
        "name": "Maya",
        "system_prompt": DEFAULT_SYSTEM_PROMPT,
        "tools": ["cf_image", "generate_video", "generate_video_from_image"],
        "tts_voice": "aura-2-helena-en",
    }

class ChunkState:
    """Tracks audio recording chunks for a session."""

    def __init__(self):
        self.counter: int = 0
        self.chunks: List[str] = []
        self.session_id: Optional[str] = None

    def reset(self, session_id: str):
        self.session_id = session_id
        self.counter = 0
        self.chunks = []

    def clear(self):
        self.counter = 0
        self.chunks.clear()
        self.session_id = None

    def get_chunk_key(self, session_id: str, chunk_num: int) -> str:
        return f"sessions/{session_id}/chunks/chunk_{chunk_num:03d}.wav"

    def add_chunk(self, chunk_key: str) -> int:
        chunk_num = self.counter
        self.counter += 1
        self.chunks.append(chunk_key)
        return chunk_num

    @property
    def total_chunks(self) -> int:
        return self.counter

    @property
    def chunk_list(self) -> List[str]:
        return self.chunks.copy()

class TranscriptManager:
    """Collects all messages during a session for saving."""

    def __init__(self):
        self._messages: List[Dict] = []

    def clear(self):
        self._messages.clear()

    def add_message(self, role: str, content: str, msg_type: str = None, **kwargs):
        entry = {
            "timestamp": datetime.now().isoformat(),
            "role": role,
            "content": content,
        }
        if msg_type:
            entry["type"] = msg_type
        entry.update(kwargs)
        self._messages.append(entry)

    def add_generation(self, generation_type: str, content: Dict):
        self._messages.append({
            "timestamp": datetime.now().isoformat(),
            "role": "system",
            "type": "generation",
            "generation_type": generation_type,
            "content": content,
        })

    def add_image_upload(self, mime_type: str, size_bytes: int, r2_url: str = None):
        self._messages.append({
            "timestamp": datetime.now().isoformat(),
            "role": "user",
            "type": "image_upload",
            "content": {"mime_type": mime_type, "size_bytes": size_bytes, "r2_url": r2_url},
        })

    @property
    def messages(self) -> List[Dict]:
        return self._messages

    @property
    def count(self) -> int:
        return len(self._messages)

    def get_user_messages(self) -> List[Dict]:
        return [m for m in self._messages if m.get("role") == "user"]

    def get_assistant_messages(self) -> List[Dict]:
        return [m for m in self._messages if m.get("role") == "assistant"]

    def get_generations(self) -> List[Dict]:
        return [m for m in self._messages if m.get("type") == "generation"]

    def get_image_generations_count(self) -> int:
        return len([m for m in self._messages if m.get("generation_type") == "image"])

    def get_image_edits_count(self) -> int:
        return len([m for m in self._messages if m.get("generation_type") == "image_edit"])

class BotSessionState:
    """Tracks all state for a single bot session."""

    def __init__(self, session_id: str, user_id: str):
        self.session_id = session_id
        self.user_id = user_id
        self.bot_start_time = time.time()
        self.user_joined = False
        self.current_user_id: Optional[str] = None
        self.no_user_timeout_task: Optional[asyncio.Task] = None
        # Audio
        self.audio_buffer = None

        # Images
        self.uploaded_image: Optional[Dict] = None
        self.last_image: Optional[Dict] = None
        self.last_generated_image: Optional[Dict] = None
        self.is_processing = False
        self._last_upload_time: float = 0

        # Videos
        self.generated_videos: List[Dict] = []

        # Pipeline references for cleanup
        self.transport = None
        self.pipeline = None
        self._background_tasks: List[asyncio.Task] = []

        # Character
        self.character_id: Optional[str] = None
        self.character_name: Optional[str] = None

        # Subscription
        self.is_premium: bool = False
        self.plan: str = ""
        # Usage counters (loaded once at session start)
        self.plan_images_remaining: int = 0
        self.plan_videos_remaining: int = 0
        self.plan_ppts_remaining: int = 0
        self.topup_images_remaining: int = 0
        self.topup_videos_remaining: int = 0
        self._topup_images_used_this_session: int = 0
        self._topup_videos_used_this_session: int = 0

    def get_session_info(self) -> str:
        info = f"session_id={self.session_id} user_id={self.user_id}"
        if self.character_id:
            info += f" character={self.character_id}"
        return info

    def can_generate_image(self) -> str:
        """Returns '' if allowed, or a message explaining why not."""
        if not self.is_premium:
            if self.plan_images_remaining <= 0:
                return "subscribe"
            return ""
        # Premium user
        if self.plan_images_remaining > 0:
            return ""
        if self.topup_images_remaining > 0:
            return ""
        return "topup"

    def can_generate_video(self) -> str:
        """Returns '' if allowed, or a message explaining why not."""
        if not self.is_premium:
            if self.plan_videos_remaining <= 0:
                return "subscribe"
            return ""
        if self.plan_videos_remaining > 0:
            return ""
        if self.topup_videos_remaining > 0:
            return ""
        return "topup"

    def can_generate_ppt(self) -> str:
        """Returns '' if allowed, or a message explaining why not."""
        if not self.is_premium:
            if self.plan_ppts_remaining <= 0:
                return "subscribe"
            return ""
        if self.plan_ppts_remaining > 0:
            return ""
        return "topup"

    def use_ppt(self):
        """Decrement PPT counter."""
        if self.plan_ppts_remaining > 0:
            self.plan_ppts_remaining -= 1

    def use_image(self):
        """Decrement image counter. Plan first, then topup."""
        if self.plan_images_remaining > 0:
            self.plan_images_remaining -= 1
        elif self.topup_images_remaining > 0:
            self.topup_images_remaining -= 1
            self._topup_images_used_this_session += 1

    def use_video(self):
        """Decrement video counter. Plan first, then topup."""
        if self.plan_videos_remaining > 0:
            self.plan_videos_remaining -= 1
        elif self.topup_videos_remaining > 0:
            self.topup_videos_remaining -= 1
            self._topup_videos_used_this_session += 1

    def can_upload_image(self, cooldown: float = 2.0) -> bool:
        now = time.time()
        if now - self._last_upload_time < cooldown:
            return False
        self._last_upload_time = now
        return True

    def set_uploaded_image(self, image_info: Dict):
        self.last_generated_image = None
        self.uploaded_image = image_info
        self.last_image = image_info

    def set_generated_image(self, image_info: Dict):
        self.last_generated_image = image_info
        self.last_image = image_info

    def set_edited_image(self, image_info: Dict):
        self.last_generated_image = image_info
        self.last_image = image_info
        self.uploaded_image = None

    def get_image_to_edit(self) -> Optional[Dict]:
        return self.last_image or self.uploaded_image or self.last_generated_image

    def get_uploaded_image(self) -> Optional[Dict]:
        return self.uploaded_image or self.last_generated_image

    def clear_all_images(self):
        self.uploaded_image = None
        self.last_image = None
        self.last_generated_image = None

    def add_generated_video(self, video_type: str, metadata: Dict, prompt: str = None):
        self.generated_videos.append({
            "video_type": video_type, "metadata": metadata, "prompt": prompt,
        })

    def cancel_timeout_task(self):
        if self.no_user_timeout_task and not self.no_user_timeout_task.done():
            self.no_user_timeout_task.cancel()

    async def cancel_background_tasks(self):
        tasks = list(self._background_tasks)
        for t in tasks:
            if not t.done():
                t.cancel()
        if tasks:
            await asyncio.gather(*tasks, return_exceptions=True)
            logger.debug(f"CLEANUP: cancelled {len(tasks)} background tasks session_id={self.session_id}")
        self._background_tasks.clear()

async def cleanup_session(
    bot_session: BotSessionState,
    session_id: str,
    transcript_manager: Optional[TranscriptManager] = None,
    chunk_state: Optional[ChunkState] = None,
):
    """Full memory cleanup after session ends."""
    threads_start = threading.active_count()
    logger.info(f"CLEANUP_START: session_id={session_id} threads={threads_start}")

    try:
        await bot_session.cancel_background_tasks()
    except Exception as e:
        logger.error(f"CLEANUP_ERROR: cancel_background_tasks: {e}")

    # Pipeline cleanup releases transport + native threads
    try:
        if bot_session.pipeline:
            if hasattr(bot_session.pipeline, "cleanup"):
                await bot_session.pipeline.cleanup()
            bot_session.pipeline = None
    except Exception as e:
        logger.error(f"CLEANUP_ERROR: pipeline: {e}")

    try:
        if bot_session.transport:
            if hasattr(bot_session.transport, "cleanup"):
                await bot_session.transport.cleanup()
            bot_session.transport = None
    except Exception as e:
        logger.error(f"CLEANUP_ERROR: transport: {e}")

    bot_session.clear_all_images()
    bot_session.audio_buffer = None
    bot_session.generated_videos.clear()

    if transcript_manager:
        transcript_manager.clear()
    if chunk_state:
        chunk_state.clear()

    threads_end = threading.active_count()
    logger.info(f"CLEANUP_END: session_id={session_id} threads={threads_start}->{threads_end}")


     1→"""Bot entry point — Traditional pipeline: Deepgram STT + GoogleVertexLLM + ElevenLabs TTS.
     2→
     3→Follows India prod pipeline.py pattern exactly.
     4→"""
     5→
     6→import asyncio
     7→import json
     8→import os
     9→import sys
    10→import uuid
    11→
    12→from dotenv import load_dotenv
    13→from loguru import logger
    14→
    15→from pipecat.adapters.schemas.tools_schema import ToolsSchema
    16→from pipecat.audio.vad.silero import SileroVADAnalyzer
    17→from pipecat.audio.vad.vad_analyzer import VADParams
    18→from pipecat.pipeline.pipeline import Pipeline
    19→from pipecat.pipeline.runner import PipelineRunner
    20→from pipecat.pipeline.task import PipelineParams, PipelineTask
    21→from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
    22→from pipecat.processors.audio.audio_buffer_processor import AudioBufferProcessor
    23→from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor
    24→from pipecat.processors.transcript_processor import TranscriptProcessor
    25→from pipecat.services.deepgram.stt import DeepgramSTTService
    26→from pipecat.services.deepgram.tts import DeepgramTTSService
    27→from pipecat.services.google.llm_vertex import GoogleVertexLLMService
    28→from pipecat.transports.services.daily import DailyParams, DailyTransport
    29→
    30→from bot.core.config import (
    31→    SAMPLE_RATE,
    32→    BotSessionState,
    33→    ChunkState,
    34→    TranscriptManager,
    35→    load_persona,
    36→)
    37→from bot.core.session_handlers import (
    38→    create_audio_data_handler,
    39→    create_cleanup_if_no_user,
    40→    create_first_participant_joined_handler,
    41→    create_participant_left_handler,
    42→    create_transcript_handler,
    43→)
    44→from bot.services.image_service import cf_image_function, create_cf_image_handler
    45→from bot.services.image_upload import handle_image_upload_url, handle_image_upload_base64
    46→from bot.services.ppt_service import generate_ppt_function, create_ppt_handler
    47→from bot.services.video_service import (
    48→    generate_video_function,
    49→    generate_video_from_image_function,
    50→    create_video_handler,
    51→    create_video_from_image_handler,
    52→)
    53→from bot.services.video_ads_service import (
    54→    generate_video_ad_function,
    55→    create_video_ad_handler,
    56→)
    57→
    58→load_dotenv(override=True)
    59→
    60→logger.remove(0)
    61→logger.add(sys.stderr, level="DEBUG")
    62→
    63→
    64→# --- SafeRTVIObserver (same as India prod) ---
    65→
    66→class SafeRTVIObserver(RTVIObserver):
    67→    """RTVIObserver that suppresses 'Unable to send messages before joining' errors."""
    68→
    69→    def __init__(self, rtvi, **kwargs):
    70→        super().__init__(rtvi, **kwargs)
    71→        self._startup_errors_suppressed = 0
    72→
    73→    async def on_process_frame(self, data):
    74→        try:
    75→            await super().on_process_frame(data)
    76→        except Exception as e:
    77→            if "Unable to send messages before joining" in str(e):
    78→                self._startup_errors_suppressed += 1
    79→                if self._startup_errors_suppressed == 1:
    80→                    logger.debug("SafeRTVIObserver: Suppressing startup message (transport not yet joined)")
    81→            else:
    82→                raise
    83→
    84→
    85→# --- Main ---
    86→
    87→async def main():
    88→    daily_room_url = os.getenv("DAILY_ROOM_URL")
    89→    daily_room_token = os.getenv("DAILY_ROOM_TOKEN")
    90→    session_id = os.getenv("SESSION_ID", uuid.uuid4().hex[:8])
    91→    user_id = os.getenv("USER_ID", "")
    92→    is_premium = os.getenv("IS_PREMIUM", "False") == "True"
    93→    plan = os.getenv("PLAN", "")
    94→    persona_id = os.getenv("PERSONA_ID", "image_gen")
    95→
    96→    # Load persona config from DB
    97→    persona = load_persona(persona_id)
    98→    persona_name = persona["name"]
    99→    persona_prompt = persona["system_prompt"]
   100→    persona_tools = persona["tools"]  # list of tool function names to register
   101→    persona_tts_voice = persona["tts_voice"]
   102→
   103→    logger.info(f"PERSONA_LOADED: id={persona_id} name={persona_name} tools={persona_tools}")
   104→
   105→    deepgram_api_key = os.getenv("DEEPGRAM_API_KEY", "")
   106→
   107→    # Vertex AI credentials
   108→    creds_path = os.getenv("GOOGLE_VERTEX_CREDENTIALS_PATH", "gcp-service-account.json")
   109→    if not os.path.isabs(creds_path):
   110→        creds_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", creds_path)
   111→    project_id = os.getenv("GOOGLE_CLOUD_PROJECT_ID", "")
   112→    location = os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1")
   113→
   114→    if not daily_room_url:
   115→        logger.error("DAILY_ROOM_URL environment variable is not set")
   116→        sys.exit(1)
   117→
   118→    logger.info(f"BOT_START: room={daily_room_url} session_id={session_id} user_id={user_id}")
   119→
   120→    # --- Session State ---
   121→    bot_session = BotSessionState(session_id=session_id, user_id=user_id)
   122→    bot_session.is_premium = is_premium
   123→    bot_session.plan = plan
   124→    bot_session.character_id = persona_id
   125→    bot_session.character_name = persona_name
   126→    transcript_manager = TranscriptManager()
   127→    chunk_state = ChunkState()
   128→
   129→    # --- VAD (same as India prod) ---
   130→    vad_analyzer = SileroVADAnalyzer(
   131→        params=VADParams(
   132→            confidence=0.7,
   133→            stop_secs=0.5,
   134→            start_secs=0.2,
   135→            min_volume=0.6,
   136→        )
   137→    )
   138→
   139→    # --- Transport (follows India prod create_transport) ---
   140→    transport = DailyTransport(
   141→        daily_room_url,
   142→        daily_room_token,
   143→        persona_name,
   144→        DailyParams(
   145→            audio_in_enabled=True,
   146→            audio_out_enabled=True,
   147→            audio_in_sample_rate=SAMPLE_RATE,
   148→            audio_out_sample_rate=SAMPLE_RATE,
   149→            vad_analyzer=vad_analyzer,
   150→        ),
   151→    )
   152→    bot_session.transport = transport
   153→
   154→    # --- STT (Deepgram) ---
   155→    stt = DeepgramSTTService(
   156→        api_key=deepgram_api_key,
   157→        settings=DeepgramSTTService.Settings(
   158→            model="nova-2",
   159→            language="en",
   160→        ),
   161→    )
   162→
   163→    # --- LLM (Google Vertex AI Gemini — same as India prod) ---
   164→    llm = GoogleVertexLLMService(
   165→        project_id=project_id,
   166→        location=location,
   167→        model="gemini-2.5-flash",
   168→        credentials_path=creds_path,
   169→        system_instruction=persona_prompt,
   170→        params=GoogleVertexLLMService.InputParams(
   171→            max_output_tokens=4096,
   172→            temperature=0.7,
   173→        ),
   174→    )
   175→
   176→    # --- TTS (Deepgram) ---
   177→    tts = DeepgramTTSService(
   178→        api_key=deepgram_api_key,
   179→        voice=persona_tts_voice,
   180→    )
   181→
   182→    # --- Context & Tools (persona-specific) ---
   183→    tool_map = {
   184→        "cf_image": cf_image_function,
   185→        "generate_video": generate_video_function,
   186→        "generate_video_from_image": generate_video_from_image_function,
   187→        "generate_ppt": generate_ppt_function,
   188→        "generate_video_ad": generate_video_ad_function,
   189→    }
   190→    standard_tools = [tool_map[t] for t in persona_tools if t in tool_map]
   191→    tools = ToolsSchema(standard_tools=standard_tools) if standard_tools else None
   192→
   193→    messages = [
   194→        {"role": "system", "content": persona_prompt},
   195→        {"role": "user", "content": "Start the Conversation :"},
   196→    ]
   197→
   198→    context = OpenAILLMContext(
   199→        messages=messages,
   200→        tools=tools,
   201→    )
   202→    context_aggregator = llm.create_context_aggregator(context)
   203→
   204→    # --- Processors (follows India prod create_processors) ---
   205→    rtvi = RTVIProcessor(config=RTVIConfig(config=[]))
   206→    transcript = TranscriptProcessor()
   207→    audio_buffer = AudioBufferProcessor(
   208→        sample_rate=SAMPLE_RATE,
   209→        num_channels=1,
   210→        buffer_duration_secs=60,
   211→    )
   212→    audio_buffer.event_handler("on_audio_data")(
   213→        create_audio_data_handler(bot_session, chunk_state)
   214→    )
   215→
   216→    # --- Pipeline (follows India prod create_pipeline EXACTLY) ---
   217→    pipeline = Pipeline([
   218→        transport.input(),
   219→        stt,
   220→        transcript.user(),
   221→        context_aggregator.user(),
   222→        rtvi,
   223→        llm,
   224→        tts,
   225→        transport.output(),
   226→        audio_buffer,
   227→        transcript.assistant(),
   228→        context_aggregator.assistant(),
   229→    ])
   230→    bot_session.pipeline = pipeline
   231→
   232→    # --- Task (follows India prod create_task EXACTLY) ---
   233→    task = PipelineTask(
   234→        pipeline,
   235→        params=PipelineParams(
   236→            enable_metrics=True,
   237→            enable_usage_metrics=True,
   238→            allow_interruptions=True,
   239→        ),
   240→        observers=[
   241→            SafeRTVIObserver(rtvi),
   242→        ],
   243→    )
   244→
   245→    # --- Register tool handlers (persona-specific) ---
   246→    handler_map = {
   247→        "cf_image": ("cf_image", create_cf_image_handler),
   248→        "generate_video": ("generate_video", create_video_handler),
   249→        "generate_video_from_image": ("generate_video_from_image", create_video_from_image_handler),
   250→        "generate_ppt": ("generate_ppt", create_ppt_handler),
   251→        "generate_video_ad": ("generate_video_ad", create_video_ad_handler),
   252→    }
   253→    for tool_name in persona_tools:
   254→        if tool_name in handler_map:
   255→            func_name, handler_factory = handler_map[tool_name]
   256→            llm.register_function(
   257→                func_name,
   258→                handler_factory(bot_session, transcript_manager, task),
   259→                timeout_secs=120, cancel_on_interruption=False,
   260→            )
   261→            logger.info(f"TOOL_REGISTERED: {func_name} for persona={persona_id}")
   262→
   263→    # --- RTVI event handlers (follows India prod) ---
   264→    @rtvi.event_handler("on_client_ready")
   265→    async def on_client_ready(rtvi_proc):
   266→        await rtvi_proc.set_bot_ready()
   267→
   268→    # --- Transcript Handler ---
   269→    transcript.event_handler("on_transcript_update")(
   270→        create_transcript_handler(bot_session, transcript_manager, task)
   271→    )
   272→
   273→    # --- Transport Event Handlers ---
   274→    @transport.event_handler("on_first_participant_joined")
   275→    async def _on_first_participant_joined(transport, participant):
   276→        handler = create_first_participant_joined_handler(bot_session, task, audio_buffer, chunk_state)
   277→        await handler(transport, participant)
   278→
   279→    @transport.event_handler("on_participant_left")
   280→    async def _on_participant_left(transport, participant, reason):
   281→        handler = create_participant_left_handler(bot_session, transcript_manager, chunk_state, task)
   282→        await handler(transport, participant, reason)
   283→
   284→    @transport.event_handler("on_app_message")
   285→    async def _on_app_message(transport, message, sender):
   286→        """Handle image uploads from the frontend."""
   287→        try:
   288→            if isinstance(message, str):
   289→                message = json.loads(message)
   290→
   291→            msg_type = message.get("type")
   292→            data = message.get("data", {})
   293→
   294→            # Unwrap RTVI client-message format
   295→            if msg_type == "client-message" and isinstance(data, dict):
   296→                inner_type = data.get("t", "")
   297→                inner_data = data.get("d", {})
   298→                if inner_type:
   299→                    msg_type = inner_type
   300→                    data = inner_data
   301→
   302→            if msg_type in ("user_image_upload", "image_upload"):
   303→                mime_type = data.get("mime_type", "image/jpeg")
   304→                image_url = data.get("image_url")
   305→                image_id = data.get("image_id", "")
   306→
   307→                if image_url:
   308→                    await handle_image_upload_url(
   309→                        image_url, image_id, mime_type,
   310→                        bot_session, transcript_manager, task,
   311→                    )
   312→                else:
   313→                    image_b64 = data.get("image", "")
   314→                    if image_b64:
   315→                        await handle_image_upload_base64(
   316→                            image_b64, mime_type,
   317→                            bot_session, transcript_manager, task,
   318→                        )
   319→                    else:
   320→                        logger.warning("Received image_upload with no image data or URL")
   321→
   322→        except Exception as e:
   323→            logger.error(f"Error handling app message: {e}", exc_info=True)
   324→
   325→    # --- No-user timeout ---
   326→    cleanup_coro = create_cleanup_if_no_user(bot_session, task)
   327→    bot_session.no_user_timeout_task = asyncio.create_task(cleanup_coro())
   328→
   329→    # --- Run ---
   330→    runner = PipelineRunner()
   331→    await runner.run(task)
   332→
   333→
   334→if __name__ == "__main__":
   335→    asyncio.run(main())
   336→

"""Bot entry point — Traditional pipeline: Deepgram STT + GoogleVertexLLM + ElevenLabs TTS.

Follows India prod pipeline.py pattern exactly.
"""

import asyncio
import json
import os
import sys
import uuid

from dotenv import load_dotenv
from loguru import logger

from pipecat.adapters.schemas.tools_schema import ToolsSchema
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.audio.vad.vad_analyzer import VADParams
from pipecat.pipeline.pipeline import Pipeline
from pipecat.pipeline.runner import PipelineRunner
from pipecat.pipeline.task import PipelineParams, PipelineTask
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
from pipecat.processors.audio.audio_buffer_processor import AudioBufferProcessor
from pipecat.processors.frameworks.rtvi import RTVIConfig, RTVIObserver, RTVIProcessor
from pipecat.processors.transcript_processor import TranscriptProcessor
from pipecat.services.deepgram.stt import DeepgramSTTService
from pipecat.services.deepgram.tts import DeepgramTTSService
from pipecat.services.google.llm_vertex import GoogleVertexLLMService
from pipecat.transports.services.daily import DailyParams, DailyTransport

from bot.core.config import (
    SAMPLE_RATE,
    BotSessionState,
    ChunkState,
    TranscriptManager,
    load_persona,
)
from bot.core.session_handlers import (
    create_audio_data_handler,
    create_cleanup_if_no_user,
    create_first_participant_joined_handler,
    create_participant_left_handler,
    create_transcript_handler,
)
from bot.services.image_service import cf_image_function, create_cf_image_handler
from bot.services.image_upload import handle_image_upload_url, handle_image_upload_base64
from bot.services.ppt_service import generate_ppt_function, create_ppt_handler
from bot.services.video_service import (
    generate_video_function,
    generate_video_from_image_function,
    create_video_handler,
    create_video_from_image_handler,
)
from bot.services.video_ads_service import (
    generate_video_ad_function,
    create_video_ad_handler,
)

load_dotenv(override=True)

logger.remove(0)
logger.add(sys.stderr, level="DEBUG")

# --- SafeRTVIObserver (same as India prod) ---

class SafeRTVIObserver(RTVIObserver):
    """RTVIObserver that suppresses 'Unable to send messages before joining' errors."""

    def __init__(self, rtvi, **kwargs):
        super().__init__(rtvi, **kwargs)
        self._startup_errors_suppressed = 0

    async def on_process_frame(self, data):
        try:
            await super().on_process_frame(data)
        except Exception as e:
            if "Unable to send messages before joining" in str(e):
                self._startup_errors_suppressed += 1
                if self._startup_errors_suppressed == 1:
                    logger.debug("SafeRTVIObserver: Suppressing startup message (transport not yet joined)")
            else:
                raise

# --- Main ---

async def main():
    daily_room_url = os.getenv("DAILY_ROOM_URL")
    daily_room_token = os.getenv("DAILY_ROOM_TOKEN")
    session_id = os.getenv("SESSION_ID", uuid.uuid4().hex[:8])
    user_id = os.getenv("USER_ID", "")
    is_premium = os.getenv("IS_PREMIUM", "False") == "True"
    plan = os.getenv("PLAN", "")
    persona_id = os.getenv("PERSONA_ID", "image_gen")

    # Load persona config from DB
    persona = load_persona(persona_id)
    persona_name = persona["name"]
    persona_prompt = persona["system_prompt"]
    persona_tools = persona["tools"]  # list of tool function names to register
    persona_tts_voice = persona["tts_voice"]

    logger.info(f"PERSONA_LOADED: id={persona_id} name={persona_name} tools={persona_tools}")

    deepgram_api_key = os.getenv("DEEPGRAM_API_KEY", "")

    # Vertex AI credentials
    creds_path = os.getenv("GOOGLE_VERTEX_CREDENTIALS_PATH", "gcp-service-account.json")
    if not os.path.isabs(creds_path):
        creds_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", creds_path)
    project_id = os.getenv("GOOGLE_CLOUD_PROJECT_ID", "")
    location = os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1")

    if not daily_room_url:
        logger.error("DAILY_ROOM_URL environment variable is not set")
        sys.exit(1)

    logger.info(f"BOT_START: room={daily_room_url} session_id={session_id} user_id={user_id}")

    # --- Session State ---
    bot_session = BotSessionState(session_id=session_id, user_id=user_id)
    bot_session.is_premium = is_premium
    bot_session.plan = plan
    bot_session.character_id = persona_id
    bot_session.character_name = persona_name
    transcript_manager = TranscriptManager()
    chunk_state = ChunkState()

    # --- VAD (same as India prod) ---
    vad_analyzer = SileroVADAnalyzer(
        params=VADParams(
            confidence=0.7,
            stop_secs=0.5,
            start_secs=0.2,
            min_volume=0.6,
        )
    )

    # --- Transport (follows India prod create_transport) ---
    transport = DailyTransport(
        daily_room_url,
        daily_room_token,
        persona_name,
        DailyParams(
            audio_in_enabled=True,
            audio_out_enabled=True,
            audio_in_sample_rate=SAMPLE_RATE,
            audio_out_sample_rate=SAMPLE_RATE,
            vad_analyzer=vad_analyzer,
        ),
    )
    bot_session.transport = transport

    # --- STT (Deepgram) ---
    stt = DeepgramSTTService(
        api_key=deepgram_api_key,
        settings=DeepgramSTTService.Settings(
            model="nova-2",
            language="en",
        ),
    )

    # --- LLM (Google Vertex AI Gemini — same as India prod) ---
    llm = GoogleVertexLLMService(
        project_id=project_id,
        location=location,
        model="gemini-2.5-flash",
        credentials_path=creds_path,
        system_instruction=persona_prompt,
        params=GoogleVertexLLMService.InputParams(
            max_output_tokens=4096,
            temperature=0.7,
        ),
    )

    # --- TTS (Deepgram) ---
    tts = DeepgramTTSService(
        api_key=deepgram_api_key,
        voice=persona_tts_voice,
    )

    # --- Context & Tools (persona-specific) ---
    tool_map = {
        "cf_image": cf_image_function,
        "generate_video": generate_video_function,
        "generate_video_from_image": generate_video_from_image_function,
        "generate_ppt": generate_ppt_function,
        "generate_video_ad": generate_video_ad_function,
    }
    standard_tools = [tool_map[t] for t in persona_tools if t in tool_map]
    tools = ToolsSchema(standard_tools=standard_tools) if standard_tools else None

    messages = [
        {"role": "system", "content": persona_prompt},
        {"role": "user", "content": "Start the Conversation :"},
    ]

    context = OpenAILLMContext(
        messages=messages,
        tools=tools,
    )
    context_aggregator = llm.create_context_aggregator(context)

    # --- Processors (follows India prod create_processors) ---
    rtvi = RTVIProcessor(config=RTVIConfig(config=[]))
    transcript = TranscriptProcessor()
    audio_buffer = AudioBufferProcessor(
        sample_rate=SAMPLE_RATE,
        num_channels=1,
        buffer_duration_secs=60,
    )
    audio_buffer.event_handler("on_audio_data")(
        create_audio_data_handler(bot_session, chunk_state)
    )

    # --- Pipeline (follows India prod create_pipeline EXACTLY) ---
    pipeline = Pipeline([
        transport.input(),
        stt,
        transcript.user(),
        context_aggregator.user(),
        rtvi,
        llm,
        tts,
        transport.output(),
        audio_buffer,
        transcript.assistant(),
        context_aggregator.assistant(),
    ])
    bot_session.pipeline = pipeline

    # --- Task (follows India prod create_task EXACTLY) ---
    task = PipelineTask(
        pipeline,
        params=PipelineParams(
            enable_metrics=True,
            enable_usage_metrics=True,
            allow_interruptions=True,
        ),
        observers=[
            SafeRTVIObserver(rtvi),
        ],
    )

    # --- Register tool handlers (persona-specific) ---
    handler_map = {
        "cf_image": ("cf_image", create_cf_image_handler),
        "generate_video": ("generate_video", create_video_handler),
        "generate_video_from_image": ("generate_video_from_image", create_video_from_image_handler),
        "generate_ppt": ("generate_ppt", create_ppt_handler),
        "generate_video_ad": ("generate_video_ad", create_video_ad_handler),
    }
    for tool_name in persona_tools:
        if tool_name in handler_map:
            func_name, handler_factory = handler_map[tool_name]
            llm.register_function(
                func_name,
                handler_factory(bot_session, transcript_manager, task),
                timeout_secs=120, cancel_on_interruption=False,
            )
            logger.info(f"TOOL_REGISTERED: {func_name} for persona={persona_id}")

    # --- RTVI event handlers (follows India prod) ---
    @rtvi.event_handler("on_client_ready")
    async def on_client_ready(rtvi_proc):
        await rtvi_proc.set_bot_ready()

    # --- Transcript Handler ---
    transcript.event_handler("on_transcript_update")(
        create_transcript_handler(bot_session, transcript_manager, task)
    )

    # --- Transport Event Handlers ---
    @transport.event_handler("on_first_participant_joined")
    async def _on_first_participant_joined(transport, participant):
        handler = create_first_participant_joined_handler(bot_session, task, audio_buffer, chunk_state)
        await handler(transport, participant)

    @transport.event_handler("on_participant_left")
    async def _on_participant_left(transport, participant, reason):
        handler = create_participant_left_handler(bot_session, transcript_manager, chunk_state, task)
        await handler(transport, participant, reason)

    @transport.event_handler("on_app_message")
    async def _on_app_message(transport, message, sender):
        """Handle image uploads from the frontend."""
        try:
            if isinstance(message, str):
                message = json.loads(message)

            msg_type = message.get("type")
            data = message.get("data", {})

            # Unwrap RTVI client-message format
            if msg_type == "client-message" and isinstance(data, dict):
                inner_type = data.get("t", "")
                inner_data = data.get("d", {})
                if inner_type:
                    msg_type = inner_type
                    data = inner_data

            if msg_type in ("user_image_upload", "image_upload"):
                mime_type = data.get("mime_type", "image/jpeg")
                image_url = data.get("image_url")
                image_id = data.get("image_id", "")

                if image_url:
                    await handle_image_upload_url(
                        image_url, image_id, mime_type,
                        bot_session, transcript_manager, task,
                    )
                else:
                    image_b64 = data.get("image", "")
                    if image_b64:
                        await handle_image_upload_base64(
                            image_b64, mime_type,
                            bot_session, transcript_manager, task,
                        )
                    else:
                        logger.warning("Received image_upload with no image data or URL")

        except Exception as e:
            logger.error(f"Error handling app message: {e}", exc_info=True)

    # --- No-user timeout ---
    cleanup_coro = create_cleanup_if_no_user(bot_session, task)
    bot_session.no_user_timeout_task = asyncio.create_task(cleanup_coro())

    # --- Run ---
    runner = PipelineRunner()
    await runner.run(task)

if __name__ == "__main__":
    asyncio.run(main())


Found it. The bot uses **Deepgram STT** (`DeepgramSTTService` with `nova-2` model). Let me check if the bot subprocess is even running and check its logs.


Bash

# Check ALL pods for bot logs
for pod in $(kubectl get pods -l app=maya-global-backend -o jsonpath='{.items[*].metadata.name}'); do
  echo "=== $pod ==="
  kubectl exec $pod -- ls /app/logs/ 2>&1
done

Find bot logs across all pods


=== maya-global-backend-55b69697f4-8nlm6 ===
server.log
=== maya-global-backend-55b69697f4-ggvk7 ===
bot_31571218.log
bot_c5c4addc.log
bot_e305a711.log
server.log
=== maya-global-backend-55b69697f4-hwj6b ===
server.log
=== maya-global-backend-55b69697f4-nfxsd ===
server.log
=== maya-global-backend-55b69697f4-qvpvb ===
bot_a13d83bf.log
server.log


kubectl exec maya-global-backend-55b69697f4-ggvk7 -- cat /app/logs/bot_e305a711.log 2>&1

Read bot log for the session


<persisted-output>
Output too large (391.3KB). Full output saved to: /home/ubuntu/.claude/projects/-home-ubuntu/c0539746-4005-4f9b-9479-9ee34a9f1610/tool-results/b5xy85pzb.txt

Preview (first 2KB):
2026-03-26 12:00:18.175 | INFO     | pipecat:<module>:14 - ᓚᘏᗢ Pipecat 0.0.107 (Python 3.10.20 (main, Mar 16 2026, 23:08:38) [GCC 12.2.0]) ᓚᘏᗢ
PyTorch was not found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.api_core once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.api_core past that date.
  warnings.warn(message, FutureWarning)
/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.cloud.speech_v2 once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.cloud.speech_v2 past that date.
  warnings.warn(message, FutureWarning)
/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.cloud.texttospeech_v1 once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.cloud.texttospeech_v1 past that date.
  warnings.warn(message, FutureWarning)
/app/bot/core/main.py:27: DeprecationWarning: Module `pipecat.services.google` is deprecated, use `pipecat.services.google.[frames,image,llm,openai,vertex,rtvi,stt,tts]` instead.
  from pipecat.services.google.llm_vertex import GoogleVertexLLMService
...
</persisted-output>

2026-03-26 12:00:18.175 | INFO     | pipecat:<module>:14 - ᓚᘏᗢ Pipecat 0.0.107 (Python 3.10.20 (main, Mar 16 2026, 23:08:38) [GCC 12.2.0]) ᓚᘏᗢ
PyTorch was not found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.
/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.api_core once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.api_core past that date.
  warnings.warn(message, FutureWarning)
/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.cloud.speech_v2 once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.cloud.speech_v2 past that date.
  warnings.warn(message, FutureWarning)
/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.cloud.texttospeech_v1 once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.cloud.texttospeech_v1 past that date.
  warnings.warn(message, FutureWarning)
/app/bot/core/main.py:27: DeprecationWarning: Module `pipecat.services.google` is deprecated, use `pipecat.services.google.[frames,image,llm,openai,vertex,rtvi,stt,tts]` instead.
  from pipecat.services.google.llm_vertex import GoogleVertexLLMService
/app/bot/core/main.py:27: DeprecationWarning: Module `pipecat.services.google.llm_vertex` is deprecated, use `pipecat.services.google.vertex.llm` instead.
  from pipecat.services.google.llm_vertex import GoogleVertexLLMService
/app/bot/core/main.py:28: DeprecationWarning: Module `pipecat.transports.services.daily` is deprecated, use `pipecat.transports.daily.transport` instead.
  from pipecat.transports.services.daily import DailyParams, DailyTransport
/app/app/__init__.py:10: DeprecationWarning: Module `pipecat.transports.services.helpers.daily_rest` is deprecated, use `pipecat.transports.daily.utils` instead.
  from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper
2026-03-26 12:00:23.319 | INFO     | __main__:main:103 - PERSONA_LOADED: id=image_gen name=Image Creator tools=['cf_image']
2026-03-26 12:00:23.319 | INFO     | __main__:main:118 - BOT_START: room=https://mayaresearch.daily.co/gERfOQbCThY7t4VJYobT session_id=e305a711 user_id=101732852492948294387
2026-03-26 12:00:23.319 | DEBUG    | pipecat.audio.vad.silero:__init__:147 - Loading Silero VAD model...
2026-03-26 12:00:23.395 | DEBUG    | pipecat.audio.vad.silero:__init__:169 - Loaded Silero VAD
/app/bot/core/main.py:164: DeprecationWarning: The `model` parameter is deprecated. Use `settings=GoogleVertexLLMService.Settings(model=...)` instead. If both are provided, `settings` takes precedence.
  llm = GoogleVertexLLMService(
/app/bot/core/main.py:164: DeprecationWarning: The `system_instruction` parameter is deprecated. Use `settings=GoogleVertexLLMService.Settings(system_instruction=...)` instead. If both are provided, `settings` takes precedence.
  llm = GoogleVertexLLMService(
/app/bot/core/main.py:164: DeprecationWarning: The `params` parameter is deprecated. Use `settings=GoogleVertexLLMService.Settings(...)` instead. If both are provided, `settings` takes precedence.
  llm = GoogleVertexLLMService(
/app/bot/core/main.py:177: DeprecationWarning: The `voice` parameter is deprecated. Use `settings=DeepgramTTSService.Settings(voice=...)` instead. If both are provided, `settings` takes precedence.
  tts = DeepgramTTSService(
/app/bot/core/main.py:198: DeprecationWarning: OpenAILLMContext is deprecated and will be removed in a future version. Use the universal LLMContext and LLMContextAggregatorPair instead. See OpenAILLMContext docstring for migration guide.
  context = OpenAILLMContext(
2026-03-26 12:00:23.628 | DEBUG    | pipecat.services.google.llm:upgrade_to_google:283 - Upgrading to Google: <pipecat.processors.aggregators.openai_llm_context.OpenAILLMContext object at 0x7efd8cfd3160>
/usr/local/lib/python3.10/site-packages/pipecat/processors/aggregators/llm_response.py:326: DeprecationWarning: GoogleUserContextAggregator (likely created with create_context_aggregator()) is deprecated and will be removed in a future version. Use the universal LLMContext and LLMContextAggregatorPair instead. See OpenAILLMContext docstring for migration guide.
  super().__init__(**kwargs)
/usr/local/lib/python3.10/site-packages/pipecat/processors/aggregators/llm_response.py:326: DeprecationWarning: GoogleAssistantContextAggregator (likely created with create_context_aggregator()) is deprecated and will be removed in a future version. Use the universal LLMContext and LLMContextAggregatorPair instead. See OpenAILLMContext docstring for migration guide.
  super().__init__(**kwargs)
/usr/local/lib/python3.10/site-packages/pipecat/processors/transcript_processor.py:294: DeprecationWarning: `TranscriptProcessor` is deprecated and will be removed in a future version. Use `LLMUserAggregator`'s and `LLMAssistantAggregator`'s new events instead.
  warnings.warn(
/usr/local/lib/python3.10/site-packages/pipecat/transports/base_input.py:152: DeprecationWarning: Parameter 'vad_analyzer' is deprecated. Use `LLMUserAggregator`'s `vad_analyzer` parameter, or `VADProcessor` if no `LLMUserAggregator` is needed.
  warnings.warn(
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking Pipeline#0::Source -> DailyInputTransport#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking DailyInputTransport#0 -> DeepgramSTTService#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking DeepgramSTTService#0 -> UserTranscriptProcessor#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking UserTranscriptProcessor#0 -> GoogleUserContextAggregator#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking GoogleUserContextAggregator#0 -> RTVIProcessor#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking RTVIProcessor#0 -> GoogleVertexLLMService#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking GoogleVertexLLMService#0 -> DeepgramTTSService#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking DeepgramTTSService#0 -> DailyOutputTransport#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking DailyOutputTransport#0 -> AudioBufferProcessor#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking AudioBufferProcessor#0 -> AssistantTranscriptProcessor#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking AssistantTranscriptProcessor#0 -> GoogleAssistantContextAggregator#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking GoogleAssistantContextAggregator#0 -> Pipeline#0::Sink
2026-03-26 12:00:23.630 | WARNING  | pipecat.pipeline.task:__init__:348 - PipelineTask#0: RTVIProcessor and RTVIObserver found, skipping default ones. They are both added by default, no need to add them yourself.
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking PipelineTask#0::Source -> Pipeline#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking Pipeline#0 -> PipelineTask#0::Sink
2026-03-26 12:00:23.631 | INFO     | __main__:main:261 - TOOL_REGISTERED: cf_image for persona=image_gen
2026-03-26 12:00:23.631 | DEBUG    | pipecat.pipeline.runner:run:71 - Runner PipelineRunner#0 started running PipelineTask#0
2026-03-26 12:00:23.632 | DEBUG    | pipecat.pipeline.task:_wait_for_pipeline_start:745 - PipelineTask#0: Starting. Waiting for StartFrame#0 to reach the end of the pipeline...
2026-03-26 12:00:23.632 | DEBUG    | pipecat.audio.vad.vad_analyzer:set_params:156 - Setting VAD params to: confidence=0.7 start_secs=0.2 stop_secs=0.5 min_volume=0.6
2026-03-26 12:00:23.632 | DEBUG    | pipecat.transports.daily.transport:start:838 - Creating custom audio source, auto silence True
2026-03-26 12:00:23.633 | INFO     | pipecat.transports.daily.transport:join:857 - Joining https://mayaresearch.daily.co/gERfOQbCThY7t4VJYobT
2026-03-26 12:00:23.633 | DEBUG    | pipecat.services.deepgram.stt:_connect:643 - Connecting to Deepgram
2026-03-26 12:00:23.650 | DEBUG    | pipecat.services.deepgram.tts:_connect_websocket:216 - Connecting to Deepgram WebSocket
2026-03-26 12:00:24.226 | DEBUG    | pipecat.services.deepgram.tts:_connect_websocket:233 - DeepgramTTSService#0: Websocket connection initialized: {"headers": {'dg-project-id': '62ab3718-2c0b-488a-beda-e2c34071f676', 'dg-request-id': 'a6e3bc80-b051-486a-8538-31ae31158f96'}}
2026-03-26 12:00:24.228 | DEBUG    | pipecat.pipeline.task:_wait_for_pipeline_start:748 - PipelineTask#0: StartFrame#0 reached the end of the pipeline, pipeline is now ready.
2026-03-26 12:00:24.356 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:25.065 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:25.313 | INFO     | pipecat.transports.daily.transport:join:878 - Joined https://mayaresearch.daily.co/gERfOQbCThY7t4VJYobT. Participant ID: 0794eb61-c23d-4a93-9316-dfc2f0a3d898, Meeting ID: 63f560dd-8df0-48e7-807a-bfffd219c012
2026-03-26 12:00:25.315 | DEBUG    | pipecat.transports.daily.transport:start_audio_in_streaming:1807 - Start receiving audio
2026-03-26 12:00:25.752 | INFO     | pipecat.transports.daily.transport:_on_participant_joined:2907 - Participant joined 1810191a-f174-45a6-b147-9cbad3dfd295
2026-03-26 12:00:25.753 | DEBUG    | pipecat.transports.daily.transport:capture_participant_audio:1229 - Starting to capture [microphone] audio from participant 1810191a-f174-45a6-b147-9cbad3dfd295
2026-03-26 12:00:25.753 | INFO     | bot.core.session_handlers:on_first_participant_joined:79 - USER_JOINED: session_id=e305a711 user_id=101732852492948294387
2026-03-26 12:00:26.623 | INFO     | app.services.usage:get_usage_state:70 - USAGE_LOADED: user_id=101732852492948294387 tier=free plan_img=3/5 plan_vid=0/0 plan_ppt=0/0 topup_img=0 topup_vid=0
2026-03-26 12:00:26.624 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
<string>:4: DeprecationWarning: OpenAILLMContextFrame is deprecated and will be removed in a future version. Use LLMContextFrame with the universal `LLMContext` and `LLMContextAggregatorPair` instead. See OpenAILLMContext docstring for migration guide.
2026-03-26 12:00:26.629 | DEBUG    | pipecat.processors.frameworks.rtvi.processor:_handle_client_ready:408 - Received client-ready: version 1.2.0
2026-03-26 12:00:26.629 | DEBUG    | pipecat.processors.frameworks.rtvi.processor:_handle_client_ready:415 - Client Details: library='@pipecat-ai/client-js' library_version='1.6.1' platform=None platform_version=None platform_details=None
2026-03-26 12:00:26.632 | DEBUG    | pipecat.services.google.llm:_stream_content_specific_context:1059 - GoogleVertexLLMService#0: Generating chat from LLM-specific context [You are Maya, an AI image creation assistant. You specialize in generating and editing images.

You have these tools available:
1. cf_image: Generate or edit images.
   - Use operation "generate" to create new images from text descriptions.
   - Use operation "edit" to modify an uploaded or previously generated image.
   CRITICAL: Do NOT call this function again if generation/edit is already in progress.

Remember to:
- Be conversational and friendly
- Ask clarifying questions about what the user wants to create
- Suggest improvements to prompts for better results
- Handle errors gracefully
- Never output asterisks or markdown formatting
- You are talking to the user over a voice call

Start by introducing yourself and asking what image the user would like to create.] | [{'parts': [{'text': 'Start the Conversation :'}], 'role': 'user'}]
2026-03-26 12:00:26.632 | DEBUG    | pipecat.adapters.base_llm_adapter:from_standard_tools:127 - Retrieving the tools using the adapter: <class 'pipecat.adapters.services.gemini_adapter.GeminiLLMAdapter'>
2026-03-26 12:00:26.632 | WARNING  | pipecat.services.google.llm:_stream_content:1020 - GoogleVertexLLMService#0: Both system_instruction and a system message in context are set. Using system_instruction.
2026-03-26 12:00:27.501 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:27.532 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_ttfb_metrics:143 - GoogleVertexLLMService#0 TTFB: 0.899s
2026-03-26 12:00:27.579 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_text_aggregation_metrics:231 - DeepgramTTSService#0 text aggregation time: 9.965896606445312e-05
2026-03-26 12:00:27.580 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [Hello! ]
2026-03-26 12:00:27.580 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.000s
2026-03-26 12:00:27.605 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_text_aggregation_metrics:231 - DeepgramTTSService#0 text aggregation time: 8.893013000488281e-05
2026-03-26 12:00:27.605 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [I'm Maya, your AI image creation assistant. ]
2026-03-26 12:00:27.606 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.001s
2026-03-26 12:00:27.770 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_text_aggregation_metrics:231 - DeepgramTTSService#0 text aggregation time: 7.128715515136719e-05
2026-03-26 12:00:27.770 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [I can help you generate new images or edit existing ones. ]
2026-03-26 12:00:27.770 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.000s
2026-03-26 12:00:27.797 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:start_llm_usage_metrics:196 - GoogleVertexLLMService#0 prompt tokens: 265, completion tokens: 37
2026-03-26 12:00:27.798 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [What kind of image would you like to create today?
 ]
2026-03-26 12:00:27.798 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.000s
<string>:4: DeprecationWarning: OpenAILLMContextFrame is deprecated and will be removed in a future version. Use LLMContextFrame with the universal `LLMContext` and `LLMContextAggregatorPair` instead. See OpenAILLMContext docstring for migration guide.
<string>:4: DeprecationWarning: OpenAILLMContextAssistantTimestampFrame is deprecated and will be removed in a future version. Use LLMContextAssistantTimestampFrame with the universal LLMContext and LLMContextAggregatorPair instead. See OpenAILLMContext docstring for migration guide.
2026-03-26 12:00:27.816 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_ttfb_metrics:143 - DeepgramTTSService#0 TTFB: 0.236s
2026-03-26 12:00:27.835 | DEBUG    | pipecat.transports.base_output:_bot_started_speaking:631 - Bot started speaking
2026-03-26 12:00:28.382 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:29.254 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:30.126 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:30.995 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:31.868 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:32.631 | DEBUG    | pipecat.services.tts_service:push_frame:817 - DeepgramTTSService#0 cleaning up TTS context 9b777ea3-554b-4b64-8500-2d4ed3a4226c
2026-03-26 12:00:32.748 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:33.619 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:34.491 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:35.361 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:36.241 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:36.951 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:37.936 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:38.284 | DEBUG    | pipecat.transports.base_output:_handle_frame:725 - Bot stopped speaking based on TTSStoppedFrame
2026-03-26 12:00:38.284 | DEBUG    | pipecat.transports.base_output:_bot_stopped_speaking:659 - Bot stopped speaking
<string>:7: DeprecationWarning: TranscriptionMessage is deprecated and will be removed in a future version. Use `LLMUserAggregator`'s and `LLMAssistantAggregator`'s new events instead.
<string>:4: DeprecationWarning: TranscriptionUpdateFrame is deprecated and will be removed in a future version. Use `LLMUserAggregator`'s and `LLMAssistantAggregator`'s new events instead.
2026-03-26 12:00:38.285 | DEBUG    | bot.core.session_handlers:on_transcript_update:58 - TRANSCRIPT: session_id=e305a711 role=assistant content=Hello! I'm Maya, your AI image creation assistant. I can help you generate new images or edit existing ones. What kind of image would you like to create today?
<string>:4: DeprecationWarning: OpenAILLMContextFrame is deprecated and will be removed in a future version. Use LLMContextFrame with the universal `LLMContext` and `LLMContextAggregatorPair` instead. See OpenAILLMContext docstring for migration guide.
<string>:4: DeprecationWarning: OpenAILLMContextAssistantTimestampFrame is deprecated and will be removed in a future version. Use LLMContextAssistantTimestampFrame with the universal LLMContext and LLMContextAggregatorPair instead. See OpenAILLMContext docstring for migration guide.
2026-03-26 12:00:38.653 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:39.363 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:39.445 | DEBUG    | pipecat.transports.base_input:_deprecated_handle_user_interruption:548 - User started speaking
2026-03-26 12:00:39.445 | DEBUG    | pipecat.processors.frame_processor:broadcast_interruption:765 - DailyInputTransport#0: broadcasting interruption
2026-03-26 12:00:39.446 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramSTTService#0 processing time: 0.001s
2026-03-26 12:00:40.345 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:41.328 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:42.039 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:42.749 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runti


grep -iE "error|fail|deepgram|stt|transcri|speech|audio_in|no audio|connection|websocket|401|403|api_key" /home/ubuntu/.claude/projects/-home-ubuntu/c0539746-4005-4f9b-9479-9ee34a9f1610/tool-results/b5xy85pzb.txt | head -40

Search bot log for STT/Deepgram errors


/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.cloud.speech_v2 once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.cloud.speech_v2 past that date.
/usr/local/lib/python3.10/site-packages/google/api_core/_python_version_support.py:275: FutureWarning: You are using a Python version (3.10.20) which Google will stop supporting in new releases of google.cloud.texttospeech_v1 once it reaches its end of life (2026-10-04). Please upgrade to the latest Python version, or at least Python 3.11, to continue receiving updates for google.cloud.texttospeech_v1 past that date.
/app/bot/core/main.py:27: DeprecationWarning: Module `pipecat.services.google` is deprecated, use `pipecat.services.google.[frames,image,llm,openai,vertex,rtvi,stt,tts]` instead.
/app/bot/core/main.py:177: DeprecationWarning: The `voice` parameter is deprecated. Use `settings=DeepgramTTSService.Settings(voice=...)` instead. If both are provided, `settings` takes precedence.
  tts = DeepgramTTSService(
/usr/local/lib/python3.10/site-packages/pipecat/processors/transcript_processor.py:294: DeprecationWarning: `TranscriptProcessor` is deprecated and will be removed in a future version. Use `LLMUserAggregator`'s and `LLMAssistantAggregator`'s new events instead.
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking DailyInputTransport#0 -> DeepgramSTTService#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking DeepgramSTTService#0 -> UserTranscriptProcessor#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking UserTranscriptProcessor#0 -> GoogleUserContextAggregator#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking GoogleVertexLLMService#0 -> DeepgramTTSService#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking DeepgramTTSService#0 -> DailyOutputTransport#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking AudioBufferProcessor#0 -> AssistantTranscriptProcessor#0
2026-03-26 12:00:23.630 | DEBUG    | pipecat.processors.frame_processor:link:589 - Linking AssistantTranscriptProcessor#0 -> GoogleAssistantContextAggregator#0
2026-03-26 12:00:23.633 | DEBUG    | pipecat.services.deepgram.stt:_connect:643 - Connecting to Deepgram
2026-03-26 12:00:23.650 | DEBUG    | pipecat.services.deepgram.tts:_connect_websocket:216 - Connecting to Deepgram WebSocket
2026-03-26 12:00:24.226 | DEBUG    | pipecat.services.deepgram.tts:_connect_websocket:233 - DeepgramTTSService#0: Websocket connection initialized: {"headers": {'dg-project-id': '62ab3718-2c0b-488a-beda-e2c34071f676', 'dg-request-id': 'a6e3bc80-b051-486a-8538-31ae31158f96'}}
2026-03-26 12:00:24.356 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:25.065 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:25.315 | DEBUG    | pipecat.transports.daily.transport:start_audio_in_streaming:1807 - Start receiving audio
2026-03-26 12:00:26.624 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
- Handle errors gracefully
2026-03-26 12:00:27.501 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:27.579 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_text_aggregation_metrics:231 - DeepgramTTSService#0 text aggregation time: 9.965896606445312e-05
2026-03-26 12:00:27.580 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [Hello! ]
2026-03-26 12:00:27.580 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.000s
2026-03-26 12:00:27.605 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_text_aggregation_metrics:231 - DeepgramTTSService#0 text aggregation time: 8.893013000488281e-05
2026-03-26 12:00:27.605 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [I'm Maya, your AI image creation assistant. ]
2026-03-26 12:00:27.606 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.001s
2026-03-26 12:00:27.770 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_text_aggregation_metrics:231 - DeepgramTTSService#0 text aggregation time: 7.128715515136719e-05
2026-03-26 12:00:27.770 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [I can help you generate new images or edit existing ones. ]
2026-03-26 12:00:27.770 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.000s
2026-03-26 12:00:27.798 | DEBUG    | pipecat.services.deepgram.tts:run_tts:341 - DeepgramTTSService#0: Generating TTS [What kind of image would you like to create today?
2026-03-26 12:00:27.798 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_processing_metrics:175 - DeepgramTTSService#0 processing time: 0.000s
2026-03-26 12:00:27.816 | DEBUG    | pipecat.processors.metrics.frame_processor_metrics:stop_ttfb_metrics:143 - DeepgramTTSService#0 TTFB: 0.236s
2026-03-26 12:00:28.382 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:29.254 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:30.126 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:30.995 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:31.868 | WARNING  | pipecat.services.deepgram.stt:_connection_handler:688 - DeepgramSTTService#0: Connection lost, will retry: headers: {'User-Agent': 'deepgram-sdk/6.0.1', 'X-Fern-Language': 'Python', 'X-Fern-Runtime': 'python/3.10.20', 'X-Fern-Platform': 'linux/6.12.55+', 'X-Fern-SDK-Name': 'deepgram-sdk', 'X-Fern-SDK-Version': '6.0.1', 'x-deepgram-session-id': '42f10835-a15a-438e-b5a5-1cd8c27c3001', 'Authorization': 'Token fc5b8fb135a99ebceebe0e8dcc8d4fdee8c4871d'}, status_code: 400, body: Unexpected error when initializing websocket connection.
2026-03-26 12:00:32.631 | DEBUG    | pipecat.services.tts_service:push_frame:817 - DeepgramTTSService#0 cleaning up TTS context 9b777ea3-554b-4b64-8500-2d4ed3a4226c


Found the issue. **Deepgram STT is failing** — it keeps retrying and getting `status_code: 400`:

```
DeepgramSTTService#0: Connection lost, will retry: ... status_code: 400,
body: Unexpected error when initializing websocket connection.
```

The Deepgram API key (`fc5b8fb...`) is being sent but the connection fails with 400. TTS works fine (same key), so the key itself is valid. The issue is likely the **STT websocket connection parameters**.

Let me check what's different vs the pipecat deployment.


grep -r "DeepgramSTTService" /home/ubuntu/pipecat/src/ 2>/dev/null | head -10

Check pipecat's Deepgram STT config


grep -r "SAMPLE_RATE\|sample_rate\|audio_in_sample_rate" /home/ubuntu/pipecat/src/ 2>/dev/null | head -10

Check pipecat's sample rate config


(Bash completed with no output)


/home/ubuntu/pipecat/src/config/constants.py:SAMPLE_RATE = 24000
/home/ubuntu/pipecat/src/config/constants.py:SAMPLE_RATE_16K = 16000
/home/ubuntu/pipecat/src/bot/core/pipeline.py:            audio_in_sample_rate=constants.SAMPLE_RATE_16K,
/home/ubuntu/pipecat/src/bot/core/pipeline.py:            audio_out_sample_rate=constants.SAMPLE_RATE_16K,
/home/ubuntu/pipecat/src/bot/core/pipeline.py:        sample_rate=constants.SAMPLE_RATE_16K,
/home/ubuntu/pipecat/src/bot/core/pipeline.py:        sample_rate=constants.SAMPLE_RATE_16K,
/home/ubuntu/pipecat/src/bot/core/pipeline.py:        sample_rate=constants.SAMPLE_RATE,
/home/ubuntu/pipecat/src/bot/core/pipeline.py:        sample_rate=constants.SAMPLE_RATE,
/home/ubuntu/pipecat/src/bot/core/pipeline.py:        buffer_size=constants.SAMPLE_RATE * 2 * constants.CHUNK_DURATION,
/home/ubuntu/pipecat/src/bot/core/session_handlers.py:    async def on_audio_data(buffer, audio, sample_rate, num_channels):


/home/ubuntu/pipecat/src/bot/core/config.py-150-        self.video_enabled = False  # NEW: Track if video generation is enabled for this session
/home/ubuntu/pipecat/src/bot/core/config.py-151-        self.is_processing = False  # NEW: In-memory lock for video/image processing (prevents concurrent requests)
/home/ubuntu/pipecat/src/bot/core/config.py-152-        self.generated_videos: List[Dict] = []  # Store generated videos to save at session end
/home/ubuntu/pipecat/src/bot/core/config.py-153-
/home/ubuntu/pipecat/src/bot/core/config.py-154-        # Pipeline service references for explicit cleanup (prevents memory leaks)
/home/ubuntu/pipecat/src/bot/core/config.py:155:        self.stt_service = None       # STT service (Deepgram/OpenAI WebSocket)
/home/ubuntu/pipecat/src/bot/core/config.py-156-        self.llm_service = None       # LLM service (Gemini/OpenAI HTTP client)
/home/ubuntu/pipecat/src/bot/core/config.py-157-        self.transport = None         # Daily WebRTC transport
/home/ubuntu/pipecat/src/bot/core/config.py-158-        self.pipeline = None          # Pipeline object
/home/ubuntu/pipecat/src/bot/core/config.py-159-        self.context_aggregator = None  # LLM context (grows with conversation)
/home/ubuntu/pipecat/src/bot/core/config.py-160-


No matches found