Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/aicc2025_sip-to-ai.json
T

1 line
9.1 KiB
JSON

{"repo": "aicc2025/sip-to-ai", "files": {"app/config.py": {"a990fb7f745e88d6ad5ddff2691089f5b754468b": {"t": 1785674555.1591294, "n": 8589, "h": "187af0978a32d8e9", "c": "\"\"\"Configuration module for SIP-to-AI bridge.\"\"\"\n\nimport os\nfrom dataclasses import dataclass\nfrom typing import Literal\n\nfrom dotenv import load_dotenv\n\n\nload_dotenv()\n\n\n@dataclass(frozen=True)\nclass AudioConfig:\n \"\"\"Audio processing configuration.\n\n Note: Fixed audio parameters for telephony compatibility:\n - Frame duration: 20ms (telephony standard)\n - Sample rate: 8kHz (PJSUA2 and telephony standard)\n - PJSUA2: PCM16 @ 8kHz (320 bytes/frame)\n - AI clients: Handle conversion internally (PCM16 → their format)\n \"\"\"\n\n # Fixed audio parameters (not configurable)\n FRAME_MS: int = 20 # Fixed: 20ms telephony standard\n SAMPLE_RATE: int = 8000 # Fixed: 8kHz telephony standard\n\n # Buffer configuration (configurable)\n uplink_buf_frames: int = 100\n downlink_buf_frames: int = 200\n\n @property\n def frame_ms(self) -> int:\n \"\"\"Frame duration in milliseconds.\"\"\"\n return self.FRAME_MS\n\n @property\n def sip_sr(self) -> int:\n \"\"\"SIP sample rate (compatibility alias).\"\"\"\n return self.SAMPLE_RATE\n\n @property\n def ai_sr(self) -> int:\n \"\"\"AI sample rate (compatibility alias).\"\"\"\n return self.SAMPLE_RATE\n\n @property\n def frame_size_sip(self) -> int:\n \"\"\"Samples per frame at SIP sample rate.\"\"\"\n return (self.sip_sr * self.frame_ms) // 1000\n\n @property\n def frame_size_ai(self) -> int:\n \"\"\"Samples per frame at AI sample rate.\"\"\"\n return (self.ai_sr * self.frame_ms) // 1000\n\n @property\n def bytes_per_frame_sip_pcm16(self) -> int:\n \"\"\"Bytes per frame for SIP in PCM16 format (PJSUA2 interface).\n \n Returns:\n 320 bytes for 20ms @ 8kHz PCM16\n \"\"\"\n return self.frame_size_sip * 2\n \n @property\n def bytes_per_frame_sip(self) -> int:\n \"\"\"Bytes per frame for SIP (PCM16). Alias for bytes_per_frame_sip_pcm16.\"\"\"\n return self.bytes_per_frame_sip_pcm16\n\n @property\n def bytes_per_frame_ai_g711(self) -> int:\n \"\"\"Bytes per frame for AI in G.711 format (OpenAI Realtime).\n \n Returns:\n 160 bytes for 20ms @ 8kHz G.711\n \"\"\"\n return self.frame_size_ai\n \n @property\n def bytes_per_frame_ai(self) -> int:\n \"\"\"Bytes per frame for AI (G.711). Alias for bytes_per_frame_ai_g711.\"\"\"\n return self.bytes_per_frame_ai_g711\n\n\n@dataclass(frozen=True)\nclass AIConfig:\n \"\"\"AI service configuration.\n\n Note: VAD, barge-in, and turn-taking are handled by AI services (OpenAI/Deepgram/Gemini).\n No client-side configuration needed.\n \"\"\"\n\n vendor: Literal[\"openai\", \"deepgram\", \"gemini\", \"grok\"] = \"openai\"\n\n # Agent Prompt Configuration (shared across vendors)\n # NOTE: Path can be relative (resolved from project root) or absolute\n agent_prompt_file: str = \"\" # REQUIRED: Path to YAML file with agent prompts (greeting + instructions)\n\n # OpenAI Configuration\n openai_api_key: str = \"\"\n openai_project: str = \"\"\n openai_organization: str = \"\"\n openai_ws_endpoint: str = \"wss://api.openai.com/v1/realtime\"\n openai_model: str = \"gpt-realtime\"\n\n # Deepgram Configuration\n deepgram_api_key: str = \"\"\n deepgram_listen_model: str = \"nova-2\" # STT model (nova-2, nova-3)\n deepgram_speak_model: str = \"aura-asteria-en\" # TTS voice (when speak_provider=deepgram)\n deepgram_llm_model: str = \"gpt-4o-mini\" # LLM model for agent\n\n # Speak provider: who renders the Deepgram agent's voice.\n # \"deepgram\" -> built-in Aura TTS (default)\n # \"60db\" -> Deepgram stays the brain (STT+LLM+turn-taking), 60db is the voice\n speak_provider: Literal[\"deepgram\", \"60db\"] = \"deepgram\"\n\n # 60db Voice (used when speak_provider=60db)\n sixtydb_api_key: str = \"\"\n sixtydb_voice_id: str = \"fbb75ed2-975a-40c7-9e06-38e30524a9a1\" # 60db default voice\n\n # Gemini Live Configuration\n gemini_api_key: str = \"\"\n gemini_model: str = \"gemini-3.1-flash-live-preview\" # Model with Live API support\n gemini_voice: str = \"Puck\" # Voice: Puck, Charon, Kore, Fenrir, Aoede\n\n # xAI Grok Voice Configuration\n grok_api_key: str = \"\"\n grok_ws_endpoint: str = \"wss://api.x.ai/v1/realtime\"\n grok_model: str = \"grok-voice-think-fast-1.0\"\n grok_voice: str = \"eve\" # Built-in voices: eve (default), ara, leo, rex, sal\n\n\n@dataclass(frozen=True)\nclass SIPConfig:\n \"\"\"SIP/PJSUA2 configuration for userless account (receive-only mode).\n\n Note: This configuration is for receiving incoming SIP INVITE requests only.\n No registration to SIP server required.\n Typically used in internal network, TLS not required.\n \"\"\"\n\n domain: str = \"localhost\" # SIP domain/IP for URI (useful for multi-IP servers)\n transport_type: Literal[\"udp\", \"tcp\"] = \"udp\"\n port: int = 5060\n\n\n@dataclass(frozen=True)\nclass SystemConfig:\n \"\"\"System-wide configuration.\"\"\"\n\n log_level: str = \"INFO\"\n log_format: Literal[\"json\", \"text\"] = \"json\"\n health_check_interval_sec: int = 30\n reconnect_delay_sec: int = 5\n max_reconnect_attempts: int = 3\n ai_connection_timeout_sec: int = 10 # Timeout for AI WebSocket connection\n\n\nclass Config:\n \"\"\"Main configuration class.\"\"\"\n\n def __init__(self) -> None:\n \"\"\"Initialize configuration from environment variables.\"\"\"\n self.audio = AudioConfig(\n uplink_buf_frames=int(os.getenv(\"UPLINK_BUF_FRAMES\", \"100\")),\n downlink_buf_frames=int(os.getenv(\"DOWNLINK_BUF_FRAMES\", \"200\")),\n )\n\n ai_vendor = os.getenv(\"AI_VENDOR\", \"mock\").lower()\n if ai_vendor not in [\"mock\", \"openai\", \"deepgram\", \"gemini\", \"grok\"]:\n ai_vendor = \"mock\"\n\n # Fail fast on an unknown SPEAK_PROVIDER rather than silently using the\n # Deepgram voice (which would mask a 60db misconfiguration).\n speak_provider = os.getenv(\"SPEAK_PROVIDER\", \"deepgram\").lower()\n if speak_provider not in (\"deepgram\", \"60db\"):\n raise ValueError(\n f\"SPEAK_PROVIDER must be 'deepgram' or '60db', got: {speak_provider!r}\"\n )\n\n self.ai = AIConfig(\n vendor=ai_vendor, # type: ignore\n agent_prompt_file=os.getenv(\"AGENT_PROMPT_FILE\", \"\"),\n openai_api_key=os.getenv(\"OPENAI_API_KEY\", \"\"),\n openai_project=os.getenv(\"OPENAI_PROJECT\", os.getenv(\"OPENAI_PROJECT_ID\", \"\")),\n openai_organization=os.getenv(\"OPENAI_ORGANIZATION\", os.getenv(\"OPENAI_ORG_ID\", \"\")),\n openai_ws_endpoint=os.getenv(\"OPENAI_WS_ENDPOINT\", \"wss://api.openai.com/v1/realtime\"),\n openai_model=os.getenv(\"OPENAI_MODEL\", \"gpt-realtime\"),\n deepgram_api_key=os.getenv(\"DEEPGRAM_API_KEY\", \"\"),\n deepgram_listen_model=os.getenv(\"DEEPGRAM_LISTEN_MODEL\", \"nova-2\"),\n deepgram_speak_model=os.getenv(\"DEEPGRAM_SPEAK_MODEL\", \"aura-asteria-en\"),\n deepgram_llm_model=os.getenv(\"DEEPGRAM_LLM_MODEL\", \"gpt-4o-mini\"),\n speak_provider=speak_provider, # type: ignore\n sixtydb_api_key=os.getenv(\"SIXTYDB_API_KEY\", \"\"),\n sixtydb_voice_id=os.getenv(\n \"SIXTYDB_VOICE_ID\", \"fbb75ed2-975a-40c7-9e06-38e30524a9a1\"\n ),\n gemini_api_key=os.getenv(\"GEMINI_API_KEY\", \"\"),\n gemini_model=os.getenv(\"GEMINI_MODEL\", \"gemini-3.1-flash-live-preview\"),\n gemini_voice=os.getenv(\"GEMINI_VOICE\", \"Puck\"),\n grok_api_key=os.getenv(\"XAI_API_KEY\", \"\"),\n grok_ws_endpoint=os.getenv(\"GROK_WS_ENDPOINT\", \"wss://api.x.ai/v1/realtime\"),\n grok_model=os.getenv(\"GROK_MODEL\", \"grok-voice-think-fast-1.0\"),\n grok_voice=os.getenv(\"GROK_VOICE\", \"eve\"),\n )\n\n self.sip = SIPConfig(\n domain=os.getenv(\"SIP_DOMAIN\", \"localhost\"),\n transport_type=os.getenv(\"SIP_TRANSPORT_TYPE\", \"udp\"), # type: ignore\n port=int(os.getenv(\"SIP_PORT\", \"5060\")),\n )\n\n self.system = SystemConfig(\n log_level=os.getenv(\"LOG_LEVEL\", \"INFO\"),\n log_format=os.getenv(\"LOG_FORMAT\", \"json\"), # type: ignore\n health_check_interval_sec=int(os.getenv(\"HEALTH_CHECK_INTERVAL_SEC\", \"30\")),\n reconnect_delay_sec=int(os.getenv(\"RECONNECT_DELAY_SEC\", \"5\")),\n max_reconnect_attempts=int(os.getenv(\"MAX_RECONNECT_ATTEMPTS\", \"3\")),\n ai_connection_timeout_sec=int(os.getenv(\"AI_CONNECTION_TIMEOUT_SEC\", \"10\")),\n )\n\n @classmethod\n def load(cls) -> \"Config\":\n \"\"\"Load configuration from environment.\"\"\"\n return cls()\n\n\nconfig = Config.load()\n"}}}}