@@ -6,12 +6,10 @@ import logging
|
||||
import os
|
||||
import uuid
|
||||
from contextlib import asynccontextmanager
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import FastAPI, WebSocket
|
||||
|
||||
from realtime_voice_service import crm_client
|
||||
from realtime_voice_service import sales_sync_client
|
||||
from realtime_voice_service.core.filler_audio import FillerAudioLibrary
|
||||
from realtime_voice_service.core.session import CallSession
|
||||
from realtime_voice_service.core.session import _normalize_voice_pronunciation
|
||||
@@ -150,52 +148,6 @@ def _llm_model_name() -> str:
|
||||
return str(os.getenv("OPENAI_LLM_MODEL", "gpt-4o-mini")).strip() or "gpt-4o-mini"
|
||||
|
||||
|
||||
def _utc_now_iso() -> str:
|
||||
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
|
||||
|
||||
def _conversation_transcript(session: CallSession) -> str | None:
|
||||
lines = [f"{speaker}: {text.strip()}" for speaker, text in session.conversation if str(text).strip()]
|
||||
if not lines:
|
||||
return None
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _conversation_summary(session: CallSession) -> str | None:
|
||||
user_turns = [text.strip() for speaker, text in session.conversation if speaker == "user" and str(text).strip()]
|
||||
assistant_turns = [text.strip() for speaker, text in session.conversation if speaker == "assistant" and str(text).strip()]
|
||||
if not user_turns and not assistant_turns:
|
||||
return None
|
||||
parts: list[str] = []
|
||||
if user_turns:
|
||||
parts.append(f"Customer asked: {user_turns[0][:220]}")
|
||||
if assistant_turns:
|
||||
parts.append(f"Assistant response: {assistant_turns[-1][:220]}")
|
||||
if session.customer_name:
|
||||
parts.append(f"Customer name: {session.customer_name}")
|
||||
return " | ".join(parts)[:700]
|
||||
|
||||
|
||||
def _session_sync_metadata(
|
||||
session: CallSession,
|
||||
transport: BaseMediaTransport,
|
||||
*,
|
||||
failure_reason: str | None = None,
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"protocol": transport.protocol,
|
||||
"sample_rate_hz": transport.sample_rate_hz,
|
||||
"frame_duration_ms": transport.frame_duration_ms,
|
||||
"frame_bytes": transport.frame_bytes,
|
||||
"session_language": session.session_language,
|
||||
"customer_name": session.customer_name,
|
||||
"interruptions": list(session.interruptions),
|
||||
"latency_ms": dict(session.last_latency_ms),
|
||||
"conversation_entries": len(session.conversation),
|
||||
"failure_reason": failure_reason,
|
||||
}
|
||||
|
||||
|
||||
|
||||
class RealtimeVoiceService:
|
||||
def __init__(self) -> None:
|
||||
@@ -324,53 +276,19 @@ class RealtimeVoiceService:
|
||||
|
||||
async def _run_transport_session(self, transport: BaseMediaTransport) -> None:
|
||||
session = self._build_session(transport)
|
||||
started_at = _utc_now_iso()
|
||||
interaction_id = await crm_client.create_interaction(call_id=session.session_id)
|
||||
await sales_sync_client.sync_voice_session(
|
||||
call_id=session.session_id,
|
||||
interaction_id=interaction_id,
|
||||
voice_session_id=session.session_id,
|
||||
ai_state=str(session.state.value).lower(),
|
||||
telephony_status="connected",
|
||||
call_status="started",
|
||||
started_at=started_at,
|
||||
summary="Realtime voice session started",
|
||||
metadata=_session_sync_metadata(session, transport),
|
||||
)
|
||||
failure_reason: str | None = None
|
||||
call_status = "completed"
|
||||
async with self._track_session(session):
|
||||
try:
|
||||
LOGGER.info(
|
||||
"starting realtime session %s via %s sample_rate=%s frame_ms=%s frame_bytes=%s",
|
||||
session.session_id,
|
||||
transport.protocol,
|
||||
transport.sample_rate_hz,
|
||||
transport.frame_duration_ms,
|
||||
transport.frame_bytes,
|
||||
)
|
||||
await session.run()
|
||||
except Exception as exc:
|
||||
call_status = "failed"
|
||||
failure_reason = str(exc)[:300]
|
||||
raise
|
||||
finally:
|
||||
await sales_sync_client.sync_voice_session(
|
||||
call_id=session.session_id,
|
||||
interaction_id=interaction_id,
|
||||
caller_name=session.customer_name,
|
||||
voice_session_id=session.session_id,
|
||||
ai_state=str(session.state.value).lower(),
|
||||
telephony_status="ended",
|
||||
call_status=call_status,
|
||||
started_at=started_at,
|
||||
ended_at=_utc_now_iso(),
|
||||
summary=_conversation_summary(session),
|
||||
transcript_text=_conversation_transcript(session),
|
||||
metadata=_session_sync_metadata(session, transport, failure_reason=failure_reason),
|
||||
)
|
||||
if interaction_id:
|
||||
await crm_client.close_interaction(interaction_id)
|
||||
LOGGER.info(
|
||||
"starting realtime session %s via %s sample_rate=%s frame_ms=%s frame_bytes=%s",
|
||||
session.session_id,
|
||||
transport.protocol,
|
||||
transport.sample_rate_hz,
|
||||
transport.frame_duration_ms,
|
||||
transport.frame_bytes,
|
||||
)
|
||||
await session.run()
|
||||
if interaction_id:
|
||||
await crm_client.close_interaction(interaction_id)
|
||||
|
||||
def _build_session(self, transport: BaseMediaTransport) -> CallSession:
|
||||
return CallSession(
|
||||
@@ -446,8 +364,6 @@ async def health() -> dict[str, object]:
|
||||
"sample_rate_hz": service.sample_rate_hz,
|
||||
"active_sessions": service.active_session_count,
|
||||
"audiosocket_port": _audiosocket_port(),
|
||||
"sales_sync_enabled": os.getenv("SALES_SYNC_ENABLED", "0"),
|
||||
"crm_interaction_enabled": os.getenv("CRM_INTERACTION_ENABLED", "0"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user