25 Commits
Author SHA1 Message Date
didar 9bf367abf4 fix: filter RU/KZ stopwords from KB search so filler words can't false-match
deploy / deploy (push) Successful in 30s
search_kb_rows had no relevance floor: any exact-token hit, however
generic, scored above zero and could win as the top/only result. A
caller utterance as thin as a bare "да" (confirming the language) could
exact-match that same common word inside an unrelated FAQ article's
body and get returned as "the" answer, which then got read back
almost verbatim — this is what surfaced live as the AI unprompted
launching into a voucher-activation explanation right after the
customer confirmed Russian, having said nothing else.

tokenize_kb_text now drops a curated set of RU/KZ greetings,
confirmations, pronouns, and particles. A stopword-only query naturally
falls through to the existing "no query tokens -> no results" path
instead of returning a coincidental match; genuine single-content-word
queries (e.g. "ваучер") are unaffected. Applies to every channel that
calls _kb_search (voice, Telegram, WhatsApp), not just voice.
2026-08-31 01:08:11 +05:00
arys 99d169ec67 fix: stop the no-answer retry from racing the dialplan hangup + fix event catalog validation
Two bugs found via a live test with two real registered browser softphones:

1. retry_escalation_no_answer() re-resolved the client channel via
   _resolve_handoff_channel() -> a live AMI CoreShowChannels round-trip that
   can take ~10s. The new mvpcc-transfer dialplan wait window (MusicOnHold,
   also ~10s, added to give the backend time to redirect before the final
   Hangup) was consistently LOST to this exact same duration: the backend's
   AMI Redirect fired against a channel the dialplan had already hung up
   ('Channel does not exist: PJSIP/...', confirmed in escalation timeline).
   Fixed by reusing the actively-maintained AsteriskCallLinkRow.channel_name
   directly (unchanged for a PJSIP channel across Redirect between contexts
   of the same call) instead of re-discovering it, falling back to the slow
   path only if that field is empty.

2. VoiceEventIn.event_type is a pydantic Literal restricted to 6 legacy
   values (call.started/ivr.completed/...). None of the Phase 2 event
   catalog names (AgentReserved/AgentRinging/AgentNoAnswer/AgentConnected/
   TransferCompleted/TransferFailed) were ever in it, so every single
   _emit_escalation_event() call has been failing with 422 since Phase 2
   shipped (swallowed silently by the broad except there) - confirmed by
   calling app._emit_voice_event() directly against the running service.
   Extended the Literal to include all six.
2026-08-31 01:03:47 +05:00
didar d2438b6954 feat: canonical intent taxonomy for AI operator (kb_answer -> intent_code)
deploy / deploy (push) Successful in 30s
Centralizes fixed control intents and adds a data-driven intent_code
field on kb_articles so many phrasings of the same FAQ question
resolve to one stable code (e.g. VOUCHER_ACTIVATION) instead of a
free-form, unvalidated string the LLM invented on the fly.

- services/shared/intents.py: CONTROL_INTENTS + normalize_intent()
- kb_articles.intent_code column (ORM + dev/sqlite runtime compat +
  migrations/sql/0034_* for postgres/sqlite)
- kb_service CRUD exposes intent_code
- orchestrator surfaces intent_code to the LLM and validates its
  intent output against control intents + the KB codes shown that turn
- voice.py: _voice_early_intent_bucket renamed to _voice_ack_topic_bucket
  to stop it being conflated with the canonical FAQ intent
2026-08-31 00:17:51 +05:00
arys 1dcfaf46cf feat: no-answer retry, agent status machine, escalation events/timeline (ТЗ §13-15,22,25-27,37, AC-08,16-19)
Phase 2 of the L1->L2 routing engine (Phase 1: MR!4).

- ami_loop() now also captures native AMI DialEnd/Hangup frames (not
  only UserEvent), needed to detect that an escalated agent did not
  answer. No dialplan change required - Redirect already routes the
  client channel into an existing Dial()-based transfer context, so
  Asterisk emits these events on its own; the listener just wasn't
  reading them before.
- retry_escalation_no_answer(): on NOANSWER/BUSY/CANCEL/CHANUNAVAIL/
  CONGESTION, releases the non-answering agent, excludes it, and
  reserves+redirects to the next available agent via the routing
  engine's existing exclude_agent_ids support. Exhausted pool marks
  the escalation failed and leaves the call with the AI instead of
  dropping the client (ТЗ §32).
- Agent status now actually moves through
  RESERVED -> RINGING -> TALKING -> AFTER_CALL_WORK -> AVAILABLE
  instead of staying stuck on RESERVED for the whole call; a new
  acw_sweep_loop background thread (same pattern as the existing
  failed_retry_loop) times out AFTER_CALL_WORK back to AVAILABLE.
- escalations gains attempt_count/real_agent_id/attempted_agent_ids_json
  (migration 0033); fixes a latent bug where assigned_agent_id stored
  the SIP extension instead of the real agent_id despite routing-service
  already returning it in RoutingAgentReserveOut.
- Every transition now records an interaction timeline entry and
  publishes the ТЗ §25 event catalog (AgentReserved/AgentRinging/
  AgentNoAnswer/AgentConnected/TransferCompleted/TransferFailed)
  through the existing emit_voice_event/EventOutboxRow idempotent path.

