# پلی‌بوک اجرایی: افزودن موتور STT بله به خط لوله چندمدله‌ی بات تدارکات > **نسخه:** ۱.۰ > **مخاطب:** ایجنت هوش مصنوعی مجری + تیم فنی + مدیر محصول > **ماهیت سند:** سند تصمیم‌محور — هر تست به یک «دروازه‌ی تصمیم (Gate)» می‌رسد و هر دروازه، جدول «وضعیت مشاهده‌شده ← اقدام» دارد. ایجنت باید فقط بر اساس وضعیت‌های واقعیِ مشاهده‌شده در همان لحظه، شاخه‌ی درست را انتخاب کند. > **هدف نهایی:** افزودن ترنسکریپشن موتور بله (STT سمت سرور بله) به‌عنوان یکی از کاندیداهای خط لوله‌ی Multi-Provider STT فعلیِ بات، بدون هیچ تغییر ساختاری در بات. --- ## ۰. قواعد الزام‌آور اجرا (برای ایجنت — قبل از هر اقدام بخوانید) 1. **مبنای گیت فقط `production` است.** سایر برنچ‌ها (از جمله `prod-v7` که کد بات روی آن است) متعلق به جریان کاری خودشان‌اند و **نباید** توسط ایجنت دست‌کاری، مرج یا پوش شوند. تنها مهندس ارشد شرکت مرج می‌کند. **هیچ پوشی بدون تأیید صریح مالک انجام نشود.** 2. **درخواست‌های شبکه به بله باید کم‌حجم و انسان‌نما باشند.** هشدار جدی: سازندگان کتابخانه‌های مهندسی‌معکوس بله تأکید کرده‌اند که «استفاده‌ی زیاد از POST gRPCِ غیر-احراز (پیام/مدیا/حضور) می‌تواند فلگ، rate-limit یا بن موقت ایجاد کند». پس: ترجیح کانال WebSocket، ترتیبی نه موازی، تأخیر انسانی، سقف روزانه. 3. **حساب‌ها:** فقط شماره‌ی اختصاصی شرکت (Bale ئرنر) — هرگز شماره‌ی شخصی. حساب فقط برای این کار باشد. 4. **حریم خصوصی:** صدای کاربران تدارکات روی سرورهای بله پردازش می‌شود (همین حالا هم ویس از بله می‌آید)؛ اما اگر ایجنت صدا را مجدداً به یک چت آپلود می‌کند (مسیر R2/R3)، باید بلافاصله پس از ترنسکرایب پیام را حذف کند و آدرسی از صدا نگه ندارد. 5. **هیچ سکرتی (توکن بات، توکن کاربر، session_file، کلید LLM) نباید در گیت کامیت شود.** سکرت‌های موجود در `docker-compose.yml` (توکن بات و کلید DeepSeek به‌عنوان پیش‌فرض) باید در فرصت مناسب rotate و به `${VAR:?…}` تبدیل شوند. 6. **معیار موفقیت، سرعت نیست — دقت است.** بدون عبور از دروازه‌ی دقت (G5) هیچ ساخت/استقراری مجاز نیست. --- ## ۱. واقعیت‌های تأییدشده (پایه‌ی همه‌ی تصمیم‌ها) ### ۱.۱ — سیستم فعلی: بات و خط لوله‌ی STT | مؤلفه | مسیر | توضیح | |---|---|---| | خط لوله STT | `bot/app/stt.py` | `transcribe_multi()` صدای MP3 (خروجی ffmpeg: mono 16kHz) را **به موازات** به همه‌ی پرووایدرها می‌فرستد؛ هر پرووایدر یک `POST {base_url}/audio/transcriptions` (multipart، فیلد `file`، به‌همراه `model` و `language=fa`) است و خروجی `{"text": "..."}` انتظار دارد. **fail-soft:** خطای هر پرووایدر فقط آن را حذف می‌کند (`return None`)؛ بقیه ادامه می‌دهند. | | پیکربندی پرووایدرها | `bot/app/config.py` | `STT_PROVIDER_1..3_NAME/BASE_URL/API_KEY/MODEL/LANGUAGE` + `STT_PROVIDERS_JSON` + fallback قدیمی `STT_*`. نکته: یک پرووایدر فقط وقتی فعال می‌شود که **هم `BASE_URL` و هم `API_KEY`** پر باشند. | | مصرف خروجی‌ها | `bot/app/nlu.py` | همه‌ی ترنسکریپت‌های کاندید به DeepSeek می‌روند و خروجی نهایی `consensus_text` + `intent` + `patch` (اقلام استعلام) است. **یعنی افزودن بله = یک کاندید بیشتر در ورودی consensus.** | | مسیر ویس در بات | `bot/app/handlers/__init__.py` | ویس → دانلود (`getFile`) → `transcribe_multi` → متن به‌عنوان ورودی NLU. | **نتیجه‌ی معماری (کلیدی):** افزودن هر مسیر STT = ساخت **یک سرویس adapter با قرارداد OpenAI-compatible** + پر کردن `STT_PROVIDER_3_*`. **صفر تغییر در کد بات.** (این نقطه‌ی اتصال رسمی و پایدار است.) ### ۱.۲ — شواهد تجربی Bot API (تست‌شده — تکرارپذیر) روی توکن بات فعلی (`procurement_assistant_bot`)، متدهای مشکوک STT تست شدند: ``` POST https://tapi.bale.ai/bot/transcribeVoice → {"ok":false,"error_code":404,"description":"Bad Request: handler not found"} POST .../getTranscription → (همان 404) POST .../transcribe → (همان 404) POST .../speechToText → (همان 404) POST .../voiceToText → (همان 404) POST .../getMe (کنترل) → {"ok":true,"result":{"username":"procurement_assistant_bot",...}} ``` مستندات رسمی (`docs.bale.ai`) هم هیچ متد یا فیلد ترنسکریپشن برای آبجکت Voice/Audio ندارند. **نتیجه: Bot API بله قطعاً STT ندارد. مسیر «فیلد مخفی» فقط با تست T1 (بخش ۳) قابل رد شدن نهایی است.** ### ۱.۳ — یافته‌های گزارش‌های ایجنت شرکت (خلاصه‌ی راستی‌آزمایی‌شده) | یافته | وضعیت راستی‌آزمایی | |---|---| | کلاینت وب بله روی **gRPC-Web + Protocol Buffers** (`@bufbuild/protobuf`) است | **منطقی و هم‌راستا:** کتابخانه‌های مستقل مهندسی‌معکوس بله هم همین را تأیید می‌کنند؛ باید در T2 مجدداً مشاهده شود. | | گیت‌وی‌ها: `https://maviz-ws.bale.ai` و `wss://maviz-ws.bale.ai/ws/?uid={user_id}` (+ بتا/نکست) | در وب ایندکس نشده (طبیعی) — **باید در T2 با چشم مشاهده شود.** | | ماژول‌های `SpeechTranscript` و `AIChat` در باندل | سرنخ معتبر — **باید در T2/T3 تأیید و دیکود شود.** | | «روش دوم غیرعملی است چون باید کل .protoها بازسازی شوند» | **نادرست/بیش‌برآورد:** با دیکود جنریک Protobuf و استخراج توصیف‌گر از خود باندل (T3) یا با استفاده از aiobale-py (T4)، این کار در حد «افزودن یک متد» کوچک می‌شود. | | ماتریس تأخیر («روش دوم زیر ۱ ثانیه») | **گمراه‌کننده:** گلوگاه، استنتاج ASR روی سرور بله است؛ عدد واقعی فقط از T2/T5 می‌آید. | ### ۱.۴ — اکوسیستم مهندسی معکوس موجود (مهم — دارایی راهبردی) - **`aiobale-py`** (v0.3.9، فعال): فریم‌ورک async برای بله روی **WebSocket + Protobuf**، الهام‌گرفته از aiogram؛ ~۸۰ متد RPC (پیام، فایل، گروه، مخاطبین…)، ورود تلفنی با OTP، `session_file`، توکن JWT، rate-limiter داخلی، docs و مخزن عمومی: - PyPI: `pip install aiobale-py` (ایمپورت: `aiobale`) - GitHub: `github.com/aminmadaniofficial/aiobale` — مستندات: `aminmadaniofficial.github.io/aiobale/` - **متد ترنسکرایب ندارد** → باید همان RPC کشف‌شده در T2 به آن افزوده شود (زیرساخت لاگین/WS/کدک آماده است). - نسخه‌ی اولیه‌ی `Enalite/aiobale` (GitHub الان 404؛ نام PyPI توسط یک پکیج خالی اشغال شده) → **فقط به فورک فعال `aiobale-py` اتکا کنید.** - پروژه‌ی جانبی مرتبط: `bale-mcp-server` (MCP برای بله با aiobale-py) — برای الگوگیری نحوه‌ی استفاده. - سرویس ثالث STT: [majidapi.ir](https://majidapi.ir) (`/ai/stt?url=...&token=...`) — مسیر جایگزین R7 (بخش ۵). ### ۱.۵ — وضعیت برنچ‌ها (قواعد همکاری) | برنچ | وضعیت | مجاز برای ایجنت؟ | |---|---|---| | `production` (`253c922`) | مبنای رسمی کار | ✅ فقط خواندن/مبنا | | `prod-v7` (کد بات/ایجنت، `638076d`) | کار مالک این سند؛ منتظر تست نهایی و مرج توسط مهندس ارشد | ⛔ دست‌کاری/پوش ممنوع | | `prod-v8/9/10`، `pm`، `pm_2` و بقیه | برنچ‌های سایر اعضا؛ در حال تست | ⛔ هیچ دخالتی | --- ## ۲. نقشه‌ی وضعیت‌ها (State Machine) — نمای کلی مسیر ``` S0 (شروع) │ ├─T1─> G1 ──(فیلد ترنسکریپشن پیدا شد)───────────────► R1: پایان! (بدون کرالر) ✅ │ └─(پیدا نشد)──► T2 │ ├─T2─> G2 ──(RPC ساده/دیکودپذیر)──► T3+T4 ─> G3 ──(موفق)──► T5+ G4 ─► T7 ─> G5 │ ├─(فقط روی WS)──────────► همان T3+T4 (aiobale مسیر WS است) ▲ │ ├─(رمزنگاری/امضای خاص)──► R4 (Playwright) یا R3 ─► T5/T7 │ └─(نیاز به اثر انگشت مرورگر)─► R4 ─► T5/T7 │ ├─(T5/T7)─> G5 ──(بهبود دقت کافی)──► G6 (ToS) ──(سبز)──► BUILD (R2/R3/R4) ─► G7 (سواک) ─► استقرار کامل │ └─(بهبود ناکافی)──► STOP: همان پرووایدرهای فعلی ⛔ │ └─(قرمز)──► STOP یا فقط R4 در حد محدود ⛔ ``` --- ## ۳. فاز A — تست‌های کشف (T1…T9) > برای هر تست: هدف، پیش‌نیاز، مراحل، خروجی، معیار قبولی، و حالت‌های ممکن. ایجنت بعد از هر تست **حتماً** به جدول دروازه‌ی مربوطه (بخش ۴) مراجعه کند. ### T1 — تست payload خام ویس (۳۰ دقیقه) **هدف:** رد یا تأیید نهایی وجود فیلد ترنسکریپشن مخفی در پیام‌های Bot API. **پیش‌نیاز:** یک ربات تستی **مستقل** (از BotFather بله بسازید؛ از ربات فعلی استفاده نکنید چون در حال long-poll است و مصرف‌کننده‌ی دوم تعارض ایجاد می‌کند). یک حساب بله برای فرستادن ویس. **مراحل:** 1. اسکریپت مینیمال پایتون: `getUpdates` بدون offset + چاپ JSON کامل هر آپدیت در فایل. 2. یک ویس فارسی واقعی (۵–۱۰ ثانیه) به ربات تستی بفرستید. 3. JSON کامل ویس را ذخیره کنید؛ کلیدهای آبجکت `voice`/`audio`/`message` را لیست کنید. 4. `getFile` را هم صدا بزنید و فیلدهای پاسخ را ضبط کنید. **معیار قبولی/حالت‌ها:** - **حالت A:** هر فیلدی مثل `transcription`/`transcript`/`speech`/`text` روی ویس وجود دارد → **پایان پروژه‌ی کرالر (R1)**؛ فقط به بات وصل کنید. - **حالت B:** خبری نیست (قوی‌ترین احتمال طبق مستندات) → برو به T2. ### T2 — کپچر ترافیک کلاینت وب (نیم‌روز) — قاطع‌ترین تست **هدف:** کشف «دقیق» مکانیزم ترنسکرایب: نام RPC، ترابری (unary/WS)، قالب فریم، هدر احراز، و **زمان واقعی ASR**. **پیش‌نیاز:** حساب سوخته‌ی اختصاصی (سیم‌کارت شرکت)، مرورگر Chromium، ابزار دیکود Protobuf: `protoscope` یا `blackboxprotobuf` (نصب با pip/go)، و اختیاری mitmproxy. **مراحل:** 1. ورود به `web.bale.ai`؛ DevTools → Network با گزینه‌ی Preserve log؛ فیلتر `WS` و `Fetch/XHR` را فعال نگه دارید. 2. یک ویس در یک چت باز کنید و دکمه‌ی «تبدیل به متن / متن پیاده» را بزنید. 3. **همه‌ی درخواست‌های همان لحظه** را ضبط کنید: - اگر `POST` روی هاستی مثل `maviz-ws.bale.ai` با `content-type: application/grpc-web+proto` (یا `grpc-web-text`) ظاهر شد → همین RPC است. - اگر قاب باینری روی WebSocket `wss://…/ws/?uid=…` رفت → RPC روی WS است. 4. برای هر نمونه ذخیره کنید: `req.bin`، `resp.bin`، هدرهای مهم (نام هدر توکن: `authorization`/`x-auth`/…)، URL کامل، و **زمان پاسخ از تب Timing** (همین = تأخیر واقعی ASR بله). 5. ۵ تا ۱۰ ویس متنوع (کوتاه/بلند/با لهجه) تکرار کنید تا پایداری اسکیما معلوم شود؛ در `samples/` ذخیره و در `findings.md` خلاصه کنید. 6. دیکود آفلاین: به یاد داشته باشید پیام‌های gRPC-Web یک **پیشوند ۵ بایتی** دارند (۱ بایت فلگ + ۴ بایت طول) و سپس Protobuf می‌آید → ابتدا پیشوند را بردارید، سپس: `protoc --decode_raw < resp_payload.bin` یا `protoscope resp_payload.bin` 7. اگر آرایه‌ی WS دیده شد: فریم‌ها را از DevTools کپی و با protoscope دیکود کنید؛ به دنبال پیام معادل `SpeechTranscript` بگردید. **خروجی:** `capture/findings.md` شامل: نام دقیق RPC، ترابری، framing، هدر احراز، تأخیر واقعی، و نقشه‌ی مقدماتی فیلدها (کدام فیلد = message_id؟ file_id؟ متن؟). ### T3 — استخراج اسکیما (۱–۲ روز) **هدف:** تبدیل کپچرها به یک اسکیمای قابل استفاده (و ترجیحاً `.proto`). **روش‌ها (به ترتیب):** 1. **از خود باندل:** باندل وب بله (`/static/js/index.*.js` و در صورت وجود `.js.map`) را از قبل دارید؛ پروژه‌های ساخته‌شده با `@bufbuild/protobuf` معمولاً **`FileDescriptorProto` باینری را داخل JS جاسازی می‌کنند**. روش عملی: جست‌وجوی بایت‌های توصیف‌گر، استخراج و سپس: `protoc --decode=google.protobuf.FileDescriptorProto google/protobuf/descriptor.proto < desc.bin` (یا دیکود همان بایت‌ها با پایتون + `grpcio-tools`). اگر sourcemap موجود بود، مستقیم‌تر است. 2. **از نمونه‌ها:** با چند نمونه‌ی req/resp دیکودشده با `protoc --decode_raw`، همبستگی فیلدها را استخراج کنید (فیلد شماره‌ی متن جواب، message_id درخواست و…). 3. **مسیر موازی (اپ اندروید):** APK رسمی بله (از سایت رسمی) + `jadx`؛ جست‌وجوی `"SpeechTranscript"`؛ در اندروید معمولاً شماره‌ی فیلدها واضح‌ترند. **معیار قبولی:** توان ساخت درخواست معتبر و خواندن پاسخ — حتی به‌صورت «فیلد ۱ = message_id، فیلد ۲ = متن». داشتن `.proto` کامل لازم نیست. ### T4 — اسپایک aiobale-py (۱ روز) **هدف:** اثبات امکان اجرای ترنسکرایب با یک اکانت کاربری، بدون مرورگر. **پیش‌نیاز:** venv ایزوله (**نه** محیط بات)، حساب سوخته، اسکیمای T3. **مراحل:** 1. `pip install aiobale-py` → ورود: `Client(session_file="bale.session", phone_number="…")` و عبور از OTP (دروازه‌ی انسانی). 2. کاوش لایه‌ی کدک: در `site-packages/aiobale/…` ببینید متدها چطور تعریف می‌شوند (احتمالاً مپینگ متد→پیام Protobuf). **یک متد سفارشی برای RPC ترنسکریپت کشف‌شده اضافه کنید.** 3. **کشف مکانیزم دقیق:** آیا RPC ترنسکرایب `message_id` می‌خواهد (یعنی صدا باید از قبل «پیامی در بله» باشد) یا `file_id`/بایت خام قبول می‌کند؟ هر سه فرض را تست کنید: - (الف) آپلود ویس به یک **چت اسکرچ** (چت با خود یا گروه خصوصی اختصاصی) → گرفتن `message_id` → ترنسکرایب → **حذف پیام**. - (ب) `upload_file` (در aiobale موجود است) → ترنسکرایب با فایل. - (ج) اگر RPC ورودی بایت می‌گیرد → ساده‌ترین (بدون پیام واسط). 4. تست صحت: متن برگشتی را با متنی که وب بله برای **همان ویس** نشان می‌دهد مقایسه کنید (باید یکسان/نزدیک باشد). 5. تست پایداری نشست: ری‌استارت پروسه و استفاده‌ی مجدد از session file؛ ثبت زمان انقضای توکن. **حالت‌ها:** - **موفق کامل** → مسیر R2 برای ساخت adapter انتخاب می‌شود. - **نیمه‌موفق** (login/WS می‌شود ولی RPC دیکود نمی‌شود) → T3 را عمیق‌تر + کپچر بیشتر؛ در غیر این صورت R3/R4. - **ناموفق** (چالش احراز، بن، نبود قابلیت افزودن متد) → R3 و R4. ### T-extra — تست «AI Chat به‌عنوان اوراکل» (نیم‌روز، موازی با T2/T4) **هدف:** اگر چت هوش مصنوعی بله ویس را با متن پیاده‌شده پاسخ می‌دهد، **حتی نیازی به RPC جدید نیست**: با متدهای پیام‌رسانیِ *موجودِ* aiobale، ویس را به چت AI بفرستید و متن را از پاسخ استخراج کنید. **مراحل:** با اکانت سوخته در وب بله، یک ویس به چت AI بفرستید؛ دقیقاً ببینید آیا متن پیاده‌شده در پاسخ می‌آید و با چه قالبی. سپس همان را با aiobale بازسازی کنید. **معیار:** اگر متن پیاده با قالب قابل‌اعتماد (ثابت) برگردد → مسیر R3 فعال می‌شود (ارزان‌تر از R2). ### T5 — اندازه‌گیری تأخیر/پایداری واقعی (۱–۲ روز) **هدف:** جایگزینی اعداد ساختگی گزارش با اعداد واقعی. **مراحل:** ۵۰–۱۰۰ ویس واقعی از مسیر انتخابی (ترتیبی، با فاصله‌ی انسانی ۱–۳ ثانیه)؛ ثبت p50/p95، نرخ خطا، انواع خطا، و هر نشانه‌ی rate-limit. **معیار پذیرش پیشنهادی:** نرخ موفقیت ≥ ۹۵٪ و p95 ≤ ۸ ثانیه (برای UX بات). در غیر این صورت G4 قرمز می‌شود. ### T7 — بنچمارک دقت (۱ روز) — **دروازه‌ی کسب‌وکاری، قبل از هر ساخت** **هدف:** آیا افزودن بله واقعاً خروجی نهایی را بهتر می‌کند؟ **مراحل:** 1. ۲۰–۳۰ ویس **واقعی تدارکاتی** جمع کنید (با اطلاع فرستندگان؛ صداها را بلندمدت نگه ندارید). 2. برای هر ویس سه خروجی بگیرید: (الف) وضعیت فعلی (whisper/سایر + consensus فعلی)، (ب) فقط متن بله، (ج) consensus با اضافه‌شدن کاندید بله. 3. سه معیار بسنجید: WER متن، دقت استخراج موجودیت‌ها (نام کالا/برند/تعداد/واحد در JSON نهایی)، و تعداد «موارد سخت» که با بله درست شده‌اند. **معیار پیشنهادی:** اگر کاهش خطای استخراج ≥ ~۱۵٪ نسبی **یا** حل ≥ ۲ مورد سختِ لهجه‌ای → پروژه توجیه دارد؛ در غیر این‌صورت **STOP** (همان خط لوله‌ی فعلی کافی است). ### T6 + T9 — سواک بن و ماندگاری نشست (۱–۲ هفته، پسیو) استفاده‌ی کم‌حجم روزانه (مثلاً ≤ ۱۰۰ ترنسکرایب در روز) از IP شرکت؛ ثبت: تعداد ورود مجدد لازم، چالش‌های امنیتی، هشدارها، عمر واقعی توکن. خروجی: سقف عملیاتی + runbook ورود مجدد. ### T8 — بازبینی حقوقی/ToS (موازی — **دروازه‌ی انسانی**) ایجنت فقط شواهد را جمع می‌کند؛ تصمیم Go/No-Go توسط مدیریت مستند می‌شود. معیار: سند تصمیم مکتوب. --- ## ۴. دروازه‌های تصمیم (جدول‌های وضعیت ← اقدام) ### G1 (بعد از T1) | وضعیت مشاهده‌شده | اقدام | |---|---| | فیلد ترنسکریپشن روی ویس هست | **پایان:** R1 — مستقیم به بات وصل کن (بدون هیچ کرالری) | | نیست | برو T2 | ### G2 (بعد از T2) — مهم‌ترین دروازه‌ی فنی | وضعیت مشاهده‌شده | معنا | اقدام | |---|---|---| | RPC از نوع **unary POST gRPC-Web** با پروتوباف دیکودپذیر | بهترین حالت | برو T3+T4 → R2 | | RPC **فقط روی WebSocket** | حالت مطلوب دوم (aiobale دقیقاً WS است) | برو T3+T4 → R2 (از کانال WS؛ **POST gRPC زیاد نزن**) | | درخواست **رمزنگاری/امضای اختصاصی** دارد (فراتر از پروتوباف) | هزینه‌ی نگهداری بالا | R4 (Playwright) یا R3 به‌عنوان مسیر اصلی؛ R2 فقط اگر … شکست | | ترنسکرایب نیازمند **اثر انگشت/رفتار مرورگر** است | R2 غیرممکن | R4 | | ترنسکرایب **message_id روی پیامی موجود** می‌خواهد | نیازمند «آپلود به چت اسکرچ» | R2 با فلوی «آپلود→ترنسکرایب→حذف» (بخش ۵.۲) | ### G3 (بعد از T3/T4) | وضعیت | اقدام | |---|---| | ساخت درخواست + خواندن پاسخ ممکن شد | برو T5 → T7 | | فقط یکی از دو جهت ممکن است | کپچر بیشتری بگیر (T2)؛ اگر نشد → R3/R4 | | شکست کامل | R3 (اگر T-extra موفق بود) وگرنه R4؛ اگر هر دو هم نشد → STOP | ### G4 (بعد از T5) | وضعیت | اقدام | |---|---| | نرخ موفقیت ≥ ۹۵٪ و p95 ≤ ۸s | ادامه | | rate-limit مشاهده شد | کاهش نرخ + backoff نمایی + سقف روزانه؛ مجدداً بسنج | | بن/چالش امنیتی | توقف فوری؛ رفتن به R4 با اکانت جدید، یا STOP | ### G5 (بعد از T7) — دروازه‌ی کسب‌وکار | وضعیت | اقدام | |---|---| | بهبود دقت مطابق آستانه | ادامه به G6 | | بهبود ناکافی | **STOP** — همان پرووایدرها؛ یافته‌ها را مستند کن | ### G6 (ToS) / G7 (سواک) | وضعیت | اقدام | |---|---| | ToS سبز + سواک سالم | استقرار کامل (بخش ۶) | | ToS قرمز | STOP یا فقط R4 با حجم محدود (طبق تصمیم مدیریت) | | سواک ناسالم | rollback به پرووایدرهای قبلی (حذف envها) و بازگشت به G4 | --- ## ۵. فاز B — پیاده‌سازی (فقط پس از عبور از دروازه‌ها) ### ۵.۱ — قرارداد مشترک adapter (برای همه‌ی مسیرها یکسان) سرویس `bale-stt` (کانتینر داخلی، **بدون پورت عمومی**): - `POST /v1/audio/transcriptions` — multipart، فیلد `file` (فایل MP3)، `model`، `language=fa` → پاسخ `{"text": "..."}` (دقیقاً قرارداد OpenAI که بات انتظار دارد). - هدر `Authorization: Bearer ` را بپذیرد (فقط کلید داخلی، نه توکن بله). - `GET /healthz` برای سلامت. - موتور داخلی قابل‌سوییچ با env: `BALE_STT_ENGINE=grpc|aichat|playwright` → همه‌ی مسیرها همزیستی دارند و تعویض موتور بدون تغییر بات. ### ۵.۲ — R2: مهندسی معکوس (مسیر پیشنهادی اصلی) معماری: `bot → adapter → aiobale-py (اکانت سوخته) → WS/gRPC بله → متن` نکات پیاده‌سازی: - اکانت: `session_file` روی یک **داکر ولوم**؛ هرگز در گیت. `BALE_PHONE` و سکرت‌ها در env. - اگر RPC به message_id نیاز دارد: فلوی «آپلود ویس به چت اسکرچ → ترنسکرایب → **حذف پیام**» — با `send_voice` و `delete_message` موجود در aiobale. - ترتیبی اجرا کنید (قفل asyncio)، فاصله‌ی ۱–۳ ثانیه، سقف روزانه قابل‌تنظیم (`BALE_STT_DAILY_CAP`). - مدیریت انقضای توکن: تشخیص خطای احراز → علامت‌گذاری «نیازمند OTP» + هشدار به ادمین (دروازه‌ی انسانی). - fail-soft مضاعف: adapter در هر خطا `{"text": ""}` (یا 5xx کنترل‌شده) برگرداند تا بات فقط این کاندید را حذف کند. ### ۵.۳ — R3: اوراکل AI Chat همان adapter، اما داخلش: ارسال ویس به چت AI (با aiobale) → استخراج متن از قالب پاسخ → بازگرداندن. سبک‌ترین پیاده‌سازی؛ وابسته به پایداری قالب پاسخ. ### ۵.۴ — R4: Playwright (فالبک) کانتینر جدا با Chromium headless + پروفایل پایدار (`userDataDir` روی ولوم)، یک worker تک‌نمونه با صف؛ مراحل: ورود یک‌باره (OTP انسانی) → برای هر درخواست: آپلود ویس به یک چت اسکرچ → کلیک «تبدیل به متن» → انتظار روی سلکتور متن → استخراج → حذف پیام. RAM/زمان بیشتر؛ مقاوم‌تر به تغییر پروتکل. ### ۵.۵ — ادغام با بات (بدون تغییر کد) در `docker-compose.yml` (نسخه‌ی تیم/سرور، **نه** برنچ دیگران) سرویس `bale-stt` اضافه و در سرویس `bot` این envها ست شود: ``` PROCUREMENT_BOT_STT_PROVIDER_3_NAME=bale PROCUREMENT_BOT_STT_PROVIDER_3_BASE_URL=http://bale-stt:8080/v1 PROCUREMENT_BOT_STT_PROVIDER_3_API_KEY= PROCUREMENT_BOT_STT_PROVIDER_3_MODEL=bale-stt PROCUREMENT_BOT_STT_PROVIDER_3_LANGUAGE=fa ``` (یادآوری از کد: پرووایدر فقط با پر بودن همزمان BASE_URL و API_KEY فعال می‌شود.) ### ۵.۶ — سکرت‌ها و امنیت | سکرت | محل | نکته | |---|---|---| | توکن بات بله | env سرور | **موجود در compose کامیت‌شده → rotate** | | کلید DeepSeek | env سرور | **همین‌طور rotate** | | `session_file` اکانت سوخته | داکر ولوم | هرگز گیت؛ مالک مشخص | | `INTERNAL_KEY` adapter | env | فقط بین bot و adapter | | `DRAFT_TOKEN_SECRET` بات | env | پیش‌فرض فعلی در کد ضعیف است → ست شود | ### ۵.۷ — مقیاس فاز اول: تک‌نمونه، ترتیبی، سقف روزانه. توسعه فقط بعد از G7 و فقط با شواهد پایداری. --- ## ۶. پذیرش، استقرار، پایش، بازگشت **تست‌های پذیرش (اجباری):** 1. `curl -F "file=@voice.mp3" -F model=bale-stt http://localhost:8080/v1/audio/transcriptions` → `{"text": "<متن فارسی درست>"}`. 2. بات: ارسال ویس واقعی → پاسخ استخراج‌شده‌ی صحیح. 3. **fail-soft:** توقف کانتینر `bale-stt` → بات همچنان ویس را (با پرووایدرهای دیگر) پردازش کند ✓. 4. حریم خصوصی: بررسی کنید هیچ فایل صوتی روی adapter باقی نمی‌ماند و پیام‌های اسکرچ حذف شده‌اند. **استقرار فازبندی‌شده:** dev → سرور با حجم کم (۱ هفته) → کامل. **پایش:** شمارش درخواست، نرخ موفقیت، هیستوگرام تأخیر، انواع خطا، وضعیت نشست. هشدار اگر نرخ خطا در ۳۰ دقیقه > ۲۰٪. **بازگشت (Rollback):** حذف `STT_PROVIDER_3_*` از env بات + توقف کانتینر adapter — بات فوراً به وضعیت قبل برمی‌گردد (بدون deploy مجدد). --- ## ۷. رجیستر ریسک | ریسک | احتمال | اثر | مقابله | |---|---|---|---| | بن/rate-limit اکانت | متوسط | توقف مسیر | حجم کم، WS-first، backoff، اکانت اختصاصی قابل‌تعویض، G4 | | تغییر پروتکل بله | متوسط | قطع سرویس | pایش نسخه‌ی باندل (hash)، آلرت خطا، فالبک R4، سوییچ موتور با env | | انقضای نشست/نیاز OTP | بالا (دوره‌ای) | توقف موقت | ولوم session + آلرت + runbook ورود (انسانی) | | دقت ناکافی نسبت به هزینه | نامعلوم | هزینه‌ی بی‌فایده | **G5 قبل از ساخت** | | ریسک حقوقی/ToS | نامعلوم | توقف اجباری | **T8/G6 — تصمیم مدیریت** | | حریم خصوصی صدا | کم | اعتباری | حذف فوری پیام‌ها/فایل‌ها؛ عدم نگهداری | | وابستگی به فرد نگهدارنده | متوسط | رکود | مستندسازی در همین پلی‌بوک + مالک رسمی | --- ## ۸. دروازه‌های انسانی (فقط انسان می‌تواند) 1. **OTP ورود اکانت** (بار اول و هر بار انقضا). 2. **تصمیم ToS/حقوقی (G6).** 3. **جمع‌آوری نمونه‌های واقعی ویس برای T7** (با رضایت). 4. **تأیید نهایی استقرار** و تأیید مرج برنچ توسط مهندس ارشد. --- ## ضمیمه A — دستورات probe (تکرارپذیر) ```bash TOKEN="" for M in transcribeVoice getTranscription transcribe speechToText voiceToText; do curl -s -X POST "https://tapi.bale.ai/bot$TOKEN/$M" -H "Content-Type: application/json" -d '{}'; echo done curl -s -X POST "https://tapi.bale.ai/bot$TOKEN/getMe" -H "Content-Type: application/json" -d '{}' ``` خروجی مورد انتظار: پنج 404 «handler not found» و یک getMe سالم. (همین تست را قبل از هر تصمیم مجدداً اجرا کنید — شاید بله متد اضافه کرده باشد.) ## ضمیمه B — اسکلت adapter (قابل استفاده برای هر سه موتور) ```python # bale_stt/adapter.py — قرارداد OpenAI-compatible برای بات import os from fastapi import FastAPI, File, Form, Header, HTTPException, UploadFile app = FastAPI() INTERNAL_KEY = os.environ["BALE_STT_INTERNAL_KEY"] ENGINE = os.environ.get("BALE_STT_ENGINE", "grpc") # grpc | aichat | playwright @app.get("/healthz") def healthz(): return {"status": "ok", "engine": ENGINE} @app.post("/v1/audio/transcriptions") async def transcribe(file: UploadFile = File(...), model: str = Form("bale-stt"), language: str = Form("fa"), authorization: str = Header("")): if authorization != f"Bearer {INTERNAL_KEY}": raise HTTPException(status_code=401) audio = await file.read() if ENGINE == "grpc": text = await grpc_transcribe(audio) # ← aiobale-py + RPC کشف‌شده elif ENGINE == "aichat": text = await aichat_transcribe(audio) # ← ارسال به چت AI و استخراج متن else: text = await playwright_transcribe(audio) return {"text": text or ""} # نکته: هرگز استثنا را بیرون نده؛ در خطا {"text": ""} برگردان تا بات fail-soft بماند. ``` ## ضمیمه C — منابع - aiobale-py: [PyPI](https://pypi.org/project/aiobale-py/) · [GitHub](https://github.com/aminmadaniofficial/aiobale) · [مستندات](https://aminmadaniofficial.github.io/aiobale/) - مستندات رسمی بات بله: [docs.bale.ai](https://docs.bale.ai) - سرویس ثالث STT: [majidapi.ir](https://majidapi.ir) - گزارش‌های ایجنت شرکت: `bale_stt_integration_report.md` و `bale_stt_integration_report_2.md` (کنار همین فایل) ## ضمیمه D — چک‌لیست سریع ایجنت (TL;DR) 1. [ ] قواعد بخش ۰ را خواندم. 2. [ ] T1 (payload ویس) → G1. 3. [ ] T2 (کپچر) → G2 → مسیر (R2 / R3 / R4) انتخاب شد. 4. [ ] T3+T4 (اسکیما + اسپایک) → G3. 5. [ ] T-extra (اوراکل AI Chat) اگر مسیر R3 مدنظر است. 6. [ ] T5 (تأخیر واقعی) → G4. 7. [ ] T7 (دقت) → G5 — **بدون این، ساخت ممنوع.** 8. [ ] T8 (ToS — انسانی) → G6. 9. [ ] ساخت adapter (۵.۱) + ادغام env (۵.۵) + تست‌های پذیرش (بخش ۶). 10. [ ] سواک (T6/T9) → G7 → استقرار کامل. هر شکست ⇒ جدول همان دروازه ⇒ اقدام جایگزین یا STOP.