Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/docs/AI_VOICE_CHAT_UPGRADE_SCENARIO.md
2026-08-18 17:20:26 +00:00

458 lines
31 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ارتقای چت صوتی ایجنت — STT، TTS، کاتالوگ مدیریت، ارائه‌دهندگان ابری
**تاریخ بررسی:** ۱۴۰۵/۰۵/۲۸ (۱۸ اوت ۲۰۲۶)
**وضعیت:** فازهای ۰–۵ و ۷ پیاده شده‌اند (cascade). فاز ۶ Realtime عمداً انجام نشده.
**هدف سند:** نقشهٔ پیاده‌سازی تدریجی تا سطح ایجنت صوتی حرفه‌ای (مرجع: ChatGPT Advanced Voice / OpenAI Realtime)
**مراجع موجود:** [`AI_VOICE_CHAT_IMPLEMENTATION.md`](AI_VOICE_CHAT_IMPLEMENTATION.md) (وضعیت فعلی)، [`AI_AGENT_SYSTEM_AUDIT.md`](AI_AGENT_SYSTEM_AUDIT.md) (`VOI-01`، `VOI-02`، `GAP-06`)
این سند را بعد از هر فاز با وضعیت ✓ و یک سطر تاریخچه به‌روز کنید.
| فاز | وضعیت |
|-----|--------|
| ۰ قرارداد + fallback env | ✓ |
| ۱ کاتالوگ ادمین + catalog کاربر | ✓ |
| ۲ ابر OpenAI + سیاست fail-closed | ✓ |
| ۳ دیکته + بلندخوانی + chip STT/TTS | ✓ |
| ۴ تماس = همان ایجنت (session/model/mode/approval) | ✓ |
| ۵ متریک، سقف جلسه، Whisper اشتراکی | ✓ (نمونهٔ WER طلایی باقی است) |
| ۶ Realtime speech-to-speech | عمداً انجام نشده |
| ۷ لاگ مصرف صوت | ✓ جزئی (بدون سهمیهٔ دقیقهٔ جدا در پلن) |
---
## ۱. جمع‌بندی سریع
| قابلیت | در سیستم امروز | کجا | شکاف با سطح ChatGPT |
|--------|-----------------|-----|---------------------|
| گفت‌وگوی صوتی دوطرفه | **هست (آبشار محلی)** | WS `/ws/ai/voice` | latency بالا؛ کیفیت فارسی پایین‌تر از ابر؛ بدون speech-to-speech |
| صدا → متن (STT) | **فقط داخل همان جلسهٔ صوت** | `faster-whisper` روی سرور، مدل `small` از env | دیکته در باکس متن نیست؛ partial transcript نیست؛ ابر نیست |
| متن → صدا (TTS) | **فقط داخل همان جلسهٔ صوت** | Piper / Coqui / **پیش‌فرض `dummy` (سکوت)** | خواندن پیام متنی نیست؛ انتخاب صدا نیست؛ ابر نیست |
| مدیریت ادمین مثل مدل متنی | **نیست** | مدل متن: جدول `ai_models` + صفحهٔ ادمین + credential | صوت فقط ENV (`VOICE_*`)؛ کاربر مدل/صدا انتخاب نمی‌کند |
| ارائه‌دهندهٔ API (OpenAI و …) | **فقط برای LLM متن** | `ai_provider_credentials` | هیچ adapter برای Whisper/TTS/Realtime ابری |
| ایجنت حین صوت (ابزار، تأیید نوشتن، مدل انتخابی) | **ناقص** | `voice_ws` حلقه را بدون `session_id` / `execution_mode` / `request_model` صدا می‌زند | نوشتن بی‌تأیید یا بی‌کارت؛ trace متنی دیده نمی‌شود |
| سوییچ متن ↔ صوت در یک جلسه | **قفل** | composer هنگام صوت `enabled: false` | ChatGPT در همان thread هم تایپ می‌کند هم حرف می‌زند |
**نتیجه:** زیرساخت صوت **وجود دارد و پخته است** (VAD، WS، فشرده‌سازی وب، barge-in پروتکلی، بازخورد کیفیت). آنچه کم است **محصول** است نه اسکلت: سه حالت استفاده (تماس / دیکته / بلندخوانی)، کاتالوگ قابل مدیریت، کیفیت ابری اختیاری، و هم‌ترازی حلقهٔ ایجنت با چت متنی.
---
## ۲. آنچه در کد هست (واقعیت امروز)
### ۲.۱ معماری فعلی — آبشار (cascade)، نه Realtime
```
میکروفون کلاینت (PCM16 16kHz یا WebM/Opus)
→ WebSocket /ws/ai/voice (auth در اولین فریم JSON)
→ VAD محلی (webrtcvad) + endpointing سکوت
→ STT محلی faster-whisper (کل utterance یکجا؛ بدون streaming)
→ همان LLM ایجنت: AIService.chat_completion_stream
→ TextChunker جمله/۱۴۰ کاراکتر
→ TTS محلی Piper (یا dummy/coqui) → فریم PCM به کلاینت
→ پیام user/assistant در همان جلسهٔ چت ذخیره می‌شود
```
این همان «Standard Voice» قدیمی ChatGPT است (صدا→متن→مدل→صدا)، نه Advanced Voice / Realtime که مدل مستقیماً روی صوت فکر می‌کند.
فایل‌های اصلی:
| لایه | مسیر |
|------|------|
| WS و ارکستراسیون | `hesabixAPI/adapters/api/v1/ai/voice_ws.py` |
| STT | `hesabixAPI/app/services/voice/stt.py` (`WhisperSTT`) |
| TTS | `hesabixAPI/app/services/voice/tts.py` (`TTSFactory`: piper / coqui / dummy) |
| VAD | `hesabixAPI/app/services/voice/vad.py` |
| تنظیمات | `hesabixAPI/app/core/settings.py` فیلدهای `voice_*` |
| بازخورد | `POST /api/v1/ai/voice/interactions/{id}/feedback` + جدول `ai_voice_interactions` |
| کلاینت | `VoiceChatController` (io/web) + `AIChatVoiceSessionController` + composer میکروفون |
### ۲.۲ سه حالت محصول — امروز فقط یکی هست
کاربر و محصول به **هر دو** STT و TTS نیاز دارند، ولی نه لزوماً همیشه با هم:
| حالت | معنی برای کاربر | امروز |
|------|-----------------|-------|
| **A — تماس صوتی** | مثل تماس ChatGPT: حرف می‌زند، ایجنت جواب می‌دهد با صدا | هست (آبشار محلی) |
| **B — دیکته (STT alone)** | دکمهٔ میکروفون متن را داخل composer می‌گذارد؛ کاربر ویرایش و ارسال می‌کند | نیست؛ میکروفون مستقیم تماس را شروع می‌کند |
| **C — بلندخوانی (TTS alone)** | روی پیام دستیار «بخوان»؛ بدون میکروفون | نیست |
هدف ارتقا: هر سه حالت روی **یک کاتالوگ STT/TTS** سوار شوند. تماس صوتی هر دو را با هم مصرف می‌کند؛ دیکته و بلندخوانی جداگانه‌اند.
### ۲.۳ مدیریت ادمین: الگوی مدل متنی هست؛ صوت از آن جدا مانده
**مدل‌های متنی (الگوی درست برای کپی):**
- جدول `ai_models`: `code`, `display_name`, `provider`, `model_id`, `tier`, قیمت مرجع، `is_active`, `sort_order`
- API ادمین ` /admin/ai/models` (CRUD + seed از config)
- صفحهٔ `AIModelsAdminPage` با provider: openai / anthropic / local / custom
- اعتبارنامه جدا: `ai_provider_credentials` (یک کلید per provider، تست اتصال)
- BYOK کسب‌وکار: `business_ai_provider_configs`
- کاربر در composer با `AIChatModelChip` مدل فعال را برمی‌دارد
- پلن‌ها مدل‌های مجاز را محدود می‌کنند
**صوت امروز:**
- فقط متغیر محیطی: `VOICE_ENABLED`, `VOICE_STT_*`, `VOICE_TTS_ENGINE`, مسیر Piper، …
- پیش‌فرض TTS در کد: `voice_tts_engine = "dummy"` → در تولید اگر env ست نشده باشد کاربر **سکوت** می‌شنود (هشدار UI هست)
- هیچ ردیف کاتالوگ، هیچ صفحهٔ ادمین، هیچ انتخاب کاربر، هیچ قیمت جدا برای دقیقهٔ صوت
- credentialهای OpenAI موجود **فقط chat completion** را تست می‌کنند؛ Whisper/TTS ابری صدا زده نمی‌شوند
- منوی تنظیمات سیستم دستهٔ «هوش مصنوعی» آیتم صوت ندارد
### ۲.۴ حلقهٔ ایجنت در مسیر صوت با چت متنی یکی نیست
چت متنی به `chat_completion_stream` می‌دهد: `session_id`, `request_model`, `execution_mode`, `approve_writes`, `user_query`, ادامهٔ run، رویدادهای SSE (trace، approval، citation).
مسیر صوت امروز تقریباً فقط این را صدا می‌زند:
```python
ai_service.chat_completion_stream(
messages=messages,
use_function_calling=True,
session_business_id=business_id,
)
```
پیامدها:
- مدل انتخابی کاربر در chip اعمال نمی‌شود (مدل پیش‌فرض config/پلن).
- `execution_mode` (تحلیلگر / با تأیید / خودکار) از UI صوت نمی‌آید.
- `approve_writes` پیش‌فرض `False` است؛ ابزار نوشتن در صوت یا متوقف می‌شود یا رویداد `awaiting_approval` به کلاینت صوت **forward نمی‌شود** (`_forward_agent_chunk` فقط `status` و `tool_start` را می‌فهمد).
- `session_id` نیست → persist run، حافظهٔ جلسه، plan، continuation مثل متن کامل نیست.
- تایم‌لاین تفکر متنی و صوت دو کانال جدا هستند (`VOI-01`).
- شارژ فقط توکن LLM است؛ دقیقهٔ STT/TTS و هزینهٔ ابر ثبت نمی‌شود.
### ۲.۵ UX و امنیت صوت (جزئیات مفید برای فازها)
آنچه خوب است:
- Auth WS در query نیست؛ اولین فریم `{"type":"auth","api_key"}` با timeout ۱۵ ثانیه (`ws_api_key_handshake.py`) — بخش بزرگی از `VOI-02` انجام شده.
- Barge-in: اگر کاربر وسط TTS حرف بزند `cancel_event` ست می‌شود؛ کلاینت هم هنگام `startRecording` دستور `barge_in` می‌فرستد.
- ذخیرهٔ PCM فقط با opt-in و `voice_data_collection_enabled`.
- سهمیه قبل از `start` و قبل از هر utterance چک می‌شود.
- وب: AudioWorklet PCM یا MediaRecorder WebM/Opus.
- تست واحد تفسیر رویداد: `ai_chat_voice_session_test.dart`. تست بک‌اند مسیر صوت تقریباً نیست (`ARC-06`).
آنچه نیست / ضعیف است:
- Rate limit per user روی WS (`VOI-02` باقی‌مانده).
- Transcript زنده (partial) هنگام حرف زدن کاربر.
- سوییچ به تایپ وسط تماس بدون قطع جلسه.
- انتخاب صدا / زبان / کیفیت در UI.
- محدودیت نرخ و سقف همزمانی اتصال صوت (هر اتصال مدل Whisper را lazy-load می‌کند؛ روی CPU سنگین است).
- متریک WER / latency perceived.
---
## ۳. رفتار مطلوب (سطح ChatGPT / ایجنت حرفه‌ای)
مرجع محصول: ChatGPT (حالت استاندارد + Advanced Voice)، Claude (فعلاً ضعیف‌تر در صوت)، Google Gemini Live.
### ۳.۱ تجربهٔ کاربر داخل همان جلسهٔ چت
1. **دیکته:** نگه داشتن یا یک ضربه روی میکروفون → متن در composer ظاهر می‌شود (قابل ویرایش) → ارسال معمولی با همهٔ قابلیت ایجنت (ابزار، تأیید نوشتن، مدل).
2. **بلندخوانی:** روی پیام دستیار «بخوان»؛ قطع با ضربه؛ انتخاب صدا از تنظیمات.
3. **تماس صوتی:** دکمهٔ جدا (مثل گوشی ChatGPT) جلسه را به حالت صوت می‌برد ولی **تاریخچه همان thread است**. کاربر می‌تواند وسط تماس تایپ کند. Transcript زنده در حباب‌ها دیده می‌شود. وقتی ایجنت ابزار می‌زند («دارم موجودی را می‌خوانم») همان وضعیت در نوار صوت و در تایم‌لاین متنی می‌آید.
4. **تأیید نوشتن صوتی:** ایجنت بلند می‌گوید «فاکتور فروش برای علی به مبلغ X بسازم؟ بگو تأیید یا رد» و همان کارت تأیید در UI ظاهر می‌شود؛ تا تأیید، write اجرا نشود.
5. **قطع و barge-in:** حرف زدن کاربر TTS را فوری قطع کند؛ دکمهٔ پایان تماس قرمز بماند.
6. **انتخاب:** مدل زبانی (همان chip موجود) + موتور STT + صدای TTS، محدود به پلن.
Latency هدف تماس آبشاری خوب: **شروع شنیدن پاسخ < ۱.۵s** برای عبارت کوتاه فارسی (`VOI-01`). Realtime ابری می‌تواند زیر ۵۰۰ms باشد ولی ابزار و فارسی را سخت‌تر می‌کند.
### ۳.۲ تجربهٔ مدیر سیستم (مثل مدل‌های متنی)
مدیر در تنظیمات سیستم بتواند:
- STT محلی (Whisper size/path) یا STT ابری (کلید، مدل، زبان) را **ردیف کاتالوگ** کند.
- TTS محلی (Piper voice id) یا TTS ابری (صدا، سرعت، زبان) را ردیف کند.
- هر ردیف را فعال/غیرفعال، مرتب، قیمت‌گذاری مرجع، و محدود به پلن کند.
- اتصال را با یک نمونهٔ کوتاه فارسی **تست** کند (مثل test-connection مدل متنی).
- پیش‌فرض سراسری و پیش‌فرض پلن تعیین کند.
- تصمیم بگیرد دادهٔ صوت به ابر برود یا فقط محلی بماند (سیاست حریم خصوصی per business).
کاربر نهایی فقط از کاتالوگ **فعال و مجاز پلن** انتخاب می‌کند؛ کلید API را نمی‌بیند (مگر BYOK کسب‌وکار در فاز بعدی).
### ۳.۳ دو مسیر فنی که نباید قاطی شوند
| مسیر | شرح | ابزار ایجنت | کیفیت مکالمه | هزینه / حریم |
|------|-----|-------------|--------------|--------------|
| **Cascade (توصیهٔ پایه)** | STT → همان حلقهٔ متن → TTS | کامل؛ همان ۱۰۰+ ابزار | وابسته به کیفیت STT/TTS و latency آبشار | قابل کنترل؛ محلی ممکن است |
| **Realtime / speech-to-speech** | مدل چندرسانه‌ای مستقیم روی صوت (OpenAI Realtime، Gemini Live) | باید tool را به جلسهٔ realtime وصل کرد؛ پیچیده‌تر | طبیعی‌تر، قطع صحبت نرم‌تر | داده به ابر؛ فارسی متغیر؛ هزینهٔ دقیقه |
برای حسابیکس (ERP با write guard و ابزار دامنه) **Cascade باید مسیر اصلی بماند.** Realtime فقط فاز اختیاری برای مکالمهٔ کوتاه بدون نوشتن سنگین است، نه جایگزینی حلقهٔ ایجنت.
---
## ۴. پیشنهاد معماری (تکمیل اسکلت فعلی، نه بازنویسی)
### ۴.۱ لایهٔ Provider جدا از WS
امروز `voice_ws.py` مستقیماً `WhisperSTT` و `TTSFactory.create` می‌سازد. باید پشت یک قرارداد ثابت برود:
```
STTProvider.transcribe(pcm16, sample_rate, language) -> Transcript
+ اختیاری: transcribe_stream(...) -> partial + final
TTSProvider.synthesize_stream(text, voice_id, cancel) -> PCM frames
VoiceRuntime.resolve(business_id, user_id, session) -> (stt, tts, policy)
```
پیاده‌سازی‌های اول:
| kind | پیاده‌سازی | منبع تنظیم |
|------|------------|------------|
| `local_whisper` | کد فعلی `WhisperSTT` | کاتالوگ + fallback env |
| `local_piper` | کد فعلی `PiperTTSEngine` | کاتالوگ + fallback env |
| `dummy` | سکوت / تست | فقط غیرتولید |
| `openai_transcription` | `POST /v1/audio/transcriptions` (whisper-1 / gpt-4o-transcribe / gpt-4o-mini-transcribe) | credential `openai` موجود |
| `openai_tts` | `POST /v1/audio/speech` (tts-1 / tts-1-hd / gpt-4o-mini-tts) | همان credential |
| بعداً | Groq Whisper، Azure Speech، Google Chirp/TTS، ElevenLabs | credential جدا در همان جدول با `provider` جدید |
`api_base_url` فعلی برای gateway سازگار با OpenAI می‌تواند همان transcription/speech را هم پروکسی کند اگر ارائه‌دهنده پشتیبانی کند — در تست اتصال باید جداگانه چک شود.
### ۴.۲ کاتالوگ داده — پیشنهاد schema
جدول جدید بهتر از شلوغ کردن `ai_models` است، چون فیلدها فرق دارند (sample rate، voice id، زبان، قیمت per minute / per 1k chars). الگوی نام‌گذاری مثل متن:
**`ai_voice_models`**
| فیلد | نقش |
|------|-----|
| `code` | شناسه پایدار مثلاً `stt-local-whisper-small` |
| `kind` | `stt` \| `tts` \| `realtime` |
| `display_name` | نام فارسی/انگلیسی برای UI |
| `provider` | `local` \| `openai` \| `azure` \| `google` \| `groq` \| `elevenlabs` \| `custom` |
| `model_id` | شناسهٔ API یا فایل محلی (مثلاً `whisper-1`, `fa_IR-ganji-medium`) |
| `language` | پیش‌فرض `fa` |
| `voice_id` | فقط TTS (alloy، ganji، …) |
| `tier` | basic/pro مثل مدل متن |
| `is_active`, `sort_order` | |
| `reference_cost_*` | مثلاً per_minute یا per_1k_chars |
| `extra_json` | device/compute_type برای محلی، format خروجی، و … |
سیاست انتخاب (ترتیب resolve):
1. انتخاب صریح کاربر در جلسه (اگر پلن اجازه دهد)
2. پیش‌فرض پلن
3. پیش‌فرض فعال سراسری ادمین
4. fallback ENV فعلی (سازگاری با نصب‌های موجود)
Credential: همان `ai_provider_credentials` گسترش داده شود (providerهای جدید + فلگ `supports_stt` / `supports_tts` / `supports_chat`) **یا** در فاز اول از کلید OpenAI موجود برای STT/TTS ابری استفاده شود تا ادمین دو بار کلید نگذارد.
### ۴.۳ API محصول (علاوه بر WS تماس)
| Endpoint پیشنهادی | حالت |
|-------------------|------|
| `POST /ai/voice/stt` (multipart یا PCM) | دیکته؛ پاسخ `{text, language}` |
| `POST /ai/voice/tts` یا SSE/WS کوتاه | بلندخوانی یک متن؛ اختیاری stream |
| `GET /ai/voice/catalog` | لیست STT/TTS مجاز کاربر |
| WS `/ws/ai/voice` | تماس؛ بعد از فاز ۱ از `VoiceRuntime.resolve` تغذیه شود نه مستقیماً از env |
احراز هویت، سهمیه، و عدم ذخیرهٔ صوت مگر opt-in برای هر سه یکی باشد.
### ۴.۴ هم‌ترازی حلقهٔ تماس با چت متنی
حداقل قرارداد `start` روی WS:
```json
{
"type": "start",
"session_id": 123,
"execution_mode": "supervised",
"model_code": "gpt-4o-mini",
"stt_code": "stt-local-whisper-small",
"tts_code": "tts-piper-ganji",
"input_codec": "pcm"
}
```
و `chat_completion_stream(..., session_id=..., request_model=..., execution_mode=..., user_query=transcript)`.
رویدادهای جدید به کلاینت صوت: `approval_required`، `trace_step` (خلاصهٔ کوتاه قابل گفتن)، `transcript_partial`.
---
## ۵. فازهای اجرا
ترتیب طوری است که هر فاز به‌تنهایی قابل عرضه باشد و فاز بعد روی آن سوار شود. معیار پذیرش را قبل از بستن فاز روی جلسهٔ واقعی فارسی حسابداری اجرا کنید.
### فاز ۰ — قرارداد و جداسازی (بدون ابر)
| کار | معیار پذیرش | ریسک |
|-----|-------------|------|
| Interface `STTProvider` / `TTSProvider` و انتقال Whisper/Piper فعلی پشت آن | `voice_ws` دیگر مستقیماً کلاس بتن نسازد؛ تست واحد dummy STT/TTS | رگرسیون تماس فعلی |
| سند ENV: fallback اگر کاتالوگ خالی است | نصب‌های موجود بدون migration همچنان کار کنند | |
### فاز ۱ — کاتالوگ ادمین (مثل مدل متنی)
| کار | معیار پذیرش | ریسک |
|-----|-------------|------|
| جدول `ai_voice_models` + CRUD ادمین + صفحه در دستهٔ AI تنظیمات | مدیر ردیف STT محلی و TTS Piper بسازد، فعال/غیرفعال کند، تست کوتاه بزند | کلید در لاگ |
| `GET /ai/voice/catalog` برای کاربر | chip یا تنظیمات چت لیست غیرخالی برمی‌گرداند | شلوغی composer |
| Seed از env فعلی (whisper-small + piper ganji + dummy) | یک کلیک seed مثل مدل متنی | |
### فاز ۲ — ارائه‌دهندهٔ API (حداقل OpenAI، قابل تعمیم)
| کار | معیار پذیرش | ریسک |
|-----|-------------|------|
| Adapter transcription + speech با credential موجود OpenAI | ادمین ردیف `openai` بسازد، تست فارسی بزند، تماس از آن استفاده کند | ارسال صوت مشتری به ابر |
| فلگ سیاست `allow_cloud_audio` per business (پیش‌فرض بسته برای کسب‌وکار حساس) | بدون فلگ، resolve به local برگردد | |
| Timeout، حجم سقف utterance، retry مثل STT فعلی | فایل ۳۰ ثانیه‌ای بیش از سقف رد شود | هزینهٔ غافلگیرکننده |
| لاگ usage با `context.type=voice_stt` / `voice_tts` | در گزارش مصرف دیده شود حتی اگر هنوز پول جدا نباشد | |
ارائه‌دهندگان بعدی (فاز ۲+، هر کدام یک adapter): Groq (Whisper سریع و ارزان)، Azure Neural (fa-IR TTS قوی)، Google، ElevenLabs. **اول OpenAI** چون کلید و الگوی credential از قبل در پنل هست.
### فاز ۳ — STT و TTS مستقل در چت متنی
| کار | معیار پذیرش | ریسک |
|-----|-------------|------|
| دیکته: میکروفون کوتاه → متن در composer؛ ارسال جدا | کاربر می‌تواند «فروش امروز» را دیکته کند، ویرایش کند، بفرستد؛ حلقهٔ کامل ایجنت اجرا شود | تداخل با دکمهٔ تماس |
| جدا کردن UX: میکروفون دیکته vs دکمهٔ تماس | دو کنترل واضح در composer | شلوغی موبایل |
| بلندخوانی پیام دستیار | پخش PCM/صوت؛ Stop؛ عدم قفل شدن composer | همپوشانی با تماس |
این فاز بیشترین ارزش محصول را برای «هم صدا به متن و هم متن به صدا» می‌دهد بدون اینکه تماس Realtime لازم باشد.
### فاز ۴ — تماس صوت = همان ایجنت متنی
| کار | معیار پذیرش | ریسک |
|-----|-------------|------|
| پاس دادن session_id / model / execution_mode | مدل chip در تماس رعایت شود | |
| Forward رویداد approval + کارت در UI؛ تا تأیید، TTS سؤال بپرسد نه اینکه write اجرا شود | ساخت فاکتور از روی صوت بدون تأیید ممکن نباشد | دور زدن SEC |
| قفل نبودن composer | وسط تماس بتوان تایپ کرد؛ متن و صوت یک تاریخچه | race دو utterance |
| `transcript_partial` اگر موتور STT streaming دارد؛ وگرنه حداقل `transcript_final` فوری در thread | حباب کاربر قبل از «thinking» دیده شود | |
### فاز ۵ — کیفیت مکالمه (بدون تغییر ارائه‌دهنده)
| کار | معیار پذیرش | ریسک |
|-----|-------------|------|
| متریک latency: vad_end → first_audio_out | عبارت کوتاه فارسی < ۱.۵s روی ابر؛ محلی جدا گزارش شود | |
| Rate limit اتصال صوت per user (`VOI-02`) | اتصال بدون auth بعد از ۱۵s (موجود) + سقف اتصال همزمان | |
| صف/یک مدل Whisper اشتراکی در فرایند به جای load per connection | حافظهٔ سرور پایدار | پیچیدگی lifecycle |
| نمونهٔ ارزیابی فارسی حسابداری (اعداد، نام کالا، «بدهکار») | WER روی ۱۰ فایل طلایی ثبت شود | |
### فاز ۶ — اختیاری: Realtime speech-to-speech
فقط بعد از فاز ۲–۴. جلسهٔ OpenAI Realtime (یا معادل) با:
- ابزار فقط read در نسخهٔ اول
- fallback به cascade اگر مدل realtime در دسترس نبود
- سیاست ابری اجباری
- عدم جایگزینی write guard
معیار پذیرش: مکالمهٔ کوتاه «سلام، فروش امروز چقدر بود؟» با حس طبیعی‌تر؛ موجودی از ابزار read بیاید؛ ساخت فاکتور همچنان به cascade+approval برگردد.
### فاز ۷ — صورتحساب و پلن
| کار | معیار پذیرش |
|-----|-------------|
| سهمیه دقیقه STT/TTS یا هزینهٔ جدا در پلن | پلن basic فقط local؛ pro ابر |
| BYOK صوت برای کسب‌وکار (مثل متن) | کلید روی `business_ai_provider_configs` برای transcription/speech |
| نمایش در UI سهمیه | کاربر بفهمد دیکته از سهمیه صوت کم می‌کند |
---
## ۶. ارائه‌دهندگان — راهنمای انتخاب (فارسی حسابداری)
کیفیت فارسی **باید با فایل واقعی فاکتور/اعداد آزمایش شود**؛ ادعاهای بازاریابی کافی نیست.
| نقش | محلی (حریم خصوصی) | ابری پیشنهادی اول | جایگزین |
|-----|-------------------|-------------------|---------|
| STT | faster-whisper `small` فعلی؛ برای کیفیت `large-v3` روی GPU | OpenAI `gpt-4o-mini-transcribe` یا `whisper-1` | Groq Whisper (ارزان/سریع)، Azure `fa-IR` |
| TTS | Piper `fa_IR-ganji-medium` (و amir/gyro) | OpenAI TTS چندزبانه؛ کیفیت فارسی را A/B کنید | Azure Neural fa-IR، Google fa-IR، ElevenLabs multilingual |
| Realtime | نیست | OpenAI Realtime | Gemini Live |
توصیهٔ محصول:
- **پیش‌فرض نصب self-host:** STT whisper + TTS piper (تمایز حریم خصوصی که در ممیزی آمده حفظ شود).
- **پیش‌فرض ابر اگر مدیر کلید OpenAI دارد و سیاست اجازه دهد:** STT ابری + TTS ابری برای کیفیت؛ LLM همان کاتالوگ متن.
- هرگز ابر را بدون فلگ کسب‌وکار اجباری نکنید.
---
## ۷. آنچه نباید کرد
- حذف پایپلاین محلی به نفع فقط ابر (مشتریان self-host و دادهٔ مالی).
- فرستادن صوت به ابر وقتی `allow_cloud_audio` خاموش است.
- چسباندن STT/TTS به جدول `ai_models` متنی بدون `kind` — فیلدها و قیمت‌گذاری فرق دارند.
- جایگزینی حلقهٔ ایجنت با Realtime برای عملیات نوشتنی (فاکتور، سند).
- ذخیرهٔ PCM پیش‌فرض برای «بهبود مدل» بدون opt-in.
- یک دکمهٔ میکروفون که هم دیکته است هم تماس — دو نیت جدا.
- TTS `dummy` در تولید بدون هشدار ادمین در کاتالوگ (الان فقط snackbar کلاینت است).
---
## ۸. سناریوهای پذیرش دستی (بعد از پیاده‌سازی فاز مربوط)
**دیکته (فاز ۳)**
۱. در چت متنی میکروفون دیکته → «موجودی کالای X را بگو».
۲. متن در باکس ظاهر شود، یک کلمه را دستی درست کند، ارسال.
۳. ایجنت ابزار موجودی بزند و پاسخ متنی بدهد. صدا پخش نشود مگر بلندخوانی.
**بلندخوانی (فاز ۳)**
۱. روی همان پاسخ «بخوان».
۲. صدا پخش شود؛ Stop قطع کند؛ بتوان همزمان پیام بعدی را تایپ کرد.
**تماس + ابزار (فاز ۴)**
۱. تماس صوتی: «گزارش فروش این ماه را خلاصه بگو».
۲. نوار وضعیت thinking/tool/speaking؛ متن در thread؛ صدا.
۳. وسط پاسخ حرف بزند (barge-in) و سوال را عوض کند.
**تأیید نوشتن (فاز ۴)**
۱. «برای علی فاکتور فروش ۱۰ عدد کالا بساز».
۲. کارت تأیید + پرسش صوتی؛ بدون تأیید سند ساخته نشود.
**ادمین (فاز ۱–۲)**
۱. ردیف Piper محلی و ردیف OpenAI STT/TTS.
۲. تست اتصال فارسی.
۳. غیرفعال کردن ابر → کاربر فقط محلی ببیند.
۴. کسب‌وکار بدون فلگ ابر حتی اگر کاتالوگ ابری فعال سراسری باشد، محلی بماند.
**رگرسیون نصب فعلی**
پس از فاز ۰–۱، بدون ردیف کاتالوگ، همان env `VOICE_*` تماس قبلی را حفظ کند.
---
## ۹. نگاشت به ممیزی و ترتیب پیشنهادی نسبت به کارهای دیگر
| آیتم ممیزی | این سند |
|------------|---------|
| `VOI-01` کیفیت و هم‌ترازی متن | فاز ۲ (کیفیت) + فاز ۳–۴ (هم‌ترازی UX و ایجنت) |
| `VOI-02` auth/rate limit | timeout auth موجود است؛ rate limit = فاز ۵ |
| `GAP-06` هم‌تراز GPT-4o Realtime | عمداً فاز ۶ و اختیاری؛ Cascade حرفه‌ای هدف اصلی است |
| `SEC` تأیید نوشتن | فاز ۴ روی صوت |
| `BIL` سهمیه | فاز ۷ |
| `ARC-06` تست صوت | از فاز ۰ تست واحد provider؛ از فاز ۲ تست HTTP mock ابر |
ترتیب پیشنهادی نسبت به صف چت متنی (`AI_CHAT_PRODUCT_ISSUES.md`): دیکته/بلندخوانی (فاز ۳) را می‌توان موازی با باگ‌های UI متن پیش برد. تماس Realtime را پشت پایداری approval و God Widget نگذارید جلو بیفتد.
پیشنهاد صف اجرا وقتی مالک شروع کرد:
1. فاز ۰ (جداسازی)
2. فاز ۱ (ادمین/کاتالوگ)
3. فاز ۳ (دیکته + بلندخوانی) — ارزش کاربر سریع
4. فاز ۲ (ابر) — کیفیت
5. فاز ۴ (ایجنت در تماس)
6. فاز ۵ (سخت شدن تولید)
7. فاز ۷ (پول)
8. فاز ۶ (Realtime) فقط اگر محصول خواست
---
## ۱۰. چک‌لیست PR وقتی یک فاز پیاده شد
- [ ] Adapter جدید: قرارداد STT/TTS + تست واحد مسیر اصلی و شکست (timeout، کلید خالی، زبان)
- [ ] اگر ابر: صوت بدون `allow_cloud_audio` هرگز از سرور خارج نشود؛ تست fail-closed
- [ ] کاتالوگ: CRUD ادمین + catalog کاربر + seed/fallback env
- [ ] l10n کلیدهای UI جدید (fa/en)
- [ ] اگر تماس: `session_id` / approval / model در مسیر WS
- [ ] متریک `AI_METRIC` برای `voice_stt_ms` / `voice_tts_ms` / `voice_ttfa_ms` (time-to-first-audio)
- [ ] به‌روز کردن وضعیت فاز در همین سند + یک سطر تاریخچه
- [ ] بستن یا یادداشت روی `VOI-01` / `VOI-02` / `GAP-06` در ممیزی فقط اگر معیار همان آیتم واقعاً تمام شد
---
## ۱۱. تاریخچهٔ به‌روزرسانی
| تاریخ | نسخه | رخداد |
|-------|------|--------|
| ۱۴۰۵/۰۵/۲۸ | 0.1 | بررسی کد بدون تغییر؛ ثبت شکاف کاتالوگ، سه حالت محصول، cascade در برابر Realtime، و فازهای اجرا |
| ۱۴۰۵/۰۵/۲۸ | 1.0 | پیاده‌سازی cascade: کاتالوگ STT/TTS، دیکته، بلندخوانی، تماس با مدل/حالت/تأیید نوشتن، سیاست ابر fail-closed |