Files
ERP/bale stt report/bale_stt_opensource_thirdparty_assessment (1).md
T

90 KiB
Raw Blame History

گزارش جامع و دستورکار اجرایی: کرالر STT بله، متن‌باز، شخص ثالث و ترکیب بهینه

نسخه: 2.0 | تاریخ: ۱۶ مهر ۱۴۰۵ (8 Oct 2026)
مخاطب اول: مدیر پروژه (انسان) ← بخش A
مخاطب دوم: ایجنت هوش مصنوعی مجری ← بخش B (دستورکار) و بخش C (دانش‌پایه)
ضمیمه‌ها: بخش D

ورودی‌های بررسی‌شده: گزارش A (متن‌باز و شخص ثالث)، گزارش B (bale_stt_executive_roadmap_report3.md)، و راستی‌آزمایی مستقل با منابع عمومی (PyPI، مستندات رسمی بله، arXiv، Hugging Face، GitHub).
دامنه: طبق درخواست، حریم خصوصی و امنیت در این سند تصمیم‌گیری نشده است. فقط «راه‌ها، ارزیابی، نظر من و ترکیب بهینه» آمده است. فقط جاهایی که ایجنت برای اجرای فنی لازم دارد (مثل مدیریت سکرت‌ها) ذکر شده است.

برچسب‌های وضعیت در کل سند:
✅ تأیید شد (با منبع عمومی) | 🟡 تأییدنشده یا استنباط من | ❌ با شواهد در تناقض است

محدودیت من: شبکه sandbox من بسته بود؛ پکیجی را نصب نکردم و پروتو یا کد کتابخانه‌ها را نخواندم. هر جا «🟡» می‌بینید یعنی ایجنت باید خودش بررسی کند، نه اینکه لزوماً غلط باشد.

راهنمای استفاده از این سند

اگر شما… این بخش‌ها را بخوانید
مدیر پروژه هستید بخش A (۵ دقیقه) و در صورت نیاز جدول ریسک و تصمیم‌ها
ایجنت مجری هستید بخش B کامل (دستورکار)، سپس بخش C برای زمینه و ضمیمه D برای دستورها
می‌خواهید همه راه‌ها و نظرات را ببینید بخش C

بخش A: برای مدیر پروژه

A1. مسئله و هدف

بات تدارکات متن کاربر را می‌گیرد و عملیات روی آن انجام می‌دهد. می‌خواهیم کاربر بتواند ویس هم بدهد. بات اکنون چند ASR را موازی صدا می‌زند و با یک LLM اجماع‌گیری می‌کند. هدف این پروژه: افزودن متن STT خود پیام‌رسان بله به‌عنوان یکی از کاندیدها تا دقت (به‌خصوص برای نام کالا، برند، عدد و واحد) بالا برود.

مشکل پایه: Bot API رسمی بله هیچ متد STT ندارد (✅ تأیید شد؛ در مستندات رسمی، شیء Voice فقط file_id و file_unique_id دارد). پس تنها راه گرفتن متن بله، شبیه‌سازی یک کاربر عادی (حساب کاربری) است.

A2. خلاصه یافته‌ها

  1. هیچ پروژه عمومی‌ای که خروجی STT خود بله را استخراج کند پیدا نشد. جامعه معمولاً STT را بیرون از بله انجام می‌دهد. ابزارهای مجاور (کلاینت‌های مهندسی‌معکوس‌شده، سلف‌بات‌ها با Selenium/Playwright) وجود دارند و نشان می‌دهند مسیر شدنی است، ولی برای این کار آزموده نشده است.
  2. دو ادعای کلیدی هر دو گزارش تأییدنشده‌اند و پروژه به آن‌ها وابسته است:
    • متد bale.turing.v1.AI / GetTranscript در bale-sdk (🟡 نتوانستم ببینم).
    • وجود دکمه «تبدیل به متن» در نسخه وب بله (🟡 هیچ منبعی تأیید نکرده؛ فقط اپ اندروید/iOS در اخبار آمده است).
  3. کتابخانه‌ها: bale-sdk (۶۰۷ RPC، نسخه 0.1.1) مجوز اختصاصی دارد؛ aiobale و فورک‌هایش autobale و BaleClient MIT هستند ولی کم‌نگهداری. پکیج aiobale-py که گزارش‌ها می‌گویند پیدا نشد (🟡).
  4. متن‌باز لزوماً دقیق‌تر نیست. بنچمارک مستقل PSRB نشان می‌دهد ASR تجاری فارسی و Google Chirp 2 حدود ۱۴ امتیاز WER از بهترین مدل متن‌باز جلوترند (۱۹٫۳ تا ۲۴٫۶ درصد در برابر ۳۳٫۹ درصد). نویسندگان مقاله از Part AI هستند و Avanegar ظاهراً محصول همان شرکت است؛ پس عدد آن را خوش‌بینانه بدانید.
  5. ایده معماری جدید و ساده‌تر: به‌جای اینکه حساب کرالر ویس آپلود کند، یک بات رله (جدا از بات اصلی) ویس را با Bot API رسمی برای حساب کرالر بفرستد. کرالر فقط دریافت، ترنسکرایب و حذف می‌کند. (جزئیات در B5.)
  6. ارزش پروژه را فقط آزمون دقت تعیین می‌کند. پیشنهاد می‌کنم قبل از هر ساخت روی ۳۰ تا ۱۰۰ ویس واقعی اندازه بگیریم (گام T2).

A3. گزینه‌ها در یک نگاه

# گزینه چه می‌کند تلاش (تخمین من) ریسک پتانسیل دقت توصیه
1 مسیر رسمی با بله درخواست API یا همکاری رسمی ۱ ایمیل کم — حتماً انجام شود (موازی)
2 کرالر RPC مستقیم (کلاینت کاربری) حساب اختصاصی ویس را ترنسکرایب می‌کند ۳–۵ روز متوسط ⭐ نامعلوم تا آزمون مسیر اصلی کرالر
3 کرالر Playwright روی وب بله مرورگر headless دکمه را می‌زند ۱–۲ روز متوسط همان فالبک (اگر دکمه وب باشد)
4 شبیه‌ساز اندروید اپ بله در امولاتور ۵+ روز بالا همان فقط آخرین چاره
5 چت هوش مصنوعی بله ویس به بات AI بله ۱ روز بالا (خروجی ناخالص) پایین رد
6 پراکسی غیررسمی (MajidAPI و مشابه) REST با لینک ویس ساعتی بالا (بدون SLA) نامعلوم رد
7 ASR تجاری فارسی (Avanegar، Aipa، فارس‌آوا، IOType) پرووایدر جدید در stt.py ۱ روز کم ⭐⭐⭐ بالا توصیه قوی
8 Google Chirp 2 (و ابرهای بین‌المللی) پرووایدر جدید ۱ روز متوسط (دسترسی و ارز) ⭐⭐⭐ بالا توصیه (اگر دسترسی هست)
9 Faster-Whisper خودمیزبان + واژگان تدارکات سرور داخلی ۱–۲ روز کم ⭐⭐ متوسط توصیه (مستقل)
10 فاین‌تیون روی داده خودتان (LoRA) مدل داخلی هفته‌ها کم ⭐⭐⭐ در بلندمدت سرمایه‌گذاری بلندمدت
11 LLM اجماع‌گیر + کاتالوگ کالا + نرمال‌سازی تقویت لایه اجماع چند روز کم ⭐⭐⭐ بیشترین بازده حتماً

A4. توصیه من

ترکیب هدف: «بله + ASR تجاری فارسی + مدل خودمیزبان + LLM اجماع‌گیر با کاتالوگ کالا»

  • کرالر بله یک کاندید است، نه ستون فقرات. اگر خراب یا مسدود شد، بات باید بی‌وقفه با بقیه ادامه دهد (ساختار همین‌طور طراحی شده: خطا ⇒ ۵۰۳ ⇒ رد شدن پرووایدر).
  • حداقل دو کاندید غیر بله همیشه فعال باشد.
  • ترتیب کار: (۱) آزمون دقت و تأیید دو ادعای کلیدی ⇐ (۲) فقط اگر ارزش داشت، ساخت کرالر ⇐ (۳) به‌موازات، پرووایدرهای شخص ثالث و تقویت اجماع ⇐ (۴) سواک ⇐ (۵) جمع‌آوری داده برای فاین‌تیون.
  • در صورتی که آزمون دقت نشان دهد بله بهتر نیست، کرالر ساخته نشود و سرمایه روی گزینه‌های ۷ تا ۱۱ برود.

A5. تصمیم‌ها و ورودی‌هایی که از شما لازم است

از آنجا که تمام اجرا توسط ایجنت AI انجام می‌شود، این‌ها تنها نقاطی‌اند که ایجنت نمی‌تواند خودش انجام دهد یا نباید تصمیمش را بگیرد.

ID تصمیم / ورودی چرا لازم است پیش‌فرض پیشنهادی من مسدودکننده برای
H1 Go/No-Go حقوقی/ToS برای خودکارسازی یک حساب کاربری بله بله ممکن است آن را مجاز نداند؛ تصمیم مدیریتی است تصمیم کتبی قبل از T4 T4 به بعد
H2 شماره/سیم‌کارت اختصاصی شرکتی و یک حساب بله برای کرالر کرالر باید با یک هویت ثابت کار کند شماره حقوقی، نه شخصی T4 به بعد
H3 نمونه ویس‌ها (حداقل ۳۰، بهتر ۱۰۰) با رونوشت انسانی و جدول کالا/تعداد/واحد تنها راه معتبر برای سنجش دقت از ویس‌های واقعی استعلام، با رضایت فرستنده T2، T9، T10
H4 ورود OTP: وقتی ایجنت کد پیامکی لازم دارد، کد را می‌دهید OTP را فقط انسان دریافت می‌کند پاسخ ظرف چند دقیقه T4
H5 ساخت بات رله با BotFather و تحویل توکن آن معماری رله (B5) یک بات جدا از بات اصلی T6
H6 دسترسی ایجنت به مخزن بات (bot/app/stt.py, config.py, .env نمونه) هماهنگی با قرارداد پرووایدرها فقط خواندن + تغییر .env T0، T11
H7 کلیدها/اکانت آزمایشی پرووایدرهای تجاری (Avanegar، Aipa، فارس‌آوا، Chirp 2…) و سقف بودجه سنجش واقعی سقف ماهانه مشخص T9
H8 سرور برای Faster-Whisper (ترجیحاً GPU) یا اجازه استفاده از سرور فعلی میزبانی مدل خودمیزبان GPU با ۸GB VRAM یا بیشتر T9
H9 ارسال ایمیل به بله برای مسیر رسمی (S1) و مالک پیگیری ایجنت نمی‌تواند به‌جای شرکت مکاتبه کند همین هفته —
H10 مالک نگهداری کرالر پس از استقرار اگر پروتکل بله عوض شد یک نفر مشخص استقرار نهایی
H11 تأیید نهایی استقرار و مرج دروازه انسانی پس از G5 T11

A6. زمان‌بندی و هزینه (تخمین من)

