b8c922c9cf1c2f2dcfa55b4592808ce0e08ce034
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
49489f89b5 |
Play a filler ack during slow voice decisions and cache KB search rows
Two independent latency fixes for the voice-assistant reply pipeline, both scoped to the parts of the flow that run regardless of whether voice_v2 is enabled for a queue: 1. media_runtime._process_utterance: the v1/fallback turn path (used by any queue not covered by AI_VOICE_V2_QUEUE_CODES) silently awaited the full LLM decision with no audio playing at all, unlike the v2 path which already has a decision-timeout ack. Give v1 the same behavior: wait up to 600ms (_v1_ack_wait_seconds) for the decision, and if it's still not ready, play a short "Секунду." filler via the existing _emit_early_ack before the real reply, instead of leaving the caller in silence for the full LLM+TTS round trip. Reuses the same ack selection/playback code path v2 already exercises, so no new failure modes - just an added timeout branch mirroring the existing v2 one. 2. ai_orchestrator_service._kb_search: every voice/chat turn re-ran a full-table scan of kb_articles (all columns, including body text) and rescored every row in Python, even though the KB rarely changes mid-conversation. Added an in-process cache keyed by language, gated on a cheap content fingerprint (row count + max id + max updated_at + summed title/body/tags length, all computed server-side without transferring the text columns). A fingerprint mismatch always triggers a fresh fetch, so this can never serve stale results after an insert/update/delete - unlike a naive TTL cache, which would have been be wrong the moment a test (or a real KB edit) changed the table within the cache window. Note the first fingerprint design (count + max id + max updated_at only) was insufficient: utc_now_iso() truncates to whole seconds and SQLite reuses primary keys after a full-table delete, so two different row sets written in the same wall-clock second could share a fingerprint. Caught this via a real test failure (test_ai_whatsapp_relaxed_kb_search_answers_phrase_query breaking only when run after test_ai_orchestrator_service.py in the same process) before it could reach production; the summed content-length term closes the gap. Added test_media_runtime_plays_filler_ack_when_v1_decision_is_slow (asserts greeting -> ack -> reply delivery order when process_turn is slow) and verified the KB cache against the full test_ai_orchestrator_service.py + test_ai_whatsapp_orchestrator_service.py suite plus a wider kb/orchestrator/whatsapp/telegram/voice-filtered run: only the same pre-existing, already-documented failures remain (unrelated sales_service test-isolation ordering, one known persona-prompt assertion) - no new failures from either change. Streaming the LLM decision itself (start speaking reply_text before the full structured JSON response finishes generating) was scoped but deliberately deferred: it needs incremental JSON parsing on top of SSE streaming to detect when just the reply_text field is complete, shared across both voice and text-channel decision paths - a separate, higher-risk change that deserves its own PR and testing pass rather than being bundled here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
a9be976845 |
Implement real streaming TTS for ElevenLabs to cut voice-assistant reply latency
The AudioSocket/media_runtime playback pipeline already supports chunked TTS streaming (voice_v2_streaming_tts), but every provider inherited the base TTSProvider.synthesize_chunks(), which just called the blocking synthesize() and yielded the entire finished audio as a single "chunk" - so the caller waited for full-utterance synthesis before any playback could start regardless of the flag. ElevenLabs is the production default (AI_VOICE_TTS_PROVIDER=elevenlabs in deployment/docker-compose.server.yml), so give it a real implementation that POSTs to the /stream endpoint and yields audio as network chunks arrive, instead of waiting for the whole response body. Chunk boundaries are re-aligned to whole 16-bit PCM samples so a split sample at a network read boundary can't corrupt playback. The full synthesized audio is still written to the on-disk cache afterwards so repeat phrases stay fast and skip the vendor call entirely, matching the existing synthesize() cache behavior. Added test_elevenlabs_tts_provider_streams_chunks_and_caches_full_audio to cover: chunk splitting mid-sample gets re-aligned, all yielded chunks are sample-aligned, the full audio round-trips through the cache, and a cached synthesis is replayed without invoking the streaming endpoint again. Verified via tests/test_ai_voice_tts_provider.py (9/9 pass) and a wider voice/tts-filtered run across the suite: the only failures present are the same pre-existing, already-documented ones (sales_service test cross-file isolation ordering, one known persona-prompt assertion) - identical set to before this change, no new failures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
cdabe61bc2 |
Add architecture longread and remove dead code found during review
- docs: longread.md — deep architecture/flow review of the whole platform (services, event bus reality vs docs, AI/ML stack honesty check, tech debt inventory) - ai_orchestrator_service/voice.py: drop _voice_decision_legacy (unreferenced) and the shadowed first _voice_decision definition (silently overwritten by the real one, dead code) - ui/analyst/app.js: drop duplicate dead definitions of loadSavedAnalyticsViews/saveAnalyticsView/deleteAnalyticsView and the first loadAnalyticsTrend implementation, all shadowed by later declarations in the same file; kept the intentional AI-mode drilldown wrapper layer (openAnalyticsDrilldown/exportAnalyticsDrilldownCsv/etc.) since that duplication is deliberate delegation, not dead code - ui/operator/vendor/sip-0.21.2.min.js: remove byte-identical orphaned duplicate of ui/operator/sip-0.21.2.min.js (unreferenced anywhere) Verified via full pytest run: identical set of 97 pre-existing failures before and after (sales_* test-isolation ordering issue and one known persona-prompt test), no new regressions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |