- AudioSocket server (asyncio TCP, Asterisk wire protocol) bridging calls into the same M2 agent used by chat - Call session engine: greeting → energy-VAD utterance collection → STT → agent turn → TTS playback with barge-in (120ms of caller speech stops playback); inactivity + max-duration guards; unintelligible audio reaches the agent as '[nerazumljivo]' so the two-attempt rule stays in the prompt - STTProvider (faster-whisper, lang hint 'sr', GPU/CPU via env) and TTSProvider (Azure Neural raw-8k PCM / ElevenLabs Flash) + test fakes - Recording (mixed caller+agent WAV), transcripts, per-turn latency trace, metering via record_agent_call (§12) - Internal dialplan API: /internal/calls/register (ring targets computed from working hours + ring settings §5.2), /answered (human outcome, no minutes), /hangup (abandoned → missed-call SMS §5.5); shared-token auth - Asterisk config generator (pjsip.conf + extensions.conf from DB): worker endpoints, per-tenant test-caller endpoint, register→Dial→AudioSocket dialplan with h-extension reporting — validated against a real Asterisk 20 container (modules, dialplan, endpoints all load) - docker-compose 'voice' profile (voice server + Asterisk), Dockerfile.voice, docs/VOICE_TESTING.md softphone runbook - 16 new tests incl. full fake-call e2e and a real-TCP AudioSocket wire test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
56 lines
1.4 KiB
Python
56 lines
1.4 KiB
Python
"""FastAPI application assembly."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from gogo.api import actions, health, webhooks
|
|
from gogo.config import get_settings
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s"
|
|
)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
scheduler = None
|
|
if get_settings().env != "test":
|
|
from gogo.jobs import build_scheduler
|
|
|
|
scheduler = build_scheduler()
|
|
scheduler.start()
|
|
yield
|
|
if scheduler:
|
|
scheduler.shutdown(wait=False)
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
from gogo.admin.router import router as admin_router
|
|
from gogo.api.internal import router as internal_router
|
|
from gogo.chat.router import router as chat_router
|
|
from gogo.dashboard.router import router as dashboard_router
|
|
|
|
app = FastAPI(title="Gogo Telefon", docs_url=None, redoc_url=None, lifespan=lifespan)
|
|
app.include_router(health.router)
|
|
app.include_router(actions.router)
|
|
app.include_router(webhooks.router)
|
|
app.include_router(chat_router)
|
|
app.include_router(dashboard_router)
|
|
app.include_router(admin_router)
|
|
app.include_router(internal_router)
|
|
|
|
@app.get("/", include_in_schema=False)
|
|
async def root():
|
|
from fastapi.responses import RedirectResponse
|
|
|
|
return RedirectResponse("/dash")
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|