فاز مدت یادداشت
تأیید ادعاها + آزمون دقت (T0–T3 و مقایسه) ۱–۲ روز هزینه صفر؛ بیشترین ارزش اطلاعاتی
کشف و پروتوتایپ کرالر (T4–T6) ۲–۴ روز فقط اگر G0 و G1 عبور کنند
adapter سخت‌شده + اتصال (T8، T11) ۲ روز
پرووایدرهای شخص ثالث و مدل خودمیزبان (T9) ۲–۳ روز، موازی هزینه ریالی/ارزی طبق قرارداد؛ استعلام رسمی بگیرید
تقویت اجماع (T10) ۲–۳ روز بیشترین بازده
سواک ۷ روز
فاین‌تیون (T13) ۲+ هفته، اختیاری

قیمت‌هایی که گزارش A آورده (مثلاً ۴۵–۶۰ هزار تومان در ساعت برای فارس‌آوا یا ۲۴۰–۵۰۰ تومان در دقیقه برای بات‌های واسط) 🟡 تأییدنشده‌اند؛ قبل از بودجه‌بندی استعلام بگیرید.

A7. ریسک‌های اصلی

ریسک احتمال اثر مقابله
RPC GetTranscript وجود ندارد یا پایدار نیست نامعلوم ساخت E1 ممکن نیست تأیید در T1 و T5؛ فالبک Playwright
دکمه در وب نیست نامعلوم Playwright ممکن نیست تأیید در T4
بن یا محدودیت حساب متوسط از دست رفتن کاندید بله حجم کم، فاصله تصادفی، سقف روزانه، ۵۰۳ و حذف از اجماع
تغییر پروتکل/UI بله متوسط قطع کاندید بله آزمون طلایی شبانه، هشدار، rollback با env
انقضای نشست و نیاز به OTP بالا (دوره‌ای) توقف موقت ولوم نشست، هشدار به انسان، تغییر مسیر به بقیه
کتابخانه‌های MIT رها می‌شوند متوسط نگهداری سخت pin نسخه، آینه داخلی، لایه‌بندی
مجوز اختصاصی bale-sdk بالا ممنوعیت استفاده از آن فقط به‌عنوان مرجع و با اجازه نویسنده؛ کد را کپی نکنید
دقت بله کمتر از انتظار متوسط پروژه بی‌ارزش G0 پیش از ساخت
ToS بله نامعلوم توقف اجباری H1 (تصمیم مدیریت)

A8. معیارهای موفقیت و توقف (دروازه‌ها)

دروازه شرط عبور (قابل‌تنظیم) اگر رد شد
G0 دقت بله خطای استخراج اقلام اجماع را حداقل ۱۵٪ نسبی کم می‌کند، یا حداقل ۲ مورد سخت را درست می‌کند که بقیه نمی‌کنند توقف کرالر، تمرکز روی گزینه‌های ۷ تا ۱۱
G1 وجود RPC تأیید شد یا دکمه وب تأیید شد شبیه‌ساز (فقط اگر G0 خیلی خوب بود) یا توقف
G2 کشف درخواست/پاسخ قابل بازتولید و پروتو ساخته شد فالبک Playwright
G3 پروتوتایپ ۱ ویس ⇒ ۱ متن فارسی معتبر بازبینی طراحی
G4 پایداری ≥ ۹۵٪ موفقیت روی ۱۰۰ ویس ترتیبی، p95 ≤ ۸ ثانیه، بدون بن کاهش نرخ؛ در بن، توقف
G5 ارزش بهبود اجماع روی مجموعه طلایی تأیید شد حذف بله از اجماع، ادامه با بقیه
G6 ToS تصمیم کتبی مدیریت توقف
G7 سواک یک هفته بدون ابطال نشست بازگشت به G4

A9. نظر من درباره دو گزارش (خلاصه)

گزارش A (متن‌باز و شخص ثالث): دسته‌بندی ۸ راهکار و پیشنهاد دومسیره درست است. ایرادها: چند ادعای تأییدنشده (aiobale-py، GetTranscript، قیمت‌ها)؛ «متن‌باز پایدارترین راهکار است» با داده PSRB سازگار نیست ❌؛ ارائه‌دهندگان مهم (Avanegar، Aipa، Chirp 2) غایب‌اند؛ مقایسه دقت فقط با WER خودگزارشی روی Common Voice.

گزارش B (نقشه راه عملیاتی): دیاگرام‌ها و جریان تاب‌آوری خوب‌اند و اصلاحات قبلی اعمال شده (۵۰۳، compare_digest، قفل ترتیبی). ایرادها:

  • کد مرجع شبه‌کد است. client.send_voice و client.raw_call در README عمومی bale-sdk نیستند. مدل رسمی RPC خام آن client.<service>.<Method>(...) است و نام namespace از بخش آخر نام سرویس می‌آید؛ پس فراخوانی bale.turing.v1.AI احتمالاً به‌شکل client.ai.GetTranscript(...) است (🟡 استنباط).
  • پاسخ GetTranscript طبق خود سند mustWait و DownloadSource است؛ یعنی متن شاید باید دانلود شود، نه رشته مستقیم.
  • کلید پیش‌فرض در کد (bale_internal_secret) و شمارنده روزانه درون‌حافظه‌ای (با ریست کانتینر صفر می‌شود).
  • آپلود ویس به Saved Messages طراحی نشده؛ راه رله ساده‌تر است (B5).
  • ادعای «بدون حتی ۱ ثانیه قطعی» فقط وقتی درست است که stt.py پاسخ ۵۰۳ را بی‌خطا رد کند؛ باید در کد بات بررسی شود (T0).

A10. آیا دیگران هم این مشکل را داشته‌اند؟

هیچ نمونه عمومی از استخراج STT خود بله پیدا نشد. الگوهای مجاور (همه در بخش C۲): بات رسمی + ASR مستقل (رایج‌ترین)، کلاینت کاربری مهندسی‌معکوس‌شده (aiobale، bale-sdk)، سلف‌بات Selenium (baleself)، اسکرپر Playwright روی Bale Web. نتیجه: مسیر شدنی است ولی پیشگام‌شدن ریسک دارد؛ برای همین کرالر را یک کاندید اختیاری و قابل‌حذف طراحی می‌کنیم.


بخش B: دستورکار ایجنت مجری

تو یک ایجنت هوش مصنوعی هستی که این پروژه را اجرا می‌کنی. این بخش دستورکار توست. بخش C دانش‌پایه است. بخش D دستورها و قالب‌ها را دارد. هر جا نوشته «انسان»، یعنی باید متوقف شوی و از انسان بخواهی (جدول H1…H11 در A5).

B0. مأموریت

افزودن متن STT پیام‌رسان بله به‌عنوان پرووایدر سوم خط‌لوله bot/app/stt.py، از طریق یک میکروسرویس adapter با قرارداد OpenAI (POST /v1/audio/transcriptions)، بدون تغییر کد بات (فقط .env). همزمان، پرووایدرهای شخص ثالث و مدل خودمیزبان را ارزیابی و اضافه می‌کنی و لایه اجماع را تقویت می‌کنی. هر مرحله با دروازه‌های A8 کنترل می‌شود.

B1. قواعد اجرا (الزامی)

  1. صداقت و برچسب‌گذاری: هر یافته را با یکی از ✅/🟡/❌ و منبع یا خروجی دستور ثبت کن. هرگز چیزی را که اجرا یا مشاهده نکرده‌ای «تأییدشده» ننویس. اگر نتوانستی بررسی کنی، بنویس «بررسی نشد» و دلیل را بگو.
  2. سند را حقیقت مطلق ندان. مواردی که در این سند 🟡 هستند را اول بررسی کن (T0–T1). اگر با واقعیت نخواند، سند را اصلاح و به انسان گزارش بده.
  3. کد گزارش B و بخش‌های «شبه‌کد» اینجا را اجراشدنی فرض نکن. نام متدها را از کتابخانه واقعی نصب‌شده بخوان (help(), dir(), خواندن سورس).
  4. مجوز: کدی از bale-sdk کپی نکن (اختصاصی). مجاز: خواندن مستندات عمومی آن برای شناخت نام سرویس/متد، و تولید پروتوی خودت از کپچر ترافیک (clean-room). استفاده از سورس/پروتوهای آن فقط با تأیید کتبی انسان (H1).
  5. فقط حساب اختصاصی (H2). هرگز از حساب شخصی استفاده نکن.
  6. نرخ در آزمایش: حداکثر ۱ درخواست در هر ۳ ثانیه و ۱۰۰ درخواست در روز روی حساب بله؛ فاصله تصادفی؛ اجرای ترتیبی. در صورت خطای ۴۲۹/محدودیت یا چالش، فوراً متوقف شو و به انسان گزارش بده.
  7. سکرت‌ها: توکن بات، JWT، کوکی، کلید API و کد OTP را هرگز در لاگ، commit، گزارش یا خروجی چاپ نکن. در کپچرها access_token، Authorization، کوکی‌ها و شماره تلفن را قبل از ذخیره redact کن. نشست فقط روی ولوم داکر.
  8. تغییر در مخزن بات: فقط .env (و در صورت لزوم مستند). هیچ چیز را روی شاخه‌های دیگران push نکن. کار روی شاخه جدا (مثلاً feat/bale-stt) و PR به انسان.
  9. متوقف شو و بپرس در این موارد: نیاز به OTP، نیاز به تصمیم حقوقی، هر خطای احتمال بن، هزینه جدید، تغییری خارج از محدوده، یا نتیجه‌ای که با فرض‌های این سند در تناقض باشد.
  10. بازتولیدپذیری: هر آزمایش را با اسکریپت در tools/ بنویس، نه دستور یک‌بارمصرف. خروجی‌ها در docs/findings/.
  11. قطع سریع (kill switch): همیشه راه خاموش‌کردن کاندید بله با env (خالی کردن PROCUREMENT_BOT_STT_PROVIDER_3_*) موجود باشد.
  12. هیچ کاری که یک تصمیم برگشت‌ناپذیر است را بدون تأیید انسان انجام نده (حذف داده، تغییر شماره، حذف حساب، مصرف بودجه).

B2. ورودی‌های لازم از انسان

همان جدول A5 (H1–H11). قبل از هر وظیفه، چک کن ورودی‌های مورد نیازش آمده است؛ اگر نه، همان لحظه با قالب B9.2 از انسان بخواه و تا رسیدن، وظایف مستقل دیگر را پیش ببر.

B3. چیدمان پیشنهادی مخزن

bale-stt/
├── adapter/
│   ├── main.py              # FastAPI، قرارداد OpenAI
│   ├── config.py            # خواندن env (الزامی‌ها بدون پیش‌فرض)
│   ├── counters.py          # شمارنده روزانه پایدار (SQLite)
│   ├── engines/
│   │   ├── base.py          # رابط Engine
│   │   ├── stub.py          # موتور آزمایشی (بدون بله)
│   │   ├── rpc.py           # E1: RPC مستقیم + رله
│   │   ├── playwright_engine.py  # E2
│   │   └── relay_bot.py     # کلاینت Bot API برای بات رله
│   ├── tests/
│   ├── Dockerfile
│   └── requirements.txt
├── tools/
│   ├── probe_bot_api.py     # T3
│   ├── capture_ws.py        # T4/T5
│   ├── decode_frames.py     # T5
│   └── eval/
│       ├── normalize.py     # نرمال‌سازی فارسی
│       ├── metrics.py       # CER/WER/F1 اقلام
│       └── run_eval.py
├── docs/findings/           # خروجی هر وظیفه
├── docker-compose.stt.yml
└── .env.example             # بدون مقدار واقعی

B4. گراف وظایف (قابل‌خواندن توسط ماشین)

tasks:
  T0:  {name: "بازبینی کد بات (stt.py/config.py)", needs: [H6], gate: null}
  T1:  {name: "تأیید پکیج‌ها و جست‌وجوی GetTranscript", needs: [], gate: G1 (بخش الف)}
  T2:  {name: "آزمون دقت دستی/نیمه‌خودکار (کیت ارزیابی)", needs: [H3], gate: G0}
  T3:  {name: "پروب Bot API (فیلد مخفی؟)", needs: [H5 or existing_bot_token], gate: null}
  T4:  {name: "بررسی وب بله + کپچر ترافیک", needs: [H1, H2, H4], gate: G1}
  T5:  {name: "رمزگشایی و ساخت پروتو (clean-room)", needs: [T4], gate: G2}
  T6:  {name: "پروتوتایپ E1 با بات رله", needs: [T5, H5], gate: G3}
  T7:  {name: "موتور فالبک Playwright (E2)", needs: [T4], gate: null, optional: true}
  T8:  {name: "adapter سخت‌شده + تست‌ها", needs: [T6 or T7 or stub], gate: null}
  T9:  {name: "پرووایدرهای شخص ثالث + Faster-Whisper", needs: [H3, H7, H8], gate: null, parallel: true}
  T10: {name: "تقویت اجماع (نرمال‌سازی، کاتالوگ، prompt)", needs: [H3], gate: G5, parallel: true}
  T11: {name: "اتصال به بات (.env) + E2E + rollback", needs: [T8, H11], gate: G4}
  T12: {name: "سواک و مانیتورینگ", needs: [T11], gate: G7}
  T13: {name: "جمع‌آوری داده و فاین‌تیون", needs: [T12, H3], gate: null, optional: true}
start_order: [T0, T1, T2, T3, T9, T10]   # مستقل از بله، همین حالا شروع می‌شوند
stop_rules:
  - "اگر G0 رد شد: T4..T8 و T11 اجرا نشود؛ فقط T9، T10 و T13"
  - "اگر G6 (ToS) تأیید نشد: هیچ وظیفه‌ای که با حساب بله کار می‌کند اجرا نشود"

B5. طراحی هدف (که باید بسازی)

B5.1 جریان کلی

sequenceDiagram
    autonumber
    actor U as کاربر
    participant BOT as بات اصلی (stt.py)
    participant AD as adapter (bale-stt)
    participant RB as بات رله (Bot API)
    participant CR as کلاینت کاربری (حساب کرالر، همان سرویس)
    participant BS as سرورهای بله
    U->>BOT: ویس
    BOT->>AD: POST /v1/audio/transcriptions (بایت‌های صوت)
    AD->>AD: ffmpeg → OGG/Opus، ساخت nonce
    AD->>RB: sendVoice(chat_id=کرالر، voice=OGG، caption=nonce)
    RB->>BS: ارسال
    BS-->>CR: آپدیت پیام جدید (Media، Peer، message_id، caption=nonce)
    CR->>BS: GetTranscript(voice, outPeer, messageId)
    BS-->>CR: mustWait / DownloadSource
    CR->>BS: (تکرار تا آماده) سپس دانلود متن
    CR->>BS: حذف پیام
    CR-->>AD: متن
    AD-->>BOT: {"text": "..."}  یا  HTTP 503

B5.2 چرا «بات رله» و نه «آپلود از حساب کرالر»؟

  • adapter طبق قرارداد OpenAI فقط بایت صوت دریافت می‌کند، نه file_id بات اصلی. پس ارسال با file_id بات اصلی ممکن نیست مگر کد بات تغییر کند.
  • گزینه R1 (توصیه من): یک بات رله جدا (توکن مخصوص adapter). adapter با Bot API رسمی sendVoice با آپلود multipart می‌فرستد (طبق مستندات رسمی تا ۵۰ مگابایت). کرالر فقط می‌خواند و ترنسکرایب می‌کند. بات اصلی دست‌نخورده می‌ماند.
  • گزینه R2: file_id را از بات اصلی پاس بدهیم (نیاز به تغییر کوچک در بات؛ file_id مخصوص همان بات است).
  • گزینه R3 (گزارش B): کرالر خودش آپلود کند. نیاز به RPCهای آپلود و ساخت Media؛ سخت‌تر.
  • قالب صوت: برای نمایش «ویس» (و در نتیجه دکمه تبدیل به متن) از sendVoice استفاده کن و صوت را به OGG/Opus تبدیل کن. طبق مستندات، ارسال از طریق URL فقط برای audio/ogg با حجم ≤ ۱ مگابایت ویس شناخته می‌شود؛ بزرگ‌تر به‌صورت فایل می‌رود. اگر ویس به‌صورت «فایل» نمایش داده شد، دکمه ترنسکرایب ممکن است نباشد (در T6 آزمایش شود).
  • همبستگی: برای تشخیص پیام درست در سمت کرالر، caption را یک nonce تصادفی بگذار. اجرای ترتیبی (قفل) هم خطر اشتباه را کم می‌کند.
  • پیش‌نیاز: حساب کرالر باید یک‌بار بات رله را /start کند (🟡 فرض؛ در T6 تأیید شود).
  • ریسک: اینکه ترنسکریپت روی پیام دریافتی از بات هم کار کند 🟡 (در اپ، دکمه روی ویس دریافتی هست؛ برای بات تأیید نشده).

B5.3 لایه‌های موتور (به ترتیب اولویت)

اولویت موتور شرط انتخاب
۱ E1 RPC مستقیم (انتقال از aiobale/autobale/BaleClient، پروتوی خودساخته) GetTranscript پیدا و بازتولید شد
۲ E1+ ورود/تمدید توکن با Playwright، کار اصلی با پروتکل ورود مستقیم ناپایدار است
۳ E2 Playwright کامل (کلیک دکمه، خواندن DOM) RPC ممکن نشد ولی دکمه وب هست
۴ E3 امولاتور اندروید فقط با تصمیم صریح انسان

نکته استنباطی: اگر RPC در باندل وب بله وجود دارد، به احتمال زیاد دکمه هم در وب هست (🟡).


B6. وظایف به تفصیل

برای هر وظیفه: هدف ← مراحل ← خروجی ← معیار پذیرش ← در صورت شکست.

T0. بازبینی کد بات

  • هدف: فهمیدن اینکه stt.py چطور پرووایدرها را صدا می‌زند و با خطا چه می‌کند.
  • مراحل:
    1. bot/app/stt.py و bot/app/config.py را بخوان؛ پاسخ این‌ها را بنویس: قرارداد درخواست؟ timeout؟ با ۵۰۳/۴۰۱/۴۲۹ چه می‌کند؟ با {"text": ""} چه می‌کند؟ چند پرووایدر پشتیبانی می‌شود؟ محل PROCUREMENT_BOT_STT_PROVIDER_N_*؟
    2. ببین اجماع (LLM) چطور کاندیدها را می‌گیرد و آیا metadata پرووایدر را می‌بیند.
  • خروجی: docs/findings/00_bot_review.md.
  • پذیرش: برای هر سؤال پاسخ مستند با شماره خط.
  • اگر شکست: اگر ۵۰۳ باعث توقف کل خط‌لوله می‌شود، یک تغییر کوچک و جدا (PR جدا) پیشنهاد بده؛ بدون تأیید اعمال نکن.

T1. تأیید پکیج‌ها و جست‌وجوی GetTranscript

  • مراحل: (دستورها در D1)
    1. بررسی وجود/نسخه: aiobale-py, aiobale, autobale, baleclient, bale-sdk, bbpb.
    2. دانلود sdist bale-sdk (بدون نصب در محیط اصلی) و جست‌وجوی transcri|turing|speech|asr|stt در proto/ و docs/.
    3. اگر پیدا شد: نام سرویس، متد، فیلدها، نوع پاسخ را فقط برای شناخت ثبت کن (کپی نکن).
    4. مجوز هر پکیج را از METADATA/LICENSE بخوان.
  • خروجی: docs/findings/01_packages.md با جدول (نام، نسخه، تاریخ آخرین انتشار، مجوز، وضعیت).
  • پذیرش (G1 (بخش الف)): وجود یا عدم وجود GetTranscript به‌صورت قطعی ثبت شد.
  • اگر شکست (متد نبود): به T4 برو و منتظر کپچر بمان؛ به انسان گزارش بده که ادعای هر دو گزارش تأیید نشد.

T2. آزمون دقت (کیت ارزیابی)

  • هدف: تصمیم G0. این مهم‌ترین وظیفه است و به بله وابسته نیست.
  • مراحل:
    1. با H3، پوشه data/gold/ بساز: فایل صوتی، ref.txt (رونوشت انسانی)، items.json (کالا، تعداد، واحد).
    2. tools/eval/normalize.py بنویس: یکسان‌سازی «ي/ی» و «ك/ک»، ارقام فارسی/عربی/لاتین، نیم‌فاصله، حذف علائم، تبدیل اعداد حرفی ساده.
    3. tools/eval/metrics.py: CER (اصلی برای فارسی)، WER، و F1 استخراج اقلام (مقایسه جدول استخراج‌شده با items.json).
    4. tools/eval/run_eval.py: ورودی = پوشه خروجی هر کاندید candidates/<name>/<id>.txt؛ خروجی = جدول مقایسه + اجماع.
    5. مسیر دستی (فوری): از انسان بخواه ۳۰ ویس را در اپ بله «تبدیل به متن» کند و متن‌ها را در candidates/bale/ بگذارد. مسیر خودکار: پس از T4 و تأیید دکمه وب، خودت انجام بده.
    6. کاندیدهای فعلی بات را روی همان ویس‌ها اجرا کن.
  • خروجی: docs/findings/02_accuracy.md با جدول و تصمیم G0.
  • پذیرش (G0): طبق A8. گزارش باید آمار موارد سخت (نام برند، عدد، واحد، لهجه) را هم داشته باشد.
  • اگر شکست: گزارش بده و T4..T8, T11 را اجرا نکن.

T3. پروب Bot API

  • هدف: مطمئن شدن که فیلد مخفی در آپدیت صوتی نیست.
  • مراحل: با یک بات تستی، یک ویس بفرست و JSON خام کامل getUpdates/Webhook را ذخیره کن (بدون توکن). همه کلیدها را فهرست کن. بررسی کن voice و message فیلدی مثل transcript/text/caption دارد؟
  • پذیرش: فهرست کلیدها ثبت شد. (انتظار: فقط file_id, file_unique_id، چون مستندات رسمی همین را می‌گویند.)
  • اگر فیلد پیدا شد: پروژه کرالر لازم نیست؛ گزارش فوری.

T4. بررسی وب بله و کپچر ترافیک

  • پیش‌نیاز: H1 (تأیید ToS)، H2، H4. بدون این‌ها شروع نکن.
  • مراحل:
    1. با Playwright (launch_persistent_context، پروفایل روی ولوم) web.bale.ai را باز کن؛ ورود با شماره H2؛ وقتی کد پیامکی خواست، متوقف شو و از انسان بخواه (B9.2).
    2. در یک گفتگوی آزمایشی (گفتگو با بات رله یا Saved Messages)، یک ویس بفرست/دریافت کن و ببین دکمه «تبدیل به متن» هست یا نه. عکس صفحه بگیر.
    3. اگر دکمه هست: با capture_ws.py (D2) تمام فریم‌های WebSocket و درخواست‌های XHR/fetch را هنگام کلیک ذخیره کن. توکن‌ها را redact کن.
    4. زمان واقعی از کلیک تا نمایش متن را اندازه بگیر.
    5. تفاوت ویس ارسالی و دریافتی را ثبت کن (دکمه روی هر دو هست؟).
  • خروجی: docs/findings/04_web.md + captures/*.bin (redact شده).
  • پذیرش (G1): «دکمه هست/نیست» با عکس و کپچر مستند شد.
  • اگر دکمه نیست و RPC در T1 هم نبود: E1 و E2 هر دو ممکن نیستند ⇒ گزارش به انسان؛ فقط E3 باقی می‌ماند (نیاز به تصمیم صریح).

T5. رمزگشایی و ساخت پروتو (clean-room)

  • مراحل:
    1. فریم‌ها را با decode_frames.py بخوان: تشخیص قالب فریم (در POST gRPC-Web پیشوند ۵ بایتی: ۱ بایت فلگ + ۴ بایت طول؛ در WebSocket بله قالب را از داده بخوان، فرض نکن).
    2. با protoc --decode_raw یا bbpb/protoscope ساختار را ببین؛ فیلدهای voice(Media)، outPeer، messageId و پاسخ mustWait/downloadSource را بازسازی کن.
    3. با کمک چند کپچر مختلف (ویس کوتاه/بلند/دریافتی) نوع فیلدها را تأیید کن.
    4. GetTranscript.proto خودت را بنویس (نام فیلدها = توصیفی، نه کپی).
    5. مشخص کن متن پاسخ مستقیم است یا لینک دانلود (DownloadSource) و راه خواندنش چیست.
  • خروجی: proto/own/gettranscript.proto + docs/findings/05_schema.md.
  • پذیرش (G2): با ارسال همان درخواست بازسازی‌شده از پایتون (توکن معتبر)، پاسخ مشابه کپچر گرفته شد.
  • اگر شکست: اگر ترافیک امضا/ناشناس‌سازی مرورگر لازم دارد ⇒ سوییچ به E2.

T6. پروتوتایپ E1 با بات رله

  • پیش‌نیاز: T5، H5.
  • مراحل:
    1. یکی از کتابخانه‌های MIT را (نسخه pin‌شده) برای انتقال و نشست انتخاب کن؛ اگر کار نکرد، فورک بعدی را امتحان کن (autobale ← BaleClient ← aiobale).
    2. حساب کرالر /start بزند؛ هویت (user_id) را ثبت کن.
    3. adapter: صوت → OGG/Opus (ffmpeg) → sendVoice(caption=nonce) به کرالر.
    4. کرالر: گوش‌دادن به پیام جدید از بات رله با همان nonce ⇒ GetTranscript ⇒ (حلقه mustWait با فاصله ~۱٫۵ ثانیه و سقف زمانی) ⇒ دریافت متن ⇒ حذف پیام.
    5. ۱۰ ویس واقعی را اجرا کن؛ تأخیر هر مرحله را ثبت کن.
  • پذیرش (G3): ۱ ویس ⇒ ۱ متن فارسی معتبر؛ ۱۰ از ۱۰ یا دلیل شکست‌ها مستند.
  • اگر شکست: (الف) اگر ویس بات به‌صورت فایل نمایش داده شد ⇒ قالب صوت را عوض کن؛ (ب) اگر RPC روی پیام دریافتی از بات کار نکرد ⇒ R3 (آپلود از کرالر)؛ (ج) در غیر این صورت E2.

T7. موتور فالبک Playwright (اختیاری)

  • مراحل: با همان پروفایل ورود، ویس را از طریق همان جریان رله بفرست، روی ویس دریافتی دکمه را کلیک کن، با MutationObserver یا expect(...).to_be_visible منتظر متن بمان، متن را بخوان، پیام را حذف کن. سلکتورها را در یک فایل جدا و متمرکز نگه دار. رم کانتینر را اندازه بگیر.
  • پذیرش: ۱۰ از ۱۰ ویس؛ رم ≤ ۸۰۰ مگابایت؛ تأخیر مستند.
  • نکته: سلکتورها با هر آپدیت UI می‌شکنند؛ تست طلایی شبانه بنویس.

T8. adapter سخت‌شده + تست‌ها

  • مراحل: کد مرجع B8 را پیاده کن؛ stub engine برای تست بدون بله؛ تست‌ها:
    1. بدون/اشتباه بودن کلید ⇒ ۴۰۱.
    2. فایل خالی/بزرگ ⇒ ۴۰۰.
    3. رسیدن به سقف روزانه ⇒ ۴۲۹؛ و پایداری شمارنده بعد از ری‌استارت (و ریست روز بعد).
    4. خطای موتور ⇒ ۵۰۳؛ متن خالی ⇒ ۵۰۳.
    5. تایم‌اوت ⇒ ۵۰۳.
    6. اجرای هم‌زمان ۵ درخواست ⇒ ترتیبی (قفل).
    7. /healthz.
  • پذیرش: همه تست‌ها سبز؛ بدون مقدار پیش‌فرض برای کلید.

T9. پرووایدرهای شخص ثالث و مدل خودمیزبان (موازی، مستقل از بله)

  • پیش‌نیاز: H3، H7، H8.
  • مراحل:
    1. برای هر کاندید یک adapter OpenAI-compatible یا پرووایدر مستقیم: Avanegar/Partai، Aipa، فارس‌آوا (عامراندیش)، IOType، Google Chirp 2، (در صورت دسترسی) Azure/OpenAI/Groq Whisper.
    2. Faster-Whisper: سرور OpenAI-compatible داخلی؛ مدل large-v3 (و در صورت تمایل یک فاینتیون فارسی مثل vhdm/whisper-v3-turbo-persian-v1.1)؛ با initial_prompt/hotwords از واژگان تدارکات و VAD. حتماً ببین هالوسینیشن در سکوت/نویز رخ نمی‌دهد.
    3. همه را با کیت T2 روی مجموعه طلایی بسنج؛ هزینه به‌ازای ویس را حساب کن.
    4. مدل‌های ضعیف‌تر (Vosk، NeMo FA، SLPL wav2vec2) فقط اگر ارزش افزوده اجماع دارند نگه دار.
  • خروجی: docs/findings/09_providers.md با جدول دقت/تأخیر/هزینه و پیشنهاد ترکیب.
  • پذیرش: حداقل ۲ کاندید غیر بله با دقت مستند آماده اتصال.

T10. تقویت اجماع

  • مراحل:
    1. نرمال‌سازی (همان normalize.py) پیش از اجماع.
    2. کاتالوگ: فهرست کالا/برند/واحد دیتابیس تدارکات را (خلاصه‌شده) به prompt اجماع‌گیر بده؛ خروجی ساختاریافته (نام، برند، تعداد، واحد).
    3. قاعده اختلاف: اگر کاندیدها روی عدد یا واحد اختلاف دارند، خروجی را «نیازمند تأیید کاربر» علامت بزن.
    4. اندازه‌گیری روی مجموعه طلایی: اجماع قبل/بعد از هر تغییر.
  • پذیرش (G5): بهبود F1 اقلام مستند؛ بدون افزایش خطای فاحش.

T11. اتصال به بات، E2E، rollback

  • مراحل:
    1. docker-compose.stt.yml را بالا بیاور.
    2. .env بات: متغیرهای PROCUREMENT_BOT_STT_PROVIDER_3_* (B8.7). کلید باید با BALE_STT_INTERNAL_KEY یکی باشد و نه مقدار نمونه.
    3. E2E: یک ویس واقعی به بات اصلی بفرست؛ در لاگ حضور کاندید bale در اجماع را تأیید کن.
    4. تست خطا: adapter را خاموش کن ⇒ بات بی‌وقفه کار کند. adapter را وادار به ۵۰۳ کن ⇒ همین.
    5. تست rollback: خالی‌کردن env ⇒ بات به حالت قبل برگردد.
  • پذیرش (G4 مقدماتی): هر ۴ سناریو موفق.
  • قبل از مرج: تأیید انسان (H11).

T12. سواک و مانیتورینگ

  • مراحل: ۷ روز با حجم کم؛ ثبت روزانه: تعداد، موفقیت، p50/p95، انواع خطا، وضعیت نشست، تفاوت با سایر کاندیدها؛ هشدار اگر نرخ خطا در ۳۰ دقیقه > ۲۰٪ یا نشست باطل شد (به انسان اعلان بده).
  • پذیرش (G7): یک هفته بدون ابطال نشست و با G4 برقرار.

T13. جمع‌آوری داده و فاین‌تیون (اختیاری)

  • مراحل: ذخیره (با رضایت) ویس + متن نهایی تأییدشده؛ ساخت دادگان؛ LoRA روی Whisper؛ ارزیابی روی مجموعه طلایی جدا؛ جایگزینی تدریجی وابستگی به بله.
  • پذیرش: مدل داخلی روی مجموعه طلایی از Faster-Whisper خام بهتر است.

B7. قواعد تصمیم برای ایجنت (جدول if-then)

شرایط اقدام
T3 فیلد ترنسکریپشن پیدا کرد کرالر لازم نیست؛ فوراً گزارش بده
G0 رد شد T4..T8, T11 را اجرا نکن؛ T9، T10، T13 را ادامه بده
G0 «کمی بهتر» (۵ تا ۱۵٪) فقط E1 سبک؛ Playwright و امولاتور را رها کن
G0 «چشمگیر» (> ۱۵٪) E1 + فالبک E2 + پیگیری مسیر رسمی
T1: GetTranscript پیدا شد T5 را با تأکید بر تأیید با کپچر خودت پیش ببر
T1: پیدا نشد و T4: دکمه هست E2؛ در موازی با کپچر دنبال RPC بگرد
T1 و T4 هر دو منفی به انسان گزارش بده؛ E3 فقط با تصمیم صریح
پاسخ GetTranscript لینک دانلود است حلقه دریافت و خواندن لینک را پیاده کن؛ زمان‌سنجی کن
ویس بات به‌صورت فایل نمایش داده شد قالب OGG/Opus و sendVoice را بازبینی کن؛ در نهایت R3
خطای ۴۲۹/چالش/ابطال نشست همه درخواست‌ها را متوقف کن، به انسان خبر بده، ترافیک را به بقیه بسپار
بیش از ۲۰٪ خطا روی کاندید بله از اجماع خودکار حذفش کن (۵۰۳) و بررسی کن
هالوسینیشن Whisper VAD و initial_prompt را بررسی؛ در اجماع وزن کمتر
کاندیدها روی عدد/واحد اختلاف دارند «نیازمند تأیید کاربر»

B8. کد و پیکربندی مرجع

توجه: این‌ها نقطه شروع‌اند، نه کد نهایی. بخش «موتور RPC» عمداً شبه‌کد است؛ نام متدها را از کتابخانه واقعی بخوان.

B8.1 adapter/counters.py (شمارنده روزانه پایدار)

import os
import sqlite3
from contextlib import closing
from datetime import date

DB_PATH = os.getenv("BALE_STT_DB", "/app/state/usage.db")


def _conn() -> sqlite3.Connection:
    os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
    con = sqlite3.connect(DB_PATH, timeout=5)
    con.execute("CREATE TABLE IF NOT EXISTS usage (day TEXT PRIMARY KEY, n INTEGER NOT NULL)")
    return con


def used_today() -> int:
    with closing(_conn()) as con:
        row = con.execute("SELECT n FROM usage WHERE day = ?", (date.today().isoformat(),)).fetchone()
    return row[0] if row else 0


def bump() -> None:
    with closing(_conn()) as con, con:
        con.execute(
            "INSERT INTO usage(day, n) VALUES (?, 1) "
            "ON CONFLICT(day) DO UPDATE SET n = n + 1",
            (date.today().isoformat(),),
        )

B8.2 adapter/engines/base.py و __init__.py

# engines/base.py
from typing import Protocol


class Engine(Protocol):
    name: str

    async def transcribe(self, audio: bytes, filename: str) -> str:
        """متن فارسی را برگرداند؛ در هر مشکل Exception بدهد."""
# engines/__init__.py
import os


def load_engine():
    name = os.environ["BALE_STT_ENGINE"]          # الزامی؛ بدون پیش‌فرض
    if name == "stub":
        if os.getenv("BALE_STT_ALLOW_STUB") != "1":
            raise RuntimeError("stub engine فقط برای تست است")
        from .stub import StubEngine
        return StubEngine()
    if name == "rpc":
        from .rpc import RpcEngine
        return RpcEngine()
    if name == "playwright":
        from .playwright_engine import PlaywrightEngine
        return PlaywrightEngine()
    raise RuntimeError(f"unknown engine: {name}")

B8.3 adapter/main.py (قرارداد OpenAI، سخت‌شده)

import asyncio
import logging
import os
import random
import secrets

from fastapi import FastAPI, File, Form, Header, HTTPException, UploadFile

from counters import bump, used_today
from engines import load_engine

log = logging.getLogger("bale-stt")
logging.basicConfig(level=logging.INFO)

KEY = os.environ["BALE_STT_INTERNAL_KEY"]                 # الزامی، بدون پیش‌فرض
DAILY_CAP = int(os.getenv("BALE_STT_DAILY_CAP", "150"))
TIMEOUT = float(os.getenv("BALE_STT_TIMEOUT", "25"))      # کمتر از timeout بات (مثلاً 30)
MIN_GAP = float(os.getenv("BALE_STT_MIN_GAP", "1.0"))
MAX_GAP = float(os.getenv("BALE_STT_MAX_GAP", "2.5"))
MAX_BYTES = int(os.getenv("BALE_STT_MAX_BYTES", str(10 * 1024 * 1024)))

app = FastAPI(title="Bale STT adapter")
engine = load_engine()
lock = asyncio.Lock()          # اجرای ترتیبی روی حساب بله


@app.get("/healthz")
async def healthz():
    return {"status": "ok", "engine": engine.name, "used_today": used_today(), "cap": DAILY_CAP}


@app.post("/v1/audio/transcriptions")
async def transcribe(
    file: UploadFile = File(...),
    model: str = Form("bale-stt"),
    language: str = Form("fa"),
    authorization: str = Header(""),
):
    if not secrets.compare_digest(authorization.encode(), f"Bearer {KEY}".encode()):
        raise HTTPException(status_code=401, detail="Unauthorized")

    audio = await file.read()
    if not audio or len(audio) > MAX_BYTES:
        raise HTTPException(status_code=400, detail="Bad audio size")

    if used_today() >= DAILY_CAP:
        raise HTTPException(status_code=429, detail="Daily cap reached")

    async with lock:
        try:
            await asyncio.sleep(random.uniform(MIN_GAP, MAX_GAP))
            text = await asyncio.wait_for(
                engine.transcribe(audio, file.filename or "voice.mp3"), timeout=TIMEOUT
            )
        except Exception:
            log.exception("engine failed")
            raise HTTPException(status_code=503, detail="Bale STT unavailable")

    text = (text or "").strip()
    if not text:
        raise HTTPException(status_code=503, detail="Empty transcript")

    bump()
    log.info("ok chars=%d", len(text))        # متن کامل را لاگ نکن
    return {"text": text}

B8.4 adapter/engines/rpc.py (شبه‌کد، منطق مورد نیاز)

class RpcEngine:
    name = "rpc"

    async def transcribe(self, audio: bytes, filename: str) -> str:
        nonce = new_nonce()
        ogg = await to_ogg_opus(audio)                              # ffmpeg
        await relay_bot.send_voice(chat_id=CRAWLER_USER_ID, voice=ogg, caption=nonce)
        msg = await crawler.wait_message(from_bot=RELAY_BOT_ID, caption=nonce, timeout=10)
        resp = await crawler.get_transcript(                         # نام و فیلدها: از T5
            voice=msg.media, out_peer=msg.peer, message_id=msg.id
        )
        deadline = loop_time() + 15
        while resp.must_wait and loop_time() < deadline:
            await asyncio.sleep(1.5)
            resp = await crawler.get_transcript(voice=msg.media, out_peer=msg.peer, message_id=msg.id)
        if resp.must_wait:
            raise TimeoutError("transcript not ready")
        text = await read_download_source(resp.download_source)      # اگر پاسخ لینک است
        await crawler.delete_messages(msg.peer, [msg.id])
        return text

B8.5 adapter/Dockerfile

FROM python:3.11-slim
WORKDIR /app
ENV TZ=Asia/Tehran PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

برای موتور Playwright از ایمیج رسمی Playwright پایتون با نسخه pin‌شده استفاده کن و ffmpeg را اضافه کن.

B8.6 docker-compose.stt.yml

services:
  bale-stt:
    build:
      context: ./adapter
    container_name: bale-stt-adapter
    restart: unless-stopped
    environment:
      TZ: Asia/Tehran
      BALE_STT_ENGINE: ${BALE_STT_ENGINE}
      BALE_STT_INTERNAL_KEY: ${BALE_STT_INTERNAL_KEY}
      BALE_STT_DAILY_CAP: ${BALE_STT_DAILY_CAP:-150}
      BALE_RELAY_BOT_TOKEN: ${BALE_RELAY_BOT_TOKEN}
      BALE_CRAWLER_USER_ID: ${BALE_CRAWLER_USER_ID}
    volumes:
      - bale_session:/app/session
      - bale_state:/app/state
    networks: [bot_network]
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://localhost:8080/healthz')"]
      interval: 30s
      timeout: 5s
      retries: 3

volumes:
  bale_session:
  bale_state:

networks:
  bot_network:
    external: true      # فرض: همان شبکه بات؛ در T0 تأیید شود

B8.7 .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=<همان BALE_STT_INTERNAL_KEY؛ مقدار واقعی را فقط انسان/سکرت‌منیجر بگذارد>
PROCUREMENT_BOT_STT_PROVIDER_3_MODEL=bale-stt
PROCUREMENT_BOT_STT_PROVIDER_3_LANGUAGE=fa
PROCUREMENT_BOT_STT_PROVIDER_3_TIMEOUT=30

شماره پرووایدر (3) را با وضعیت واقعی بات (T0) تطبیق بده. پرووایدر وقتی فعال است که BASE_URL و API_KEY پر باشند (طبق گزارش‌ها؛ در T0 تأیید شود).


B9. قالب‌های گزارش‌دهی

B9.1 گزارش دروازه (برای انسان)

## گزارش دروازه Gx — <تاریخ>
**نتیجه:** عبور / رد / نیازمند تصمیم شما
**آنچه اجرا شد:** (وظایف، دستورها، فایل‌های خروجی)
**یافته‌ها:** (جدول؛ هر ردیف با ✅/🟡/❌ و مدرک)
**اعداد:** (CER/WER/F1/تأخیر/موفقیت/هزینه)
**انحراف از سند:** (چه چیزی با فرض‌های سند نخواند)
**ریسک‌های تازه:**
**پیشنهاد گام بعد:**
**چیزی که از شما لازم دارم:** (H#)

B9.2 درخواست از انسان (کوتاه، فوری، قابل‌اقدام)

⏸️ **نیاز به اقدام شما — H#: <عنوان>**
**چرا:** <یک جمله>
**دقیقاً چه کنید:** <۱–۳ گام>
**چه چیزی را پس می‌دهید:** <فقط همان مقدار لازم، مثلاً کد ۶ رقمی>
**تا آن موقع من:** <کاری که موازی انجام می‌دهم>

B10. عیب‌یابی

نشانه علت محتمل اقدام
۵۰۳ پیاپی از adapter نشست باطل / تغییر پروتکل / دکمه وب حذف شد لاگ را بخوان؛ /healthz؛ اگر نشست باطل است H4 را درخواست کن؛ ترافیک به بقیه
ویس بات به‌صورت فایل نمایش داده می‌شود قالب یا متد ارسال sendVoice با OGG/Opus؛ حجم ≤ ۱MB برای ویس؛ در نهایت R3
ترنسکریپت برای پیام بات کار نمی‌کند محدودیت سمت سرور R3 یا E2
mustWait همیشه true استنتاج کند یا RPC وضعیت دیگری دارد زمان‌سنجی؛ افزایش سقف تا ۲۰ ثانیه؛ مقایسه با کپچر وب
پاسخ خالی ویس بی‌کلام/نویز VAD قبل از ارسال؛ ۵۰۳ برای متن خالی (اینجا درست است)
compare_digest خطا می‌دهد رشته غیر ASCII همه را .encode() کن
شمارنده روزانه ریست نمی‌شود منطقه زمانی کانتینر TZ=Asia/Tehran
Playwright روی سرور بالا نمی‌آید وابستگی‌های مرورگر از ایمیج رسمی Playwright همان نسخه
بن/محدودیت نرخ زیاد توقف کامل؛ کاهش سقف؛ فاصله بیشتر؛ گزارش به انسان
اجماع به‌خاطر بله بدتر شد بله روی نوع ویس شما ضعیف است وزن کمتر یا حذف کاندید (G5)

B11. تعریف «انجام شد» (DoD)

  • T0 تا T3 مستند شده و ادعاهای 🟡 این سند ✅ یا ❌ شده‌اند.
  • G0 با اعداد واقعی گزارش شد (حتی اگر پروژه متوقف شود).
  • اگر ادامه یافت: adapter با تست‌های T8 سبز است و فقط با .env به بات وصل می‌شود.
  • kill switch (خالی‌کردن env) و rollback تست شده.
  • حداقل ۲ کاندید غیر بله فعال و ارزیابی شده‌اند.
  • اجماع با نرمال‌سازی و کاتالوگ بهبود یافته و اندازه‌گیری شده است.
  • سواک ۷ روزه گزارش شد؛ هشدارها فعال‌اند.
  • مستندات: docs/findings/*، دستور اجرا، runbook ورود مجدد (OTP)، مالک نگهداری (H10).
  • هیچ سکرت یا کپچر redact‌نشده‌ای در مخزن نیست.

بخش C: دانش‌پایه (همه راه‌ها، شواهد و نظرات من)

این بخش مرجع است؛ برای اجرا به بخش B برگردید. هر جا برچسب ✅/🟡/❌ هست، همان تعریف سر سند را دارد.

C1. راستی‌آزمایی ادعاهای گزارش A

ادعا وضعیت یافته من
Bot API رسمی STT ندارد ✅ مستندات رسمی docs.bale.ai: شیء Voice فقط file_id و file_unique_id دارد و هیچ متدی برای ترنسکریپت نیست. اصلاح: گزارش می‌گوید «حجم و مدت‌زمان» هم هست؛ در مستندات رسمی نیست (SDKهای شخص ثالث فیلد duration را اضافه کرده‌اند).
bale-sdk از mtp1376، نسخه 0.1.1، ۶۰۷ RPC در ۵۲ سرویس، پوشه proto/ ✅ صفحه PyPI (27 Jun 2026) دقیقاً همین را می‌گوید.
bale-sdk مجوز اختصاصی «All Rights Reserved» دارد ✅ تأیید شد. خود README هم می‌گوید استفاده فقط با حساب‌ها و داده‌هایی که مجاز هستید و مطابق ToS بله باشد.
سرویس bale.turing.v1.AI و متد GetTranscript با فیلدهای ۱ تا ۳ 🟡 نتوانستم از منبع عمومی تأیید کنم. فایل‌های docs/API_COVERAGE.md و docs/API_FA.md در صفحه PyPI لینک شده‌اند ولی برای من باز نشد. این مهم‌ترین ادعای هر دو گزارش است و باید در ۳۰ دقیقه اول خودتان تأیید شود (وظیفه T1).
aiobale-py توسط امین مدنی، نسخه 0.3.x، متکی بر blackboxprotobuf 🟡 aiobale-py را پیدا نکردم. آنچه پیدا کردم: aiobale (Enalite، MIT، آخرین نسخه 0.1.5 در Aug 2025). توضیح خودش می‌گوید «از صفر و بدون فایل proto ساخته شده».
aiobale اولیه رها شده ✅ آخرین انتشار Aug 2025؛ فورک‌ها (autobale، BaleClient) می‌گویند نگهداری نمی‌شود و باگ دارد.
autobale توسط علی عمادی، نسخه 1.0.0، MIT ✅ تأیید شد (مخزن AliEmadii/autobale).
BaleClient — در گزارش نیست، ولی وجود دارد: فورک دیگری از aiobale (Foad-KhAb/baleclient، Python 3.11+).
bale-mcp-server روی aiobale-py 🟡/❌ پروژه‌ای به نام bale-mcp پیدا کردم که با توکن بات رسمی و tapi.bale.ai کار می‌کند، نه کلاینت کاربری. شاید پروژه دیگری باشد.
bale-chat-exporter با نشست کلاینت 🟡 اسکرپر Playwright + BeautifulSoup برای Bale Web پیدا کردم (خروجی اکسل). هم‌پروژه‌بودن با آنچه گزارش می‌گوید تأییدنشده است، ولی الگوی Playwright روی وب بله اثبات‌شده است.
Bale-Self-Bot در GitLab چابکان 🟡 آن را پیدا نکردم. ولی baleself (PyPI، zellias/bale-self-bot) یک سلف‌بات مبتنی بر Selenium است و الگو را تأیید می‌کند.
MajidAPI یک پراکسی روی موتورهای عمومی است 🟡 endpoint را در منابع قابل جست‌وجو پیدا نکردم. «پراکسی روی موتور عمومی» استنباط گزارش است، نه چیزی که ثابت شده باشد. هرچه باشد، SLA ندارد.
فارس‌آوا (عامراندیش): ۱۰ هزار ساعت داده، نصب On-Premise، ساعتی ۴۵–۶۰ هزار تومان 🟡 قیمت و جزئیات را مستقل تأیید نکردم؛ قیمت‌ها زمان‌دارند. قبل از تصمیم، استعلام رسمی بگیرید.
آی‌او تایپ دقت «بالای ۹۸٪» 🟡 ادعای بازاریابی است. روی ویس تدارکات خودتان اندازه بگیرید.
hezarai/whisper-small-fa روی CPU زیر ۲ ثانیه 🟡 محتمل برای ویس کوتاه، ولی مدل small روی فارسی محاوره‌ای ضعیف است (C5).
nezamisafa/whisper-v3-turbo-persian-v1.0 با WER ‏۲۹.۹٪ 🟡 این مدل را نتوانستم باز کنم. مدل مشابه vhdm/whisper-v3-turbo-persian-v1.1 ‏WER ‏14.07٪ می‌دهد ولی روی دادگان خودش؛ اعداد WER بین دادگان‌های مختلف قابل مقایسه نیستند.
Faster-Whisper تا ۴ برابر سریع‌تر ✅ (تا حد دانسته‌های من) ادعای رسمی خود پروژه است. در مقاله PSRB هم Faster-Whisper هالوسینیشن کمتری از Whisper خام داشت.
«مدل‌های متن‌باز پایدارترین راهکار فنی‌اند» ❌ با داده PSRB سازگار نیست: ASR تجاری فارسی و Chirp 2 به‌طور محسوس دقیق‌ترند (C5.2). متن‌باز «مستقل‌ترین» است، نه لزوماً «دقیق‌ترین».

C2. آیا دیگران هم همین مشکل را داشته‌اند؟ و چطور حل کرده‌اند؟

C2.1 نتیجه کلی

پروژه عمومیِ مشخصی که خروجی STT خود بله را برای یک بات استخراج کند پیدا نکردم (گزارش A هم همین را می‌گوید). آنچه وجود دارد الگوهای مجاور است:

الگو نمونه‌های تأییدشده درسی که برای ما دارد
A. بات رسمی + ASR مستقل بات‌های بله روی Webhook (مثلاً hamedcode/bale-ai-bot روی Cloudflare Workers)، بات‌های مشتری‌مداری با LLM رایج‌ترین راه. بله فقط کانال پیام است؛ صوت دانلود و به موتور دیگر داده می‌شود.
B. کلاینت کاربری مهندسی‌معکوس‌شده aiobale (MIT)، فورک‌های autobale و BaleClient، bale-sdk (اختصاصی، ۶۰۷ RPC) نشان می‌دهد نگهداری نشست کاربری و تماس با RPCهای داخلی شدنی است، ولی پروژه‌ها تازه‌اند و بعضی رها می‌شوند.
C. اتوماسیون مرورگر baleself (Selenium)، اسکرپر چت Bale Web با Playwright روی وب بله جاافتاده است، ولی معمولاً برای کارهای ساده مثل پاسخ خودکار و بک‌آپ.
D. سوءاستفاده خلاقانه از زیرساخت BaleVPN (تونل روی تماس صوتی بله) نشان می‌دهد جامعه تا کجا از لایه‌های داخلی استفاده می‌کند؛ برای STT به کار ما نمی‌آید.
E. ابزارهای مدیریتی روی Bot API bale-mcp، SDKهای PHP/Node/Python/Swift/C# فقط Bot API؛ بدون هیچ راهی برای رسیدن به ترنسکریپت.

C2.2 برداشت از همین شواهد

  1. راه مشترک جامعه این است که STT را بیرون از بله انجام می‌دهند. هیچ‌کس STT بله را «کرال» نکرده یا اگر کرده منتشر نشده است. پس در هر حال ریسک بالاتر و شواهد کمتری برای مسیر شماست.
  2. زیرساخت لازم برای کرالر وجود دارد (انتقال WebSocket، OTP، نشست، ارسال و دانلود فایل)؛ پس این پروژه «ساخت از صفر» نیست.
  3. پایداری کتابخانه‌ها ضعیف است (aiobale رها شد، bale-sdk نسخه 0.1.1 است). هر معماری باید فرض کند اجزای متن‌باز ممکن است از کار بیفتند.
  4. تنها چیزی که کل شرط‌بندی را توجیه می‌کند، دقت بله روی ویس‌های شماست. بدون آزمون دستی (وظیفه T2) نباید ساخت شروع شود.

C3. کاتالوگ کامل راهکارها

برای هر راهکار: چه کار می‌کند، مزیت، عیب، تلاش (تخمین من)، وضعیت تأیید.

گروه ۱: استفاده از STT خود بله

# راهکار توضیح مزایا معایب تلاش وضعیت
S1 مسیر رسمی/تجاری با بله ایمیل یا تماس با تیم توسعه‌دهندگان/بیزینس بله برای API رسمی STT یا قرارداد پایدار، بدون ریسک بن احتمال موافقت کم؛ زمان‌بر ۱ ایمیل هیچ مستند عمومی‌ای برای وجود چنین API نیست
S2 RPC مستقیم با bale-sdk استفاده از پروتوها و کلاینت آماده، فراخوانی GetTranscript سریع‌ترین مسیر فنی؛ پروتو آماده مجوز اختصاصی؛ نسخه 0.1.1؛ ادعای RPC تأییدنشده ۱–۲ روز 🟡
S3 RPC مستقیم با aiobale / autobale / BaleClient (MIT) + پروتوی خودساخته انتقال و نشست و آپلود از کتابخانه MIT؛ تعریف GetTranscript را از کپچر ترافیک خودتان در web.bale.ai بسازید بدون مشکل مجوز؛ قابل‌نگهداری کشف RPC و تعریف پروتو کار می‌برد؛ کتابخانه‌ها کم‌نگهداری‌اند ۳–۵ روز ✅ ابزارها؛ 🟡 RPC
S4 Playwright روی web.bale.ai کرومیوم headless با پروفایل پایدار؛ ارسال ویس، کلیک دکمه، خواندن DOM مقاوم در برابر تغییر پروتکل؛ الگوی اثبات‌شده ۳۵۰–۵۰۰MB رم؛ شکننده در برابر تغییر UI؛ وابسته به وجود دکمه در وب ۱–۲ روز ✅ الگو؛ 🟡 دکمه وب
S5 ترکیب «مرورگر برای ورود، پروتکل برای کار» Playwright فقط ورود/تمدید توکن (JWT) را انجام می‌دهد؛ کار اصلی را کلاینت WebSocket سبک می‌کند از سبک‌بودن S3 و پایداری ورود S4 بهره می‌برد پیچیدگی دو لایه ۴–۶ روز پیشنهاد من
S6 شبیه‌ساز اندروید + uiautomator اپ بله در شبیه‌ساز، خواندن متن از رابط مستقل از وب رم >2GB؛ شکننده؛ سخت‌ترین نگهداری ۵+ روز آخرین چاره
S7 چت هوش مصنوعی بله (Oracle) ارسال ویس به بات هوش مصنوعی بله و گرفتن پاسخ بدون کشف RPC خروجی «متن + تفسیر»؛ تأخیر ۵–۱۵ ثانیه؛ اینکه ویس را پیاده می‌کند هم تأییدنشده ۱ روز 🟡

گروه ۲: سرویس‌های شخص ثالث

# راهکار توضیح مزایا معایب تلاش وضعیت
T1 پراکسی‌های غیررسمی (MajidAPI و مشابه) فراخوانی REST با لینک ویس چند ساعت بدون SLA؛ موتور نامشخص؛ ورودی URL (ویس باید لینک عمومی شود) ساعتی 🟡
T2 ASR تجاری فارسی Avanegar/Partai (API: api.ivira.ai/partai/speechRecognition)، Aipa، عامراندیش (فارس‌آوا)، IOType دقت بالا روی فارسی؛ پشتیبانی؛ گاهی On-Premise هزینه ریالی؛ قرارداد ۱ روز (adapter) ✅ Avanegar/Aipa در بنچمارک؛ 🟡 قیمت‌ها
T3 ASR ابری بین‌المللی Google Chirp 2، Azure Speech، OpenAI Whisper API، Groq Whisper Chirp 2 تقریباً هم‌سطح بهترین تجاری فارسی هزینه ارزی؛ محدودیت دسترسی؛ بسته‌به شرایط شبکه ۱ روز ✅ Chirp/Azure در بنچمارک

گروه ۳: مدل‌های متن‌باز خودمیزبان

# راهکار توضیح مزایا معایب تلاش وضعیت
O1 Faster-Whisper large-v3 (+ initial_prompt/hotwords واژگان تدارکات) سرور OpenAI-compatible داخلی مستقل؛ قابل‌شخصی‌سازی با واژگان؛ بهترین متن‌باز در بنچمارک WER ≈ 34٪ روی فارسی واقعی؛ نیاز به GPU برای سرعت ۱ روز ✅
O2 Whisper large-v3-turbo فاین‌تیون فارسی (مثل vhdm/whisper-v3-turbo-persian-v1.1) مدل فاین‌تیون‌شده روی دادگان فارسی سریع‌تر؛ احتمالاً دقیق‌تر روی گفتار تمیز ارقام خودگزارشی و روی دادگان متفاوت؛ باید روی داده شما سنجید ۱ روز 🟡
O3 NeMo FastConformer فارسی (NVIDIA) stt_fa_fastconformer_hybrid_large مدل مستقل از Whisper؛ دیکودر CTC در بنچمارک WER ≈ 45٪ (ضعیف‌تر از Whisper) ۲ روز ✅
O4 Vosk فارسی (vosk-model-fa-0.5) Kaldi/DNN-HMM سبک، CPU بسیار سبک؛ آفلاین WER ≈ 45٪ ساعتی ✅
O5 SLPL wav2vec2 فارسی (دانشگاه شریف) XLS-R فاین‌تیون‌شده مناسب برای تحقیق و ensemble WER ≈ 47٪ ۲ روز ✅
O6 Seamless M4T v2 مدل چندزبانه — احتمال بازنویسی/هالوسینیشن (Interpretation Error در مقاله PSRB) ۲ روز ✅ مشکل

گروه ۴: لایه‌های بالاتر (نه جایگزین، بلکه تقویت‌کننده)

# راهکار توضیح
L1 LLM اجماع‌گیر با کاتالوگ کاندیدها + فهرست کالا/برند/واحد دیتابیس تدارکات را به LLM بدهید تا خروجی ساختاریافته و تصحیح‌شده بسازد. (مقاله PSRB هم می‌گوید LLMها خطاهای جزئی ASR را خوب اصلاح می‌کنند.)
L2 نرمال‌سازی فارسی یکسان‌سازی «ی/ک»، ارقام، نیم‌فاصله (ZWNJ) و اعداد حرفی. PSRB نشان می‌دهد مرزبندی کلمه و نیم‌فاصله WER را بالا می‌برد.
L3 فاین‌تیون روی داده خودتان ویس‌های واقعی + متن نهایی تأییدشده توسط کاربر → LoRA روی Whisper. بلندمدت‌ترین سرمایه‌گذاری برای استقلال از بله.
L4 مسیریابی بر اساس اعتماد اگر کاندیدها در اعداد/واحدها اختلاف دارند، از کاربر تأیید بگیرید (UX)، نه حدس بزنید.

C4. الگوبرداری از پروژه‌های متن‌باز

مؤلفه منبع مجوز چه چیزی بردارید ملاحظات
انتقال WebSocket، ورود با OTP، ذخیره نشست، Dispatcher aiobale / autobale / BaleClient MIT ✅ همه چیز؛ حتی می‌توانید فورک کنید aiobale رها شده؛ autobale فعال‌تر ادعا می‌شود. نسخه را قفل کنید
ارسال/دانلود فایل و رسانه همان کتابخانه‌ها (گزارش A: حدود ۸۰ متد) MIT آپلود و ساخت پیام صوتی اگر معماری بات رله (B5.2) را بپذیرید، آپلود از سمت کرالر لازم نیست
تعریف RPC و پروتوها bale-sdk (proto/) اختصاصی ❌ فقط به‌عنوان مرجع و با اجازه نویسنده؛ کپی مستقیم ممنوع راه تمیز: از کپی خود کپچر web.bale.ai پروتو بسازید. ابزارها: bbpb/blackboxprotobuf، protoscope، protoc --decode_raw
دیکود بدون .proto blackboxprotobuf (NCC Group؛ پکیج رسمی PyPI با نام bbpb) MIT تحلیل payloadهای ناشناخته wire format خودتوصیف نیست؛ نام فیلد را باید خودتان حدس بزنید
الگوی DOM و صفحه‌خوانی بله اسکرپر Playwright چت (GitHub) و baleself (Selenium) بررسی مجوز هر مخزن سلکتورها، انتظار برای بارگذاری، اسکرول سلکتورها با هر آپدیت UI می‌شکنند
سرور OpenAI-compatible برای Whisper Faster-Whisper، پروژه‌های سرور مبتنی بر آن معمولاً MIT/Apache سرور داخلی با همان /v1/audio/transcriptions همان قرارداد adapter بات؛ تغییر کد لازم نیست
نرمال‌سازی و ارزیابی فارسی مقاله و دادگان PSRB (PartAI/PSRB در Hugging Face)، hazm بررسی معیار SW-WER، فهرست خطاهای رایج فارسی دادگان عمومی فقط زیرمجموعه است

قواعد پیشنهادی الگوبرداری

  1. تمیز‌کاری حقوقی: منطق را از متن‌باز MIT بردارید و تعریف RPC را از کپچر خودتان تولید کنید (clean-room).
  2. لایه‌بندی: adapter فقط قرارداد OpenAI را بشناسد؛ هیچ import از bale* بیرون از ماژول driver/ نباشد.
  3. قفل نسخه و آینه: نسخه دقیق کتابخانه‌ها را pin و کپی داخلی نگه دارید (رهاشدن مخزن، مثل aiobale، اتفاق افتاده است).
  4. تست قرارداد: با یک «ویس طلایی» شبانه بررسی کنید پاسخ هنوز ساختار قبلی را دارد.

C5. استفاده از سرویس‌های شخص ثالث

C5.1 ساده‌ترین کار: هر سرویس یک «پرووایدر» در stt.py

بات شما اکنون پرووایدرهای شماره‌دار با قرارداد OpenAI دارد. پس هر شخص ثالث را یا مستقیم (اگر OpenAI-compatible است) یا با یک adapter کوچک اضافه کنید. مزیت: چند ASR موازی، اجماع LLM، و حذف خودکار پرووایدر خراب (۵۰۳).

C5.2 داده مستقل برای انتخاب: بنچمارک PSRB (مقاله arXiv:2505.21230)

مقاله «PSRB: A Comprehensive Benchmark for Evaluating Persian ASR Systems» (مرکز تحقیقات Part AI، May 2025) ده سیستم را روی گفتار واقعی (۵۸٪ خودانگیخته، ۷۱٪ غیررسمی، ۵۳٪ نویزی/تلفنی) سنجیده است.

مدل نوع CER WER SW-WER
Avanegar تجاری فارسی 8.75 19.30 15.68
Google Chirp v2 ابری چندزبانه 9.05 19.92 15.99
Aipa تجاری فارسی 10.43 24.64 19.83
Faster-Whisper large-v3 متن‌باز 13.72 33.93 26.77
Azure ابری چندزبانه 15.78 33.94 28.06
Seamless M4T v2 متن‌باز 22.30 38.85 34.76
Whisper large-v3 متن‌باز 18.92 41.49 36.97
Vosk fa 0.5 متن‌باز 23.96 44.62 39.41
NeMo FastConformer fa متن‌باز 19.42 44.85 37.58
SLPL wav2vec2 متن‌باز 19.06 46.74 39.16

نکات خواندن این جدول:

  • ASR تجاری فارسی (و Chirp 2) تقریباً ۱۴ امتیاز WER از بهترین متن‌باز جلوتر است. این یعنی ادعای «متن‌باز پایدارترین راهکار» در گزارش A دقیق نیست. متن‌باز مستقل‌تر است، نه دقیق‌تر.
  • سوگیری احتمالی: نویسندگان از Part AI هستند و از آدرس API در پاورقی مقاله، Avanegar ظاهراً محصول همان شرکت است. پس عدد Avanegar را «بالاترین حد خوش‌بینانه» بدانید.
  • داده‌ها عمومی‌اند، نه واژگان تدارکات. نام کالا، برند، واحد و اعداد در محاوره سخت‌ترین بخش‌اند و فقط آزمون روی ویس‌های خودتان تصمیم‌گیر است.
  • مقاله می‌گوید خطا در گفتار غیررسمی و لهجه‌های محلی (مثلاً مشهدی، یزدی) بیشتر است و Whisper/Seamless هالوسینیشن و بازنویسی دارند. هر دو برای متن تدارکات خطرناک‌اند، چون عدد یا واحد اشتباه معنی سفارش را عوض می‌کند.
  • ترکیب «مرتبه اول: دو سیستم متفاوت» معمولاً از «یک سیستم قوی» پایدارتر است. اجماع LLM هم برای همین است.

C5.3 ارزیابی شخص ثالث: روش پیشنهادی (بدون وابستگی به ادعاهای فروشنده)

  1. مجموعه طلایی: ۱۰۰ ویس واقعی (حداقل ۳۰) با رونوشت انسانی و جدول کالا/تعداد/واحد.
  2. اجرای همه کاندیدها روی همان ویس‌ها: بله (دستی، وظیفه T2)، Avanegar، Aipa، Chirp 2، Faster-Whisper، مدل فاین‌تیون فارسی.
  3. سه معیار: CER (برای فارسی مناسب‌تر از WER)، WER و مهم‌تر F1 استخراج اقلام (نام کالا، تعداد، واحد).
  4. انتخاب بر اساس «بهبود اجماع»، نه دقت تکی: هر کاندید را به مجموعه فعلی اضافه کنید و ببینید خطای نهایی استخراج اقلام چقدر کم می‌شود.
  5. هزینه: هزینه به ازای هر ویس ۱۰–۳۰ثانیه‌ای در ماه برای حجم واقعی شما.

C5.4 چه سرویسی را کجا بگذاریم؟ (پیشنهاد مقدماتی)

نقش کاندید چرا
پرووایدر ۱ (تجاری فارسی) Avanegar یا Aipa بهترین در بنچمارک فارسی و لهجه‌ها
پرووایدر ۲ (ابری) Chirp 2 (اگر دسترسی ممکن است) تقریباً هم‌سطح؛ مکمل متفاوت
پرووایدر ۳ (خودمیزبان) Faster-Whisper large-v3 + واژگان تدارکات مستقل؛ قابل بهبود با داده خودتان
پرووایدر ۴ (بله) کرالر بله (S3/S4/S5) تنها کاندیدی که تفاوت واقعی (آموزش‌دیده روی داده‌های متفاوت) دارد؛ ارزش آن را وظیفه T2 تعیین می‌کند
پشتیبان سبک Vosk فقط برای اینکه همیشه یک خروجی داشته باشیم

C6. تقویت اجماع (بیشترین بازده دقت)

اقدام اثر مورد انتظار هزینه
تزریق کاتالوگ کالا/برند/واحد به LLM اجماع‌گیر بیشترین کاهش خطای استخراج اقلام کم
initial_prompt/hotwords واژگان تدارکات در Faster-Whisper بهبود نام برندها و اصطلاح‌ها کم
نرمال‌سازی فارسی (ZWNJ، ارقام) پیش از اجماع کاهش اختلاف‌های ساختگی بین کاندیدها کم
قاعده «اختلاف عدد/واحد ⇒ تأیید از کاربر» حذف سفارش اشتباه متوسط (UX)
ثبت ویس + متن تأییدشده → LoRA روی Whisper استقلال بلندمدت؛ بهبود روی لهجه و واژگان شما بالا (ولی ارزشمندترین)
VAD پیش از ASR کاهش هالوسینیشن Whisper روی سکوت/نویز (پیشنهاد مقاله PSRB) کم

C7. نظرات و توصیه‌های من (فهرست کامل)

  1. قبل از ساخت اندازه بگیرید. بدون آزمون دقت (T2)، هر ساعت کار روی کرالر قمار است. اگر بله بهتر نباشد، کرالر ارزش ندارد.
  2. بله یک کاندید است، نه ستون فقرات. معماری باید بدون آن هم درست کار کند. همین طراحی fail-soft (۵۰۳ ⇒ رد کاندید) درست است.
  3. دو ادعای کلیدی را با چشم خودتان ببینید: (الف) وجود GetTranscript، (ب) وجود دکمه در وب. هر دو گزارش آن‌ها را فرض کرده‌اند.
  4. RPC بر Playwright ترجیح دارد، اگر کپچر ساده باشد (سبک، متن خالص، سریع). Playwright فالبک مطمئن‌تر در برابر تغییر پروتکل است ولی به وجود دکمه وب وابسته است. «ورود با مرورگر، کار با پروتکل» (E1+) میان‌راه خوبی است.
  5. از کتابخانه‌های MIT استفاده کنید و پروتوی GetTranscript را خودتان از کپچر بسازید (clean-room). bale-sdk مجوز اختصاصی دارد؛ فقط با اجازه نویسنده.
  6. معماری «بات رله» ساده‌تر از آپلود از حساب کرالر است. بات رسمی ارسال را انجام می‌دهد، کرالر فقط ترنسکرایب و حذف می‌کند. (با ریسک: کار کردن RPC روی پیام دریافتی از بات.)
  7. گزارش B را شبه‌کد بدانید. نام متدها و فرض «پاسخ = متن مستقیم» را با واقعیت تطبیق دهید.
  8. «متن‌باز بهتر از تجاری است» غلط است (روی داده PSRB). برای دقت، ASR تجاری فارسی و Chirp 2 را کنار بله بسنجید. متن‌باز برای استقلال و سفارشی‌سازی است.
  9. اجماع چندکاندیدی را تقویت کنید (کاتالوگ کالا، نرمال‌سازی، قاعده اختلاف عدد/واحد). این بیشترین بازده را دارد و به بله وابسته نیست.
  10. اختلاف عدد/واحد را به کاربر برگردانید. یک تأیید کوتاه از یک سفارش اشتباه ارزان‌تر است.
  11. داده جمع کنید. ویس + متن تأییدشده، دارایی بلندمدت برای فاین‌تیون و استقلال از بله است.
  12. مسیر رسمی را هم باز کنید (ایمیل به بله). احتمال موافقت کم است ولی هزینه‌اش یک ایمیل است.
  13. قیمت‌ها و ادعاهای بازاریابی را باور نکنید؛ استعلام رسمی و آزمون روی داده خودتان.
  14. شبیه‌ساز اندروید فقط آخرین چاره است و فقط اگر بله به‌طور چشمگیر بهتر باشد و وب و RPC هر دو ممکن نباشند.
  15. چت AI بله و پراکسی‌های غیررسمی را کنار بگذارید (خروجی ناخالص، بدون SLA).
  16. هر چیز بله پشت قرارداد OpenAI و کلید env باشد تا جایگزینی، تعطیلی و rollback در چند ثانیه ممکن باشد.
  17. ToS/حقوقی تصمیم مدیریتی است و باید پیش از هر کار با حساب کاربری گرفته شود (H1). من در این سند آن را بررسی نکرده‌ام، طبق درخواست.

بخش D: ضمیمه‌ها

D1. دستورهای کشف سریع (T1، حدود ۳۰ دقیقه)

python -m venv .v && . .v/bin/activate
pip index versions aiobale-py       # آیا واقعاً وجود دارد؟
pip index versions aiobale autobale baleclient bale-sdk bbpb

mkdir -p /tmp/sdk && cd /tmp/sdk
pip download bale-sdk --no-deps --no-binary :all:
tar xzf bale_sdk-*.tar.gz
grep -rniE "transcri|turing|speech|asr|stt" bale_sdk-*/proto bale_sdk-*/docs | head -50

در پایتون (در محیط جدا):

from bale import ALL_RPCS            # نام ایمپورت bale است؛ نام توزیع bale-sdk
for r in ALL_RPCS:
    s = str(r).lower()
    if any(k in s for k in ("transcri", "turing", "speech")):
        print(r)

مجوز هر پکیج:

pip download <pkg> --no-deps && unzip -p <wheel> '*.dist-info/METADATA' | grep -iE "^(License|Classifier: License)"

D2. کپچر ترافیک وب (T4/T5)

# tools/capture_ws.py  — فریم‌های WebSocket و درخواست‌ها را ذخیره می‌کند (قبل از ذخیره redact کن)
import asyncio, json, pathlib, re, time
from playwright.async_api import async_playwright

OUT = pathlib.Path("captures"); OUT.mkdir(exist_ok=True)
REDACT = re.compile(rb"(eyJ[A-Za-z0-9_\-\.]{20,})")      # JWT

def save(kind: str, url: str, payload):
    data = payload if isinstance(payload, (bytes, bytearray)) else str(payload).encode()
    data = REDACT.sub(b"<JWT>", data)
    (OUT / f"{int(time.time()*1000)}_{kind}.bin").write_bytes(data)

async def main():
    async with async_playwright() as p:
        ctx = await p.chromium.launch_persistent_context("/data/profile", headless=False)
        page = ctx.pages[0] if ctx.pages else await ctx.new_page()

        def on_ws(ws):
            print("WS", ws.url)
            ws.on("framesent", lambda payload: save("ws_sent", ws.url, payload))
            ws.on("framereceived", lambda payload: save("ws_recv", ws.url, payload))

        page.on("websocket", on_ws)
        page.on("request", lambda r: print("REQ", r.method, r.url) if r.resource_type in ("fetch", "xhr") else None)
        await page.goto("https://web.bale.ai")
        input("ورود را انجام بده، ویس را بفرست، روی «تبدیل به متن» بزن، سپس Enter...")
        await ctx.close()

