Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cb358e80d | ||
|
|
2f4a9795b5 | ||
|
|
48d1fabba1 | ||
|
|
66652845d8 |
@@ -84,9 +84,9 @@ AI_VOICE_TTS_CACHE_DIR=/app/.data_local/ai_voice_tts_cache
|
||||
AI_VOICE_TTS_ELEVENLABS_API_KEY=sk_1c50faab05df13fd0ecde9aeca4abadcc7269ffdc15a03ad
|
||||
AI_VOICE_TTS_ELEVENLABS_API_BASE=https://api.elevenlabs.io
|
||||
AI_VOICE_TTS_ELEVENLABS_MODEL_ID=eleven_turbo_v2_5
|
||||
AI_VOICE_TTS_ELEVENLABS_RU_VOICE_ID=0ArNnoIAWKlT4WweaVMY
|
||||
AI_VOICE_TTS_ELEVENLABS_RU_VOICE_ID=ut2XM2wJyIZLTtW6lFzZ
|
||||
AI_VOICE_TTS_ELEVENLABS_RU_LANGUAGE_CODE=ru
|
||||
AI_VOICE_TTS_ELEVENLABS_KK_VOICE_ID=0ArNnoIAWKlT4WweaVMY
|
||||
AI_VOICE_TTS_ELEVENLABS_KK_VOICE_ID=ut2XM2wJyIZLTtW6lFzZ
|
||||
AI_VOICE_TTS_ELEVENLABS_KK_LANGUAGE_CODE=kk
|
||||
AI_VOICE_TTS_ELEVENLABS_OUTPUT_FORMAT=pcm_16000
|
||||
AI_VOICE_TTS_YANDEX_API_KEY=AQWJUMiUaXmbegxN4kgvM2XIlNqAPoBR5Wtq-40
|
||||
|
||||
@@ -482,7 +482,7 @@ class AudioSocketMediaRuntime:
|
||||
if ack_kind == "handoff":
|
||||
return (
|
||||
"Угу, секунду.",
|
||||
"Понял вас, соединяю.",
|
||||
"Понялa вас, соединяю.",
|
||||
"Мхм, соединяю.",
|
||||
"Хорошо, сейчас соединю.",
|
||||
"Да, сейчас соединю.",
|
||||
@@ -492,7 +492,7 @@ class AudioSocketMediaRuntime:
|
||||
return (
|
||||
"Угу, сейчас подскажу.",
|
||||
"Мхм, сориентирую.",
|
||||
"Ага, понял вас.",
|
||||
"Ага, понялa вас.",
|
||||
"Хм, сейчас уточню.",
|
||||
"Хорошо, сейчас подскажу.",
|
||||
"Ясно, сейчас разберусь.",
|
||||
@@ -509,14 +509,12 @@ class AudioSocketMediaRuntime:
|
||||
"Дайте уточню.",
|
||||
)
|
||||
return (
|
||||
"Ага.",
|
||||
"Ясно, минутку.",
|
||||
"Угу.",
|
||||
"Хорошо, сейчас.",
|
||||
"Так, слушаю.",
|
||||
"Момент.",
|
||||
"Понял вас.",
|
||||
"Хм, сейчас гляну.",
|
||||
"Так, слушаю вас.",
|
||||
"Один момент.",
|
||||
"Поняла вас.",
|
||||
"Хмм, сейчас гляну.",
|
||||
)
|
||||
|
||||
def _select_ack_payload(
|
||||
|
||||
@@ -311,7 +311,7 @@ def _resolve_handoff_extension(
|
||||
target_level: str | None = None,
|
||||
tenant_id: str | None = None,
|
||||
required_skills: list[str] | None = None,
|
||||
) -> tuple[str, str, str | None, str | None]:
|
||||
) -> tuple[str, str, str | None, str | None, str | None]:
|
||||
bridge = _bridge_app()
|
||||
queue_code = _queue_code_for_queue_id(target_queue_id) or str(fallback_queue_code or "").strip()
|
||||
if not queue_code:
|
||||
@@ -327,7 +327,7 @@ def _resolve_handoff_extension(
|
||||
required_skills=required_skills,
|
||||
)
|
||||
if reserved_agent:
|
||||
return queue_code, reserved_agent["extension"], level, resolved_tenant_id
|
||||
return queue_code, reserved_agent["extension"], level, resolved_tenant_id, reserved_agent["agent_id"]
|
||||
raise HTTPException(status_code=409, detail=f"No available {level} agent right now")
|
||||
|
||||
extension = bridge._transfer_target_map().get(queue_code)
|
||||
@@ -336,7 +336,7 @@ def _resolve_handoff_extension(
|
||||
status_code=400,
|
||||
detail=f"Unknown transfer queue_code: {queue_code}",
|
||||
)
|
||||
return queue_code, extension, None, None
|
||||
return queue_code, extension, None, None, None
|
||||
|
||||
|
||||
def _resolve_handoff_channel(session, link: AsteriskCallLinkRow) -> str:
|
||||
@@ -546,11 +546,33 @@ def request_handoff(call_id: str, body: VoiceAIHandoffRequestIn, actor: dict) ->
|
||||
if voice_session is None:
|
||||
raise HTTPException(status_code=404, detail="Voice AI session not found")
|
||||
|
||||
queue_code, target_extension, resolved_level, resolved_tenant_id = _resolve_handoff_extension(
|
||||
queue_code, target_extension, resolved_level, resolved_tenant_id, reserved_agent_id = _resolve_handoff_extension(
|
||||
body.target_queue_id or voice_session.handoff_target_queue_id or link.queue_id,
|
||||
fallback_queue_code=_queue_code_for_queue_id(link.queue_id),
|
||||
call_id=call_id,
|
||||
)
|
||||
escalation: EscalationRow | None = None
|
||||
if reserved_agent_id and target_extension != "7100":
|
||||
now_reserved = utc_now_iso()
|
||||
escalation = EscalationRow(
|
||||
escalation_id=new_id("esc"),
|
||||
call_id=call_id,
|
||||
tenant_id=resolved_tenant_id,
|
||||
from_level=str(link.current_level or "L1"),
|
||||
to_level=resolved_level,
|
||||
reason_code="AI_HANDOFF",
|
||||
required_skills_json="[]",
|
||||
priority=3,
|
||||
status="ringing",
|
||||
real_agent_id=reserved_agent_id,
|
||||
assigned_agent_id=target_extension,
|
||||
attempted_agent_ids_json=json.dumps([reserved_agent_id], ensure_ascii=False),
|
||||
requested_at=now_reserved,
|
||||
)
|
||||
session.add(escalation)
|
||||
session.flush()
|
||||
_append_escalation_timeline(escalation, link, action="escalation.agent_reserved", extra={"agent_id": reserved_agent_id})
|
||||
_emit_escalation_event(event_type="AgentReserved", escalation=escalation, link=link, extra={"agent_id": reserved_agent_id})
|
||||
handoff_metadata = body.metadata or {}
|
||||
actor_user = str(actor.get("user") or actor.get("sub") or "ai-voice-runtime").strip()
|
||||
actor_role = str(actor.get("role") or "admin").strip() or "admin"
|
||||
@@ -597,15 +619,33 @@ def request_handoff(call_id: str, body: VoiceAIHandoffRequestIn, actor: dict) ->
|
||||
target_extension,
|
||||
body.target_queue_id or voice_session.handoff_target_queue_id or link.queue_id,
|
||||
)
|
||||
ami_result = bridge._ami_action(
|
||||
"Redirect",
|
||||
{
|
||||
"Channel": channel,
|
||||
"Context": bridge._transfer_context(),
|
||||
"Exten": target_extension,
|
||||
"Priority": 1,
|
||||
},
|
||||
)
|
||||
try:
|
||||
ami_result = bridge._ami_action(
|
||||
"Redirect",
|
||||
{
|
||||
"Channel": channel,
|
||||
"Context": bridge._transfer_context(),
|
||||
"Exten": target_extension,
|
||||
"Priority": 1,
|
||||
},
|
||||
)
|
||||
except Exception as exc:
|
||||
if escalation is not None:
|
||||
try:
|
||||
routing_release_by_agent_id(reserved_agent_id)
|
||||
except Exception:
|
||||
LOGGER.warning("bridge.handoff_release_agent_failed call_id=%s agent_id=%s", call_id, reserved_agent_id)
|
||||
escalation.status = "failed"
|
||||
escalation.completed_at = utc_now_iso()
|
||||
session.commit()
|
||||
_append_escalation_timeline(escalation, link, action="escalation.transfer_failed", extra={"agent_id": reserved_agent_id, "error": str(exc)})
|
||||
_emit_escalation_event(event_type="TransferFailed", escalation=escalation, link=link, extra={"agent_id": reserved_agent_id, "error": str(exc)})
|
||||
raise HTTPException(status_code=502, detail=f"Failed to redirect call to agent: {exc}") from exc
|
||||
|
||||
if escalation is not None:
|
||||
set_routing_agent_status(reserved_agent_id, "RINGING")
|
||||
_append_escalation_timeline(escalation, link, action="escalation.agent_ringing", extra={"agent_id": reserved_agent_id})
|
||||
_emit_escalation_event(event_type="AgentRinging", escalation=escalation, link=link, extra={"agent_id": reserved_agent_id})
|
||||
|
||||
now = utc_now_iso()
|
||||
link.voice_session_id = (
|
||||
|
||||
Reference in New Issue
Block a user