Not in this MR (see plan): SLA config, Callback, L3 (needs real
technical agents from the business), metrics.
2026-08-30 14:19:23 +05:00
arys 82c89eff5a feat: give aimaq voice AI a Qazaqgaz Aimaq persona (Zhanna) and language-choice greeting
Parametrize ai_operator_default_config() via AI_OPERATOR_* env vars,
falling back to the existing hardcoded defaults so the other two stacks
(call-center, sales-call-center) that share this code are unaffected.

Set aimaq-only overrides matching the client-provided script (Скрипт
ии-оператора КГА.docx): agent renamed to Жанна, company to Казакгаз
Аймак, the voice greeting now asks the caller whether Russian or
Kazakh is more convenient before anything else, and the base system
prompt instructs the model to commit to whichever language the caller
picks for the rest of the call, ask how to address them, and confirm
there's nothing else before saying goodbye.
2026-08-30 12:22:00 +05:00
Hermes Agent 2243f305b8 feat: L1->L2 agent pool and routing engine for voice escalation
Replaces the hardcoded single-extension redirect for AI->human call
escalation with a real Agent Pool + Routing Engine:

- agents/escalations/routing_rules tables (migration 0031), asterisk_call_links
  gains tenant_id/current_level/required_skills_json/priority.
- services/routing_service/engine.py: level/tenant/skill filtered agent
  selection with atomic (CAS) reservation, no double-booking.
- routing-service: /agents CRUD + /internal/routing/reserve-agent and
  /internal/routing/release-agent.
- asterisk-bridge-service: voice_ai.request_handoff now uses the Routing
  Engine automatically for any queue_code configured in
  ASTERISK_QUEUE_LEVEL_MAP_JSON (all other queue_codes keep the existing
  static ASTERISK_TRANSFER_TARGET_MAP_JSON behavior unchanged); new
  POST /asterisk/live-calls/{call_id}/escalations entrypoint; agent is
  released back to AVAILABLE and the escalation closed when the call ends.

Targets the Tele2 Kazgaz DID +77476456048 (from-tele2-kazgaz context) as the
first queue wired to real L2 routing instead of AI-only.

Known gap (documented in docs/architecture/l1-l2-routing-engine.md):
automatic no-answer retry-to-next-agent needs a small, separately reviewed
dialplan change and is left for a follow-up MR rather than guessed at blind.

Tests: services/routing_service/engine.py covered by
tests/test_routing_engine.py (selection filtering, atomic reservation,
release); existing test_asterisk_bridge_service.py and
test_routing_service_pg_counter.py suites still pass unmodified.
2026-08-28 16:22:32 +05:00
didarandClaude Sonnet 5 c5dd87ee32 feat: make ai_operator_settings config code the source of truth
Add sync_ai_operator_config_from_code() which overwrites the DB-cached
ai_operator_settings row from ai_operator_default_config() on startup
of ai_orchestrator_service and ai_voice_runtime_service. Greeting and
system prompt changes now go through git + deploy instead of manual
psql/API edits to prod. Also adds a root README pointing to existing docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 21:00:34 +05:00
didar 13ef87eb2c Fix voice greeting test marker to hit the actually-served default
deploy / deploy (push) Canceled after 25h17m2s
The previous marker was added to operator_persona.py's inline fallback,
which only fires when config is None. Every real call path loads a
populated AIOperatorConfig via load_effective_ai_operator_config(),
so ai_operator_default_config() in services/shared/ai_operator_config.py
is the default that's actually served.
2026-08-20 22:53:27 +05:00
Magzhan Zhumabayev fbc77ba8a7 Expand sales leads and customer contracts 2026-05-12 19:09:31 +05:00
Magzhan Zhumabayev ce3ba270aa Add configurable AI operator persona 2026-05-11 17:37:26 +05:00
Magzhan Zhumabayev ebba6c3f82 Allow tenant header for bearer-authenticated requests 2026-05-11 16:02:16 +05:00
Admin 8577d97356 Implement sales CRM workflow foundation 2026-05-10 20:54:01 +05:00
Magzhan Zhumabayev 866d96e560 Implement sales tenant pipeline events 2026-05-10 18:24:06 +05:00
arys f53af1f7dc sales fix 2026-05-09 09:16:06 +05:00
Yera All 39096e114f fix(voice): harden noisy streaming turns 2026-04-17 00:30:41 +05:00
Yera All 6b8f5d069b feat(ai): add rolling conversation summary memory 2026-04-13 11:59:43 +05:00
Yera All f2ece35588 fix(tests): isolate db state and stabilize voice runtime suite 2026-04-12 11:38:02 +05:00
Yera All c4c8b69494 fix(sql): backfill whatsapp chat_id in compat init 2026-04-12 01:52:27 +05:00
Yera All bdaf65a256 fix(db): avoid repeated sqlite schema initialization 2026-04-12 01:45:49 +05:00
Yera All fc976804e4 feat(voice): add admin-configurable elevenlabs tts 2026-04-11 16:46:43 +05:00
Yera All bfaec370f9 chore: remove dead supervisor analytics and archived tooling 2026-04-08 15:59:07 +05:00
Yera All 064f480341 fix(models): resolve PydanticUndefinedAnnotation forward refs 2026-04-06 23:31:17 +05:00
Yera All d959c2b2f0 Add voice name flow controls and analytics 2026-04-05 03:26:18 +05:00
Yera All 78918a38fd Improve operator CRM flows and UI 2026-04-03 23:47:34 +05:00
Yera All 5374c202d9 Initial import with GitLab CI/CD and registry deploy flow 2026-04-02 17:05:45 +05:00