asyncio.run(main())

نکات: (۱) پروفایل را روی ولوم امن نگه دار. (۲) فایل‌های captures/*.bin را قبل از هر اشتراک بازبینی کن؛ شماره تلفن و شناسه‌ها را هم redact کن. (۳) در سرور بدون نمایشگر از xvfb استفاده کن. (۴) ورود OTP را انسان انجام دهد (H4). (۵) نسخه Playwright را pin کن؛ نام رویدادها بین نسخه‌ها فرق می‌کند، پس مستندات نصب‌شده را بخوان.

رمزگشایی اولیه:

# فریم باینری بدون پیشوند را مستقیم، یا با برداشتن ۵ بایت ابتدای فریم gRPC-Web
protoc --decode_raw < captures/<file>.bin
# یا: pip install bbpb  و استفاده از decode_message

D3. دستور شروع برای ایجنت (قابل کپی)

نقش: تو ایجنت مجری پروژه «کرالر STT بله» هستی. سند مرجع: این فایل (بخش‌های B و C).
قواعد: بخش B1 را کامل رعایت کن. هیچ سکرتی را چاپ نکن. بدون تأیید انسان هیچ کاری در حساب بله انجام نده (H1).
شروع: وظایف T0، T1، T3 و T2 (کیت ارزیابی) را همین حالا اجرا کن؛ T9 و T10 مستقل از بله‌اند.
هر وظیفه: خروجی در docs/findings/ ، برچسب ✅/🟡/❌ ، و گزارش دروازه با قالب B9.1.
جایی که به انسان نیاز داری: از قالب B9.2 استفاده کن و ادامه بده با کارهای مستقل.
اگر G0 رد شد: T4..T8 و T11 را اجرا نکن و گزارش بده.
در پایان هر مرحله: خلاصه ۵ خطی برای مدیر پروژه بنویس (نتیجه، عدد، ریسک، تصمیم لازم).

D4. پرسش‌های باز (بررسی در وظایف مربوطه)

# پرسش وظیفه
1 GetTranscript واقعاً وجود دارد؟ نام سرویس، فیلدها، نوع پاسخ؟ T1، T5
2 دکمه «تبدیل به متن» در web.bale.ai برای ویس ارسالی و دریافتی هست؟ T4
3 پاسخ RPC متن مستقیم است یا DownloadSource؟ T5
4 RPC روی ویسی که بات فرستاده کار می‌کند؟ T6
5 ویس بات رله با OGG/Opus به‌صورت «ویس» نمایش داده می‌شود؟ T6
6 حساب کرالر برای دریافت پیام از بات رله باید /start کند؟ T6
7 stt.py با ۵۰۳ و متن خالی چه می‌کند؟ شماره پرووایدرها؟ timeout؟ T0
8 نام و وضعیت واقعی aiobale-py؟ کدام کتابخانه MIT برای انتقال بهتر کار می‌کند؟ T1، T6
9 حجم واقعی ویس‌ها در روز و طول متوسط؟ H3 / T12
10 دسترسی شبکه سرور به Chirp 2 و ارائه‌دهندگان خارجی؟ T9
11 قیمت واقعی پرووایدرهای تجاری برای حجم شما؟ H7
12 مالک نگهداری کرالر پس از استقرار؟ H10
13 پاسخ بله به درخواست رسمی (S1)؟ H9

D5. واژه‌نامه

اصطلاح معنی
کاندید خروجی یک موتور STT برای یک ویس
اجماع ترکیب کاندیدها توسط LLM به یک خروجی ساختاریافته
adapter میکروسرویسی با قرارداد OpenAI که موتور بله را پشت خود پنهان می‌کند
بات رله بات رسمی جداگانه که فقط ویس را برای حساب کرالر می‌فرستد
E1/E1+/E2/E3 موتورهای RPC / ورود مرورگر + کار پروتکل / Playwright کامل / امولاتور
G0…G7 دروازه‌های تصمیم (A8)
H1…H11 ورودی/تصمیم‌های انسانی (A5)
clean-room تولید تعریف RPC از مشاهده ترافیک خودمان، نه کپی از پروژه اختصاصی
CER/WER نرخ خطای کاراکتر/کلمه
F1 اقلام دقت و بازیابی استخراج «کالا، تعداد، واحد»
PSRB بنچمارک مستقل ASR فارسی (arXiv:2505.21230)

D6. منابع