forked from hesabix/arc
458 lines
31 KiB
Markdown
458 lines
31 KiB
Markdown
# ارتقای چت صوتی ایجنت — 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 |
|