diff --git a/bale stt report/bale_stt_crawler_playbook.md b/bale stt report/bale_stt_crawler_playbook.md new file mode 100644 index 0000000..cc9ae75 --- /dev/null +++ b/bale stt report/bale_stt_crawler_playbook.md @@ -0,0 +1,388 @@ +# پلی‌بوک اجرایی: افزودن موتور 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. diff --git a/bale stt report/bale_stt_final_report.md b/bale stt report/bale_stt_final_report.md new file mode 100644 index 0000000..f9a2d37 --- /dev/null +++ b/bale stt report/bale_stt_final_report.md @@ -0,0 +1,256 @@ +# گزارش جامع: اعتبارسنجی گزارش‌ها و راهکار کرال موتور STT بله + +> **تاریخ:** ۱۵ مهر ۱۴۰۵ +> **موضوع:** افزودن متن تولیدشده توسط قابلیت «تبدیل ویس به متن» پیام‌رسان بله به‌عنوان یک کاندید در خط‌لوله چندمدله STT بات تدارکات +> **ورودی‌ها:** سه سند ارائه‌شده (تحلیل جامع، پلی‌بوک اجرایی، تحلیل مقایسه‌ای) + راستی‌آزمایی مستقل وب +> **محدودیت:** `integration_report` شماره ۱ و ۲ در دسترس من نبود و فقط از روی نقل‌قول‌های همین اسناد قضاوت شده‌اند. دستورهای کشف (بخش ۴) در محیط من اجرا نشده‌اند، چون شبکه sandbox بسته بود. + +--- + +## ۱. خلاصه مدیریتی + +1. **Bot API رسمی بله STT ندارد.** همه اسناد در این مورد درست می‌گویند. +2. **تناقض اصلی** (مهندسی معکوس «غیرعملی است» در برابر «ساده است») هیچ‌کدام کاملاً درست نیست. مسیر شدنی است، ولی «افزودن یک متد» نیست و نیاز به یک فاز کشف دارد. +3. **دو فرض اثبات‌نشده** کل مسیر را تحت تأثیر قرار می‌دهد: + - دکمه «تبدیل به متن» روی **وب** بله وجود دارد (هر سه سند فرض کرده‌اند، هیچ منبعی تأیید نکرده). + - پکیج `aiobale-py` نسخه 0.3.9 فعال است (پیدا نشد؛ به بخش ۲ مراجعه کنید). +4. **کشف جدید:** پکیج `bale-sdk` با ۶۰۷ RPC تایپ‌شده و پروتوهای بازیابی‌شده. احتمال وجود RPC ترنسکریپشن در آن بالاست. ولی مجوز آن اختصاصی است. +5. **بزرگ‌ترین صرفه‌جویی:** آزمون دقت را **قبل از ساخت** و به‌صورت **دستی** روی ۳۰ ویس واقعی انجام دهید. اگر بله از خط‌لوله فعلی بهتر نبود، پروژه لازم نیست. +6. **ریسک حقوقی/ToS** (استفاده خودکار از حساب کاربری) تصمیم مدیریتی است و باید قبل از استقرار روشن شود. + +--- + +## ۲. اعتبارسنجی گزارش‌ها + +### ۲.۱ جدول ادعاها + +| ادعا | نتیجه | توضیح | +|---|---|---| +| Bot API رسمی STT ندارد | ✅ تأیید | پروب‌ها همگی ۴۰۴ داده‌اند و تایپ‌های SDKها برای Voice فقط شناسه فایل و مدت‌زمان دارند. **تذکر:** ۴۰۴ برای نام‌های حدسی چیزی درباره فیلد مخفی در payload نمی‌گوید؛ تست T1 (ویس به یک بات تستی و خواندن JSON کامل) هنوز لازم است (۳۰ دقیقه). | +| بله قابلیت تبدیل ویس به متن دارد | ✅ با شرط | نسخه جدید بله این قابلیت را برای اندروید و iOS منتشر کرده. وجود دکمه در **وب** تأییدنشده است و ریسک اصلی مسیر Playwright همین است. | +| مهندسی معکوس «غیرعملی» است (گزارش ۲) | ⚠️ بدبینانه | کلاینت‌های متن‌باز مهندسی‌معکوس‌شده وجود دارند. اما استدلال «Protobuf خودتوصیف است» نادرست است: wire format بدون `.proto` فقط تا حدی (شماره فیلد و نوع خام، بدون نام و معنا) دیکود می‌شود. | +| مهندسی معکوس «در حد افزودن یک متد» است (پلی‌بوک) | ⚠️ خوش‌بینانه | کشف RPC، نگاشت فیلدها و پایداری نشست کار واقعی است. تخمین «نیم‌روز» برای T2 ریسکی است. | +| `aiobale-py` v0.3.9 فعال است و نام `aiobale` را پکیج خالی گرفته | ❌ تأییدنشده / احتمالاً غلط | `aiobale-py` را پیدا نکردم. `aiobale` یک کتابخانه واقعی است (MIT، مستندات `docs.aiobale.ir`، نسخه‌های 0.1.x). فورک‌هایی مثل Autobale می‌گویند نسخه اصلی نگهداری نمی‌شود و باگ دارد. قبل از وابستگی به هر نام، `pip index versions` را اجرا کنید. | +| `bale-sdk` (کشف جدید) | 🔎 مهم | از کلاینت وب بله مهندسی معکوس شده؛ ۶۰۷ RPC در ۵۲ سرویس، پروتوهای بازیابی‌شده، احراز با JWT یا OTP؛ نسخه 0.1.1 (ژوئن ۲۰۲۶). **مجوز اختصاصی و «همه حقوق محفوظ»**. | +| تأخیر «زیر ۱ ثانیه» | ❌ رد | همه موافقند. اعداد ۱–۴ ثانیه (R2) و ۴–۸ ثانیه (Playwright) هم **اندازه‌گیری نشده‌اند**؛ فقط T5 عدد واقعی می‌دهد. | +| AI Chat بله ویس را «پیاده» می‌کند (R3) | ⚠️ تأییدنشده و نامطلوب | حتی اگر درست باشد، خروجی «متن + تفسیر مدل» است و برای consensus مناسب نیست. با کنار گذاشتن موافقم. | +| RAM ‏۳۵۰–۵۰۰ مگابایت برای Playwright | ✅ معقول | برای بات تک‌کاربره قابل‌تحمل است. | +| Playwright در ۱–۲ روز | ⚠️ خوش‌بینانه | وابسته به وجود دکمه وب، چالش‌های ورود و پایداری سلکتورها. | +| ساختار Gate / State-machine پلی‌بوک | ✅ قوی | بهترین بخش اسناد. ایراد: دروازه دقت (T7) بعد از ساخت آمده، در حالی که دستی و قبل از ساخت شدنی است. | +| ریسک بن، ToS و حریم خصوصی | ✅ درست و حیاتی | فقط پلی‌بوک به آن پرداخته است. | +| ترجیح WebSocket به POST gRPC (پلی‌بوک) | ✅ منطقی | کلاینت رسمی بله POST gRPC را عمدتاً برای چند متد احراز هویت استفاده می‌کند؛ استفاده زیاد از آن غیرعادی به نظر می‌رسد. | +| «Playwright اول» (سند مقایسه‌ای) در برابر «R2 اول» (پلی‌بوک) | ⚠️ هر دو مشروط | بدون نتیجه کشف روز اول قابل تصمیم نیست. | + +### ۲.۲ ایرادهای کد adapter در سند اول + +- کلید پیش‌فرض ضعیف (`bale_secure_token`) و مقایسه کلید با `!=` به‌جای مقایسه ثابت‌زمان. +- بلعیدن همه استثناها بدون لاگ. +- بازگرداندن ۲۰۰ با متن خالی: ممکن است بات آن را یک کاندید معتبر (خالی) در consensus بشمارد. باید در `bot/app/stt.py` بررسی شود که پاسخ ۵xx و متن خالی چطور مدیریت می‌شوند. توصیه: در خطا ۵۰۳ برگردد. +- نبود قفل ترتیبی، سقف روزانه و تأخیر تصادفی در خود adapter. + +### ۲.۳ نکات فنی اصلاح‌شده + +- فریم gRPC-Web پیشوند ۵ بایتی دارد (۱ بایت فلگ + ۴ بایت طول). این برای **POST gRPC-Web** درست است؛ اینکه فریم‌های WebSocket بله هم همان قالب را دارند فرض اثبات‌نشده‌ای است و باید در T2 دیده شود. +- هاست‌ها و نام ماژول‌هایی مثل `maviz-ws.bale.ai` و `SpeechTranscript` از تحلیل باندل آمده‌اند و در وب ایندکس نشده‌اند. باید با چشم در کپچر تأیید شوند. + +--- + +## ۳. راهکارها + +| راهکار | توضیح | رم | خلوص متن | ریسک | حکم | +|---|---|---|---|---|---| +| **A. مسیر رسمی** | درخواست API یا همکاری از بله | — | — | — | هزینه صفر؛ قبل از هر کار غیررسمی یک ایمیل بزنید | +| **B. RPC مستقیم** | کلاینت کاربری روی WebSocket با `bale-sdk` یا `aiobale` + RPC ترنسکریپت | ≈ ۲۰ MB | خالص | متوسط (تغییر پروتکل، بن) | **بهترین نتیجه فنی**، مشروط به کشف RPC | +| **C. Playwright** | Chromium headless روی `web.bale.ai` با پروفایل پایدار | ۳۵۰–۵۰۰ MB | خالص | کم–متوسط | مقاوم، **فقط اگر دکمه وب موجود باشد** | +| **D. AI Chat** | ویس به چت هوش مصنوعی بله و استخراج از پاسخ | ≈ ۲۰ MB | ❌ متن + تفسیر | بالا | کنار گذاشته شود | +| **E. سرویس ثالث** | پروکسی‌های STT با ادعای بله | — | نامشخص | حریم خصوصی/پایداری | کنار گذاشته شود | +| **F. اندروید امولاتور** | اپ بله + uiautomator | زیاد | خالص | بالا (شکننده) | آخرین چاره اگر B و C ممکن نشد | + +--- + +## ۴. مسیر دقیق اجرا + +### گام ۰: آزمون دقت دستی (نیم‌روز، بدون کد) + +۳۰ ویس واقعی تدارکاتی (با رضایت فرستندگان؛ صداها را بلندمدت نگه ندارید) را در اپ بله دستی به متن تبدیل کنید و سه حالت را مقایسه کنید: + +1. خروجی فعلی بات (whisper/سایرین + consensus) +2. فقط متن بله +3. consensus با اضافه‌شدن بله + +سه معیار: WER متن، دقت استخراج اقلام (نام کالا، برند، تعداد، واحد) و تعداد موارد سخت لهجه‌ای که با بله درست شده‌اند. + +**معیار پیشنهادی:** کاهش نسبی خطای استخراج حدود ۱۵٪ یا بیشتر، یا حل حداقل ۲ مورد سخت. در غیر این صورت **STOP**. + +### گام ۱: کشف سریع (حدود ۳۰ دقیقه) + +```bash +python -m venv .v && . .v/bin/activate +pip index versions aiobale-py # وجود یا عدم وجود را بررسی کنید +pip download bale-sdk --no-deps --no-binary :all: && tar xzf bale_sdk-*.tar.gz +grep -rniE "transcri|speech|asr|stt" bale_sdk-*/proto bale_sdk-*/docs | head -50 +``` + +همچنین در پایتون `from bale import ALL_RPCS` را با همان کلیدواژه‌ها فیلتر کنید. + +همزمان **T1**: یک بات تستی مستقل بسازید، یک ویس بفرستید و JSON کامل آپدیت را بخوانید. اگر فیلد ترنسکریپشن وجود داشت، پروژه کرالر لازم نیست. + +### گام ۲: وب و کپچر (نیم‌روز تا یک روز) + +1. با **یک حساب اختصاصی شرکت** روی `web.bale.ai` وارد شوید و بررسی کنید دکمه «تبدیل به متن» روی ویس هست یا نه (روی ویس دریافتی و ارسالی). +2. DevTools تب Network را با Preserve log و فیلتر WS و Fetch/XHR ضبط کنید و هنگام کلیک روی دکمه ذخیره کنید: + - URL کامل و ترابری (unary یا WS) + - هدر احراز + - `req.bin` و `resp.bin` + - زمان پاسخ (همان تأخیر واقعی ASR) +3. ۵ تا ۱۰ ویس متنوع (کوتاه، بلند، لهجه‌دار) را تکرار کنید و در `findings.md` خلاصه کنید. +4. دیکود آفلاین: پیشوند ۵ بایتی را بردارید، سپس `protoc --decode_raw` یا `protoscope`. + +### گام ۳: انتخاب مسیر + +| وضعیت | مسیر | +|---|---| +| RPC در `bale-sdk` یا کپچر پیدا شد | **B** | +| RPC پیدا نشد ولی دکمه وب هست | **C** | +| هیچ‌کدام | **F** یا توقف | +| ترنسکرایب نیاز به امضا یا اثر انگشت مرورگر دارد | **C** | + +**الگوی پیشنهادی برای B:** بات خودتان با `sendVoice(file_id)` ویس را برای حساب کرالر بفرستد (کرالر یک‌بار بات را `/start` کرده). این‌طور نیازی به دانلود و آپلود مجدد نیست و `message_id` بومی در اختیار کرالر است. پس از دریافت متن، پیام حذف شود. اگر RPC به `message_id` نیاز نداشت، مستقیم فایل یا بایت داده شود. + +**مجوز:** `aiobale` مجوز MIT دارد ولی نگهداری نمی‌شود (فورک‌ها: Autobale و BaleClient). `bale-sdk` مجوز اختصاصی دارد؛ برای استفاده تجاری از نویسنده اجازه بگیرید. + +### گام ۴: ساخت adapter سخت‌شده + +قرارداد OpenAI-compatible (همان `POST /v1/audio/transcriptions` که بات انتظار دارد) و موتور قابل‌سوییچ با `BALE_STT_ENGINE=grpc|playwright`. پیشنهاد کد: + +```python +import os, secrets, asyncio, logging +from fastapi import FastAPI, File, Form, Header, HTTPException, UploadFile + +log = logging.getLogger("bale-stt") +app = FastAPI() +KEY = os.environ["BALE_STT_INTERNAL_KEY"] # بدون مقدار پیش‌فرض +DAILY_CAP = int(os.environ.get("BALE_STT_DAILY_CAP", "100")) +lock = asyncio.Lock() # اجرای ترتیبی +count_today = 0 # در عمل با ریست روزانه + +@app.get("/healthz") +def healthz(): + return {"status": "ok"} + +@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, f"Bearer {KEY}"): + raise HTTPException(401) + if count_today >= DAILY_CAP: + raise HTTPException(429) + async with lock: + try: + text = await run_engine(await file.read()) # + تأخیر تصادفی ۱ تا ۳ ثانیه + except Exception: + log.exception("bale stt failed") + raise HTTPException(503) # تا بات فقط همین کاندید را حذف کند + if not text: + raise HTTPException(503) + return {"text": text} +``` + +اتصال به بات (بدون تغییر کد): + +```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` پر باشند. + +### گام ۵: عملیات و ایمنی + +- فقط **شماره اختصاصی شرکت**؛ هرگز شماره شخصی. +- `session_file` یا پروفایل مرورگر روی ولوم داکر و هرگز در گیت. +- **OTP تنها گام انسانی** است. در ابطال نشست: هشدار به ادمین و هدایت ترافیک به سایر پرووایدرها. +- اجرای ترتیبی، فاصله تصادفی ۱ تا ۳ ثانیه، سقف روزانه، و ترجیح WebSocket به POST gRPC. +- حذف فوری پیام اسکرچ پس از استخراج و عدم نگهداری فایل صوتی روی دیسک. +- پایش: نرخ موفقیت، p50 و p95 تأخیر، انواع خطا، وضعیت نشست. هشدار اگر نرخ خطا در ۳۰ دقیقه بیش از ۲۰٪ شد. +- **Rollback:** خالی کردن `STT_PROVIDER_3_*` در env بات و توقف کانتینر. بات فوراً به وضعیت قبل برمی‌گردد. +- سکرت‌هایی که در `docker-compose.yml` کامیت شده‌اند (توکن بات، کلید DeepSeek) باید rotate شوند و `DRAFT_TOKEN_SECRET` پیش‌فرض ضعیف را عوض کنید. +- روی برنچ‌های دیگران (`prod-v7` و غیره) دخالت نکنید و بدون تأیید مالک چیزی پوش نشود. + +### گام ۶: اندازه‌گیری و سواک + +- **T5:** ۵۰ تا ۱۰۰ ویس ترتیبی با فاصله انسانی؛ پذیرش پیشنهادی: موفقیت ≥ ۹۵٪ و p95 ≤ ۸ ثانیه. +- **سواک یک‌هفته‌ای** با حجم کم (حدود ۱۰۰ ترنسکرایب در روز): ثبت تعداد ورود مجدد، چالش‌های امنیتی و عمر واقعی توکن. + +--- + +## ۵. دروازه‌های تصمیم (خلاصه) + +| دروازه | شرط عبور | در صورت شکست | +|---|---|---| +| G0 دقت دستی | بهبود ≈ ۱۵٪ یا ۲ مورد سخت | **STOP** | +| G1 (T1) | فیلد ترنسکریپشن نبود | اگر بود: پایان، مستقیم وصل شود | +| G2 کشف | RPC یا دکمه وب پیدا شد | F یا توقف | +| G3 پروتوتایپ | درخواست ساخته و پاسخ خوانده شد | سوییچ به C | +| G4 نرخ و پایداری | ≥ ۹۵٪ موفقیت، p95 ≤ ۸ ثانیه | کاهش نرخ و backoff؛ در صورت بن: توقف | +| G6 ToS | تصمیم مکتوب مدیریت | **STOP** یا حجم بسیار محدود | +| G7 سواک | نشست سالم یک هفته | rollback و بازگشت به G4 | + +--- + +## ۶. زمان‌بندی واقع‌بینانه + +| مرحله | زمان | +|---|---| +| گام ۰ و ۱ (دقت دستی + کشف سریع) | نیم‌روز | +| گام ۲ (وب و کپچر) | ۱ روز | +| ساخت B یا C | ۲ تا ۴ روز | +| T5 و سواک | ۱ هفته | + +--- + +## ۷. رجیستر ریسک + +| ریسک | احتمال | اثر | مقابله | +|---|---|---|---| +| بن یا rate-limit حساب | متوسط | توقف مسیر | حجم کم، WS-first، سقف روزانه، حساب اختصاصی قابل‌تعویض | +| تغییر پروتکل یا UI بله | متوسط | قطع سرویس | هشدار خطا، فالبک، سوییچ موتور با env، rollback با env | +| انقضای نشست و نیاز به OTP | بالا (دوره‌ای) | توقف موقت | ولوم نشست + هشدار + runbook ورود انسانی | +| دکمه وب وجود ندارد | نامعلوم | ناممکن‌شدن C | بررسی در گام ۲ قبل از ساخت | +| دقت ناکافی نسبت به هزینه | نامعلوم | هزینه بی‌فایده | G0 قبل از ساخت | +| ToS / مسائل حقوقی | نامعلوم | توقف اجباری | تصمیم مکتوب مدیریت (G6)؛ ایمیل به بله (A) | +| مجوز اختصاصی `bale-sdk` | بالا | ممنوعیت استفاده | اجازه از نویسنده یا استفاده از `aiobale` (MIT) | +| حریم خصوصی صدا | کم | اعتباری | حذف فوری پیام‌ها، عدم نگهداری فایل | +| وابستگی به یک فرد نگهدارنده | متوسط | رکود | مستندسازی و مالک رسمی | + +--- + +## ۸. دروازه‌های انسانی + +1. OTP ورود حساب (بار اول و هر انقضا). +2. تصمیم ToS / حقوقی. +3. جمع‌آوری نمونه ویس واقعی با رضایت. +4. تأیید نهایی استقرار و مرج توسط مهندس ارشد. + +--- + +## ۹. توصیه نهایی + +1. **همین هفته:** گام ۰ (دقت دستی) + ایمیل به بله + گام ۱ (کشف سریع و T1). +2. **اگر دقت توجیه داشت:** گام ۲ و انتخاب B یا C بر اساس شواهد. +3. **معماری:** adapter با قرارداد OpenAI و موتور قابل‌سوییچ؛ بات دست‌نخورده می‌ماند. +4. **تصمیم مدیریتی** درباره ToS قبل از استقرار. +5. اگر هر کدام از G0 یا G6 شکست خورد، **توقف** و ادامه با پرووایدرهای فعلی. + +--- + +## منابع + +- aiobale (PyPI): https://pypi.org/project/aiobale +- bale-sdk (PyPI): https://pypi.org/project/bale-sdk/ +- Autobale (فورک aiobale): https://pypi.org/project/autobale/ +- خبر انتشار نسخه بله با قابلیت تبدیل پیام صوتی به متن (خبرگزاری آنا): https://ana.ir/fa/news/994908 +- مستندات رسمی بات بله: https://docs.bale.ai diff --git a/bale stt report/bale_stt_integration_report.md b/bale stt report/bale_stt_integration_report.md new file mode 100644 index 0000000..fcea3bd --- /dev/null +++ b/bale stt report/bale_stt_integration_report.md @@ -0,0 +1,159 @@ +# گزارش فنی و امکان‌سنجی: ادغام قابلیت صوت به متن (STT) بله در سامانه تدارکات هوشمند + +> [!NOTE] +> **مخاطب گزارش:** تیم فنی، معماران سیستم و مدیران محصول +> **موضوع:** بررسی، مقایسه دو رویکرد فنی و نقشه راه پیاده‌سازی اتصال به موتور تبدیل ویس به متن پیام‌رسان «بله» +> **هدف نهایی:** افزودن بله به عنوان یکی از مدل‌های کاندید در خط لوله STT چندمدله (Multi-Provider STT) جهت افزایش دقت استخراج اقلام تدارکات + +--- + +## ۱. خلاصه مدیریتی (Executive Summary) + +سامانه هوشمند تدارکات در حال حاضر مجهز به موتور پردازش موازی صوت (**Multi-Provider STT**) است که قادر است فایل‌های صوتی دریافتی از کاربران را هم‌زمان به چندین ارائه‌دهنده (نظیر Whisper، Soniox یا سرویس‌های ابری) ارسال کرده و از طریق مدل تحلیلی DeepSeek یک خروجی یکپارچه و دقیق (**Consensus Text**) به همراه اقلام استعلام استخراج کند. + +برای افزایش هرچه بیشتر دقت در زبان فارسی و لهجه‌های محلی، استفاده از **موتور صوت‌به‌متن بومی پیام‌رسان بله** به عنوان یکی از مدل‌های کاندید مطرح شده است. از آن‌جا که پلتفرم توسعه‌دهندگان بله (Bale Bot API) متد مستقیمی برای فراخوانی API ترنسکرایب ارائه نمی‌دهد، دو روش اجرایی اصلی برای دستیابی به این موتور وجود دارد: +1. **روش اول (اتوماسیون رابط کاربری وب / Headless Browser):** شبیه‌سازی رفتار کاربر در مرورگر و کلیک روی دکمه تبدیل وویس. +2. **روش دوم (مهندسی معکوس پروتکل کلاینت / Direct Client API):** شبیه‌سازی درخواست‌های شبکه (WebSocket/HTTP) کلاینت بله بدون نیاز به مرورگر. + +--- + +## ۲. پس‌زمینه فنی زیرساخت بله (Technical Context) + +| بخش | وضعیت دسترسی | توضیحات | +| :--- | :--- | :--- | +| **Bale Bot API (رسمی)** | فاقد متد STT | فقط امکان دانلود فایل صوتی خام (`voice` / `audio`) با `file_id` را فراهم می‌کند. | +| **کلاینت‌های کاربری بله (Web / Android)** | دارای موتور فعال STT | روی هر ویس یک دکمه تبدیل صوت به متن وجود دارد که با فشرده شدن آن، صوت روی سرورهای بله به متن تبدیل می‌شود. | +| **دستیار هوش مصنوعی بله** | پشتیبانی از ورودی صوتی | کاربر با ارسال ویس به چت هوش مصنوعی بله، متن پیاده‌شده به همراه پاسخ تحلیلی را دریافت می‌کند. | + +**نتیجه:** برای بهره‌برداری از این قابلیت، بات تدارکات باید در لایه ارتباطی خود، یک نشست کاربری (User Session) در بله داشته باشد تا بتواند خدمات تبدیل صوت کلاینت را فراخوانی کند. + +--- + +## ۳. روش اول: اتوماسیون رابط کاربری وب (Headless Browser Scraping) + +در این رویکرد، یک مرورگر کنترل‌شده بدون واسط گرافیکی (مانند Chromium تحت فریم‌ورک **Playwright**) در یک کانتینر اختصاصی داکر مستقر شده و در نسخه وب بله (`web.bale.ai`) فعال نگه داشته می‌شود. + +### معماری جریان داده (Architecture Flow) +```mermaid +sequenceDiagram + autonumber + participant Bot as Procurement Bot + participant Worker as Browser Worker (Playwright) + participant BaleWeb as web.bale.ai (Chromium) + participant BaleServer as سرورهای پردازش صوت بله + + Bot->>Worker: ارسال فایل صوتی (voice.mp3) + Worker->>BaleWeb: بارگذاری فایل در چت کلاینت + BaleWeb->>BaleServer: ارسال صوت و درخواست تبدیل + BaleServer-->>BaleWeb: بازگرداندن متن پیاده‌شده به صفحه + Worker->>BaleWeb: استخراج متن از DOM (.transcription-text) + Worker-->>Bot: بازگرداندن متن خروجی به خط لوله STT +``` + +### مؤلفه‌های پیاده‌سازی: +1. **مدیریت نشست پایدار (Persistent Profile):** + - پوشه پروفایل مرورگر (`userDataDir`) بر روی یک داکر ولوم ماندگار (`/data/bale_browser_session`) قرار می‌گیرد. + - ورود اولیه فقط یک‌بار توسط تیم فنی با شماره تلفن اختصاصی و کد OTP پیامکی انجام می‌شود و کوکی‌ها و توکن‌های LocalStorage/IndexedDB برای همیشه حفظ می‌گردند. +2. **انتظار هوشمند بر اساس رخدادهای DOM:** + - استفاده از سلکتورهای مقاوم با `waitForSelector` یا `MutationObserver` روی باکس متن ترنسکرایب. +3. **مزایا و معایب:** + - 🟢 **تضمین تطابق با سرور:** چون مرورگر رسمی در حال اجرا است، هیچ‌گونه ریسک ناسازگاری پکت‌های شبکه وجود ندارد. + - 🟢 **پایداری در برابر تغییرات داخلی API:** حتی اگر بله فریم‌ورک پکت‌های وب‌سوکت خود را عوض کند، تا زمانی که دکمه در UI وجود دارد سیستم کار می‌کند. + - 🔴 **مصرف بالای منابع:** هر نمونه Chromium حدود ۳۰۰ تا ۵۰۰ مگابایت رم اشغال می‌کند. + - 🔴 **تاخیر زمانی (Latency):** فرآیند رندر صفحه، شبیه‌سازی آپلود و رویداد کلیک حدود ۳ تا ۶ ثانیه زمان می‌برد. + +--- + +## ۴. روش دوم: مهندسی معکوس شبکه و پروتکل کلاینت (Direct Client API) + +در این روش، بدون استفاده از مرورگر سنگین، کلاینت پایتونی سبک ما مستقیماً همان درخواست‌های سوکت یا HTTP کلاینت رسمی بله را بازسازی و فراخوانی می‌کند. + +### معماری جریان داده (Architecture Flow) +```mermaid +sequenceDiagram + autonumber + participant Bot as Procurement Bot + participant Adapter as Bale Client Protocol Adapter (Python) + participant BaleCore as هسته سرور بله (WebSocket / RPC) + + Bot->>Adapter: ارسال بایت‌های فایل صوتی + Adapter->>BaleCore: فراخوانی مستقیم متد Transcribe (از طریق سوکت یا HTTP) + BaleCore-->>Adapter: پاسخ مستقیم باینری/JSON متن + Adapter-->>Bot: متن نهایی در کمتر از ۱ ثانیه +``` + +### مؤلفه‌های پیاده‌سازی: +1. **آنالیز ترافیک شبکه (Traffic Inspection):** + - تحلیل تب Network مرورگر در `web.bale.ai`: + - استخراج مسیر اتصال (غالباً `wss://api.bale.ai/v1/users/websocket` یا اندپوینت‌های REST). + - استخراج هدرهای نشست از `localStorage` کلاینت وب (مانند `access_token`, `device_id`, `session_key`). + - کشف پکت یا درخواست RPC مسئول تبدیل صوت (شامل شناسه پیام `message_id` و شناسه فایل صوتی `file_id`). +2. **کلاینت ناهمگام پایتون (`httpx` / `websockets`):** + - اتصال دائمی و سبک به زیرساخت پیام‌رسانی بله. + - ارسال مستقیم درخواست بدون بارگذاری فایل‌های گرافیکی، جاوااسکریپت و CSS مرورگر. +3. **مزایا و معایب:** + - 🟢 **سرعت خیره‌کننده (Ultra-low Latency):** پاسخگویی معمولاً در **کمتر از ۱ ثانیه** انجام می‌شود. + - 🟢 **مصرف ناچیز منابع (Lightweight):** مصرف حافظه رم زیر ۱۰ مگابایت. + - 🟢 **ظرفیت پردازش همزمان بالا (High Concurrency):** امکان ارسال همزمان ده‌ها درخواست بدون افت پرفورمنس. + - 🔴 **حساسیت به پروتکل:** در صورت بازنویسی پروتکل یا فیلدهای باینری کلاینت بله، ممکن است نیاز به به‌روزرسانی پکت‌ها باشد. + +--- + +## ۵. ماتریس مقایسه جامع فنی (Technical Decision Matrix) + +| معیار ارزیابی | روش اول: اتوماسیون وب (Playwright) | روش دوم: مهندسی معکوس پروتکل کلاینت | اهمیت برای سیستم | +| :--- | :---: | :---: | :---: | +| **تاخیر پاسخ‌دهی (Latency)** | ۳ تا ۶ ثانیه | **زیر ۱ ثانیه** ⚡️ | بسیار بالا (تاثیر مستقیم روی UX وویس) | +| **مصرف حافظه RAM** | ۳۵۰ الی ۵۰۰ مگابایت | **کمتر از ۱۵ مگابایت** 🪶 | متوسط به بالا | +| **هزینه پردازشی CPU** | متوسط تا بالا (رندرینگ) | بسیار پایین (درخواست شبکه) | متوسط | +| **پیچیدگی پیاده‌سازی اولیه** | کم (طی چند ساعت با سلکتور) | متوسط (نیازمند اسنیف و دیکود پکت) | موقت | +| **پایداری نشست کاربری (Session)** | ذخیره در پوشه کاربری مرورگر | ذخیره توکن در متغیر محیطی / دیتابیس | بالا | +| **ریسک تغییرات توسط بله** | کم (مقاوم در برابر تغییر پروتکل) | نیازمند پایش در تغییر نسخه‌های ماژور | متوسط | +| **قابلیت مقیاس‌پذیری (Scalability)** | محدود (محدودیت منابع مرورگر) | نامحدود (ایسنک و چندنخی) | بالا | + +--- + +## ۶. نحوه اتصال به پایپ‌لاین STT چندمدله فعلی + +سامانه تدارکات در حال حاضر قابلیت دریافت چند کاندیدا را دارد. با اتصال بله، خروجی به شکل زیر در پردازش قرار می‌گیرد: + +```mermaid +graph TD + Voice[وویس دریافتی کاربر از بله] --> FFMPEG[تبدیل یک‌باره به MP3] + FFMPEG --> S1[مدل ۱: Whisper-Large] + FFMPEG --> S2[مدل ۲: Soniox / OpenRouter] + FFMPEG --> S3[مدل ۳: موتور صوت هوش مصنوعی بله] + + S1 --> Aggregator[تجمیع نتایج کاندیداها] + S2 --> Aggregator + S3 --> Aggregator + + Aggregator --> DeepSeek[تحلیلگر هوشمند DeepSeek] + DeepSeek --> Final[متن اصلاح‌شده Consensus Text + استخراج اقلام استعلام JSON] +``` + +--- + +## ۷. نقشه راه پیشنهادی دو مرحله‌ای برای تیم توسعه (Recommended Roadmap) + +> [!TIP] +> **استراتژی برد-برد (Fast Win + High Performance):** +> برای اینکه از سریع‌ترین حالت ممکن با کمترین ریسک بهره‌مند شویم، فرآیند اجرای فازبندی‌شده زیر پیشنهاد می‌شود: + +### فاز ۱: ممیزی و اکتشاف ترافیک شبکه (کمتر از ۱ روز کاری) +1. باز کردن `web.bale.ai` با تب Network و کلیک روی دکمه تبدیل ویس. +2. تعیین ماهیت درخواست: + - اگر درخواست یک **HTTP POST ساده** یا یک فریم متنی وب‌سوکت با Authorization Header باشد: + 👉 **روش دوم بلافاصله پیاده‌سازی می‌شود** و به بالاترین سرعت و بهره‌وری ممکن می‌رسیم. + - اگر پروتکل شامل امضاها و فریم‌های رمزگذاری‌شده پیچیده باشد: + 👉 به سراغ اجرای فاز ۲ (روش اول) می‌رویم. + +### فاز ۲: استقرار سرویس اتوماسیون (Fallback تضمین‌شده) +1. ساخت کانتینر داکر سبک `playwright-worker` در کنار کانتینر `procurement-bot`. +2. اتصال امن این کانتینر از طریق یک اندپوینت داخلی ساده: + `POST http://bale-stt-worker:8080/transcribe` +3. قرار گرفتن این سرویس به عنوان مدل سوم در متغیر محیطی: + `PROCUREMENT_BOT_STT_PROVIDER_3_BASE_URL=http://bale-stt-worker:8080/v1` + +--- +*تهیه شده جهت ارائه به تیم فنی و مدیران سیستم تدارکات هوشمند.* diff --git a/bale stt report/bale_stt_integration_report_2.md b/bale stt report/bale_stt_integration_report_2.md new file mode 100644 index 0000000..ae85cd8 --- /dev/null +++ b/bale stt report/bale_stt_integration_report_2.md @@ -0,0 +1,137 @@ +# گزارش فنی و امکان‌سنجی: ادغام قابلیت صوت به متن (STT) بله در سامانه تدارکات هوشمند + +> [!NOTE] +> **مخاطب گزارش:** تیم فنی، معماران سیستم و مدیران محصول +> **موضوع:** بررسی، مقایسه دو رویکرد فنی و نقشه راه پیاده‌سازی اتصال به موتور تبدیل ویس به متن پیام‌رسان «بله» +> **هدف نهایی:** افزودن بله به عنوان یکی از مدل‌های کاندید در خط لوله STT چندمدله (Multi-Provider STT) جهت افزایش دقت استخراج اقلام تدارکات + +--- + +## ۱. خلاصه مدیریتی (Executive Summary) + +سامانه هوشمند تدارکات در حال حاضر مجهز به موتور پردازش موازی صوت (**Multi-Provider STT**) است که قادر است فایل‌های صوتی دریافتی از کاربران را هم‌زمان به چندین ارائه‌دهنده (نظیر Whisper، Soniox یا سرویس‌های ابری) ارسال کرده و از طریق مدل تحلیلی DeepSeek یک خروجی یکپارچه و دقیق (**Consensus Text**) به همراه اقلام استعلام استخراج کند. + +برای افزایش هرچه بیشتر دقت در زبان فارسی و لهجه‌های محلی، استفاده از **موتور صوت‌به‌متن بومی پیام‌رسان بله** به عنوان یکی از مدل‌های کاندید مطرح شده است. از آن‌جا که پلتفرم توسعه‌دهندگان بله (Bale Bot API) متد مستقیمی برای فراخوانی API ترنسکرایب ارائه نمی‌دهد، دو روش اجرایی اصلی برای دستیابی به این موتور وجود دارد: +1. **روش اول (اتوماسیون رابط کاربری وب / Headless Browser):** شبیه‌سازی رفتار کاربر در مرورگر و کلیک روی دکمه تبدیل وویس. +2. **روش دوم (مهندسی معکوس پروتکل کلاینت / Direct Client API):** شبیه‌سازی درخواست‌های شبکه (WebSocket/HTTP) کلاینت بله بدون نیاز به مرورگر. + +--- + +## ۲. پس‌زمینه فنی زیرساخت بله (Technical Context) + +| بخش | وضعیت دسترسی | توضیحات | +| :--- | :--- | :--- | +| **Bale Bot API (رسمی)** | فاقد متد STT | فقط امکان دانلود فایل صوتی خام (`voice` / `audio`) با `file_id` را فراهم می‌کند. | +| **کلاینت‌های کاربری بله (Web / Android)** | دارای موتور فعال STT | روی هر ویس یک دکمه تبدیل صوت به متن وجود دارد که با فشرده شدن آن، صوت روی سرورهای بله به متن تبدیل می‌شود. | +| **دستیار هوش مصنوعی بله** | پشتیبانی از ورودی صوتی | کاربر با ارسال ویس به چت هوش مصنوعی بله، متن پیاده‌شده به همراه پاسخ تحلیلی را دریافت می‌کند. | + +**نتیجه:** برای بهره‌برداری از این قابلیت، بات تدارکات باید در لایه ارتباطی خود، یک نشست کاربری (User Session) در بله داشته باشد تا بتواند خدمات تبدیل صوت کلاینت را فراخوانی کند. + +--- + +## ۳. روش اول: اتوماسیون رابط کاربری وب (Headless Browser Scraping) + +در این رویکرد، یک مرورگر کنترل‌شده بدون واسط گرافیکی (مانند Chromium تحت فریم‌ورک **Playwright**) در یک کانتینر اختصاصی داکر مستقر شده و در نسخه وب بله (`web.bale.ai`) فعال نگه داشته می‌شود. + +### معماری جریان داده (Architecture Flow) +```mermaid +sequenceDiagram + autonumber + participant Bot as Procurement Bot + participant Worker as Browser Worker (Playwright) + participant BaleWeb as web.bale.ai (Chromium) + participant BaleServer as سرورهای پردازش صوت بله + + Bot->>Worker: ارسال فایل صوتی (voice.mp3) + Worker->>BaleWeb: بارگذاری فایل در چت کلاینت + BaleWeb->>BaleServer: ارسال صوت و درخواست تبدیل + BaleServer-->>BaleWeb: بازگرداندن متن پیاده‌شده به صفحه + Worker->>BaleWeb: استخراج متن از DOM (.transcription-text) + Worker-->>Bot: بازگرداندن متن خروجی به خط لوله STT +``` + +### مؤلفه‌های پیاده‌سازی: +1. **مدیریت نشست پایدار (Persistent Profile):** + - پوشه پروفایل مرورگر (`userDataDir`) بر روی یک داکر ولوم ماندگار (`/data/bale_browser_session`) قرار می‌گیرد. + - ورود اولیه فقط یک‌بار توسط تیم فنی با شماره تلفن اختصاصی و کد OTP پیامکی انجام می‌شود و کوکی‌ها و توکن‌های LocalStorage/IndexedDB برای همیشه حفظ می‌گردند. +2. **انتظار هوشمند بر اساس رخدادهای DOM:** + - استفاده از سلکتورهای مقاوم با `waitForSelector` یا `MutationObserver` روی باکس متن ترنسکرایب. +3. **مزایا و معایب:** + - 🟢 **تضمین تطابق با سرور:** چون مرورگر رسمی در حال اجرا است، هیچ‌گونه ریسک ناسازگاری پکت‌های شبکه وجود ندارد. + - 🟢 **پایداری در برابر تغییرات داخلی API:** حتی اگر بله فریم‌ورک پکت‌های وب‌سوکت خود را عوض کند، تا زمانی که دکمه در UI وجود دارد سیستم کار می‌کند. + - 🔴 **مصرف بالای منابع:** هر نمونه Chromium حدود ۳۰۰ تا ۵۰۰ مگابایت رم اشغال می‌کند. + - 🔴 **تاخیر زمانی (Latency):** فرآیند رندر صفحه، شبیه‌سازی آپلود و رویداد کلیک حدود ۳ تا ۶ ثانیه زمان می‌برد. + +--- + +## ۴. روش دوم: مهندسی معکوس شبکه و پروتکل کلاینت (Direct Client Protocol / gRPC-Web) + +در این روش، بدون استفاده از مرورگر سنگین، تلاش می‌شود تا کلاینت پایتونی مستقیماً همان درخواست‌های شبکه کلاینت رسمی بله را شبیه‌سازی و فراخوانی کند. + +### یافته‌های واقعی حاصل از بازرسی و تحلیل سورس‌کد لایو وب بله (`web.bale.ai`): +تیم ما اسکریپت‌های باندل تولیدی بله (`/static/js/index.4aefbadad3.js` و بسته‌های وابسته) را مستقیماً تحلیل کرد که نتایج فنی مهم زیر حاصل شد: +1. **پروتکل زیرساختی:** بله از APIهای متنی ساده (مانند REST یا JSON) استفاده **نمی‌کند**؛ بلکه کل ارتباطات کلاینت وب بر پایه **gRPC-Web همراه با Protocol Buffers دودویی (`@bufbuild/protobuf`)** پیاده‌سازی شده است. +2. **آدرس‌های Gateway سرورهای بله:** + - سرور gRPC پروداکشن: `https://maviz-ws.bale.ai` + - اتصال وب‌سوکت پروداکشن: `wss://maviz-ws.bale.ai/ws/?uid={user_id}` + - محیط‌های آزمایشی: `beta-ws.bale.ai` و `next-ws.bale.ai` +3. **شناسه‌ها و فیچرهای کشف‌شده در کد:** + - شناسه ماژول پیاده‌سازی صوت: `u.SpeechTranscript = "SpeechTranscript"` + - ماژول چت هوش مصنوعی: `u.AIChat = "AIChat"` با روت صفحه `/${L.AI_CHAT}` + - دامنه‌های مربوطه: `domain_chat_ai: "app.all.domain_chat_ai"` و `chat_ai_ttl` + +### ارزیابی فنی و چالش‌های روش دوم: +- **پیچیدگی بالای پیاده‌سازی:** به دلیل استفاده از پکت‌های فشرده دودویی Protobuf، برای فراخوانی مستقیم در پایتون باید فایل‌های تعاریف اینترفیس (`.proto`) بله به صورت کامل مهندسی معکوس و دیکود شوند و فریم‌های ۵ بایتی استاندارد gRPC-Web در کلاینت پایتون کدگذاری گردند. +- **ریسک شکنندگی در تغییر اسکیما:** با کوچک‌ترین تغییر در شماره فیلدهای Protobuf یا متدهای سرور بله در نسخه‌های جدید، کلاینت مهندسی معکوس‌شده از کار می‌افتد. + +--- + +## ۵. ماتریس مقایسه جامع فنی (Technical Decision Matrix) + +| معیار ارزیابی | روش اول: اتوماسیون وب (Playwright) | روش دوم: مهندسی معکوس پروتکل کلاینت | اهمیت برای سیستم | +| :--- | :---: | :---: | :---: | +| **تاخیر پاسخ‌دهی (Latency)** | ۳ تا ۶ ثانیه | **زیر ۱ ثانیه** ⚡️ | بسیار بالا (تاثیر مستقیم روی UX وویس) | +| **مصرف حافظه RAM** | ۳۵۰ الی ۵۰۰ مگابایت | **کمتر از ۱۵ مگابایت** 🪶 | متوسط به بالا | +| **هزینه پردازشی CPU** | متوسط تا بالا (رندرینگ) | بسیار پایین (درخواست شبکه) | متوسط | +| **پیچیدگی پیاده‌سازی اولیه** | کم (طی چند ساعت با سلکتور) | متوسط (نیازمند اسنیف و دیکود پکت) | موقت | +| **پایداری نشست کاربری (Session)** | ذخیره در پوشه کاربری مرورگر | ذخیره توکن در متغیر محیطی / دیتابیس | بالا | +| **ریسک تغییرات توسط بله** | کم (مقاوم در برابر تغییر پروتکل) | نیازمند پایش در تغییر نسخه‌های ماژور | متوسط | +| **قابلیت مقیاس‌پذیری (Scalability)** | محدود (محدودیت منابع مرورگر) | نامحدود (ایسنک و چندنخی) | بالا | + +--- + +## ۶. نحوه اتصال به پایپ‌لاین STT چندمدله فعلی + +سامانه تدارکات در حال حاضر قابلیت دریافت چند کاندیدا را دارد. با اتصال بله، خروجی به شکل زیر در پردازش قرار می‌گیرد: + +```mermaid +graph TD + Voice[وویس دریافتی کاربر از بله] --> FFMPEG[تبدیل یک‌باره به MP3] + FFMPEG --> S1[مدل ۱: Whisper-Large] + FFMPEG --> S2[مدل ۲: Soniox / OpenRouter] + FFMPEG --> S3[مدل ۳: موتور صوت هوش مصنوعی بله] + + S1 --> Aggregator[تجمیع نتایج کاندیداها] + S2 --> Aggregator + S3 --> Aggregator + + Aggregator --> DeepSeek[تحلیلگر هوشمند DeepSeek] + DeepSeek --> Final[متن اصلاح‌شده Consensus Text + استخراج اقلام استعلام JSON] +``` + +--- + +## ۷. نقشه راه پیشنهادی دو مرحله‌ای برای تیم توسعه (Recommended Roadmap) + +> [!TIP] +> **نتیجه تست و تحلیل تجربی ترافیک:** +> بر اساس آنالیز دقیق سورس‌کد لایو `web.bale.ai` که انجام شد، ماهیت درخواست‌های بله مشخص گردید: ارتباطات به صورت فریم‌های دودویی **Protobuf + gRPC-Web** است. بنابراین مهندسی معکوس پکت‌ها نیازمند استخراج و بازسازی مستمر فایل‌های `.proto` اختصاصی بله است که نگهداری آن را بسیار شکننده می‌کند. +> +> **نتیجه‌گیری و پیشنهاد نهایی:** +> مناسب‌ترین، مطمئن‌ترین و پایدارترین گزینه برای استقرار در محصول، **روش اول (اتوماسیون مرورگر با Playwright Worker)** است: +> ۱. مرورگر به صورت طبیعی از وب‌سوکت و پروتکل Protobuf بله پشتیبانی می‌کند و نیازی به رمزگشایی پکت‌ها نیست. +> ۲. با یک سشن پایدار در داکر، وویس در صفحه پیاده شده و متن خروجی به شکل مطمئن به خط لوله Multi-STT تحویل داده می‌شود. +> ۳. پیاده‌سازی آن در قالب یک کانتینر ایزوله (`bale-stt-worker`) با یک اندپوینت سبک `POST /transcribe` سریعاً انجام می‌پذیرد. + +--- +*تهیه شده جهت ارائه به تیم فنی و مدیران سیستم تدارکات هوشمند.* diff --git a/bale stt report/bale_stt_master_crawler_blueprint.md b/bale stt report/bale_stt_master_crawler_blueprint.md new file mode 100644 index 0000000..a50fe0c --- /dev/null +++ b/bale stt report/bale_stt_master_crawler_blueprint.md @@ -0,0 +1,499 @@ +# سند جامع، تحلیلی و عملیاتی کرالر تبدیل صوت‌به‌متن (STT) بله +> **نسخه:** ۳.۰ (جامع، تجمیعی و نهایی) +> **مخاطب اول:** مدیر پروژه (تصمیم‌گیری کلان، ماتریس‌های مقایسه‌ای، جریان‌های داده و فلوهای اجرایی) +> **مخاطب دوم:** ایجنت هوش مصنوعی مجری (شناخت پروژه، معماری فنی، ساختار پروتکل، کدهای مرجع و چک‌لیست راه‌اندازی) +> **وضعیت نیازمندی‌ها:** محدودیت حریم خصوصی موقتاً برداشته شده؛ صحت و کیفیت بالای موتور بله در استعلام‌های تدارکاتی توسط کاربر اثبات شده؛ وجود دکمه تبدیل متن در وب (`web.bale.ai`) تأیید شده؛ رویکرد Playwright به عنوان **فالبک آخر (Last Resort)** و رویکرد RPC به عنوان **اولویت اول** تعیین شده است؛ هیچ تخمین زمانی تقویمی وجود ندارد چون تمامی پیاده‌سازی‌ها توسط ایجنت انجام می‌شود. + +--- + +# بخش اول: برای مدیر پروژه (دید کلان، مقایسه‌ها و فلوهای تصویری) + +## ۱. صورت‌مسئله و دلایل توجیهی پروژه + +در سامانه استعلام هوشمند تدارکات، کاربران پیام‌های صوتی (ویس) حاوی درخواست‌های پیچیده خرید (شامل نام کالا، برند، مشخصات فنی، مقادیر، واحدها و اصطلاحات بازار) ارسال می‌کنند. +بات تدارکات برای رونویسی این پیام‌ها از یک **خط‌لوله چندمدله (Multi-Provider STT Pipeline)** استفاده می‌کند؛ به این معنا که ویس همزمان به چند سرویس تبدیل صوت ارسال شده و متن‌های دریافت‌شده در اختیار یک مدل تحلیلی هوش مصنوعی (DeepSeek NLU) قرار می‌گیرد تا با ایجاد اجماع (Consensus)، خطاهای واژگانی برطرف و ساختار استاندارد استعلام استخراج گردد. + +### چرا کرال کردن موتور بله ارزش بالایی دارد؟ +1. **کیفیت اثبات‌شده در آزمون واقعی:** تست‌های تجربی روی ویس‌های واقعی استعلام تدارکات نشان داد که موتور تشخیص گفتار بله تسلط بسیار بالایی بر محاورات عامیانه فارسی، اصطلاحات بازاری و لهجه‌ها دارد. +2. **فقدان متد در رابط رسمی (Bot API):** در مستندات رسمی بله (`docs.bale.ai`)، شیء `Voice` ارسالی به ربات‌ها فاقد فیلد ترنسکریپت بوده و تنها مشخصاتی نظیر `file_id` را برمی‌گرداند؛ بنابراین دسترسی به این موتور صرفاً از طریق شبیه‌سازی نشست کاربری امکان‌پذیر است. +3. **تأیید وجود دکمه در وب بله:** بررسی محیط `web.bale.ai` اثبات کرد که قابلیت تبدیل ویس به متن در کلاینت تحت وب بله فعال است؛ این یافته امکان کپچر ترافیک و مهندسی معکوس و نیز استفاده از وب به عنوان پشتیبان را تضمین می‌کند. +4. **دستور مدیریت پروژه در چینش اولویت‌ها:** + * **اولویت اول:** ارتباط مستقیم در لایه شبکه و پروتکل کاربری (RPC / WebSocket) به دلیل مصرف ناچیز منابع (کمتر از ۲۰ مگابایت رم) و سرعت استخراج بالا. + * **فالبک آخر (Last Resort):** کرال کردن بصری صفحه چت با Playwright صرفاً در شرایطی که ارتباط شبکه‌ای به بن‌بست برسد. + +--- + +## ۲. ماتریس مقایسه‌ای جامع ۸ راهکار موجود + +برای ایجاد دید همه‌جانبه، تمامی راهکارهای مطرح در اکوسیستم فنی در جدول زیر کالبدشکافی شده‌اند (بدون ستون‌های زائد زمانی؛ با تمرکز بر رفتار سیستمی): + +| # | راهکار فنی | نحوه عملکرد | مصرف حافظه RAM | خلوص متن خروجی | پایداری در برابر تغییرات | ریسک عملیاتی | اولویت در پروژه ما | +| :---: | :--- | :--- | :---: | :---: | :---: | :---: | :---: | +| **۱** | **کلاینت شبکه و RPC بله (Web RPC / WS)** | فراخوانی مستقیم متد کشف‌شده `GetTranscript` در کلاینت کاربری پایتون | کمتر از ۲۰ مگابایت | متن خالص گفتار | متوسط (وابسته به ثبات متد) | احتمال نیاز به تمدید نشست (OTP) | ⭐⭐⭐ **اولویت اول و اصلی** | +| **۲** | **اتوماسیون Playwright در `web.bale.ai`** | مرورگر بدون سربرگ (Headless)، کلیک روی دکمه و خواندن متن از DOM | ۳۵۰ تا ۵۰۰ مگابایت | متن خالص گفتار | بالا (مقاوم به پکت‌های شبکه) | مصرف رم و سربار رندر المان‌ها | ⚠️ **فالبک نهایی (Last Resort)** | +| **۳** | **وب‌سرویس‌های تجاری ایرانی (فارس‌آوا / آوانگار)** | اتصال رسمی به API شرکت‌های پردازش صوت بومی (عامراندیش، پارت‌آی و...) | ناچیز (سمت سرور ثالث) | متن خالص گفتار | بسیار بالا (SLA تجاری) | ایجاد هزینه اشتراک ریالی برای دقایق صوت | ⭐⭐ **پیشنهاد به عنوان کاندید موازی** | +| **۴** | **مدل متن‌باز خودمیزبان (Faster-Whisper)** | استقرار مدل `whisper-v3-turbo` با واژگان تدارکات روی سرور داخلی | ۱ تا ۳ گیگابایت | متن خالص گفتار | پایدار مطلق (بدون وابستگی) | نیاز به پردازنده گرافیکی (GPU) مناسب | ⭐⭐ **پرووایدر پشتیبان و مستقل** | +| **۵** | **وب‌سرویس‌های واسط غیررسمی (MajidAPI و...)** | ارسال لینک ویس به پراکسی‌های عمومی رایگان | ناچیز (سمت سرور ثالث) | وابسته به موتور واسط | بسیار پایین و ناپایدار | فقدان SLA، احتمال قطعی ناگهانی و نشت صوت | ❌ **رد شده (غیرقابل اتکا)** | +| **۶** | **چت هوش مصنوعی بله (اوراکل)** | ارسال ویس به ربات رسمی هوش مصنوعی درون بله | کمتر از ۲۰ مگابایت | **ناخالص** (متن + تحلیل LLM) | بسیار پایین | پاسخ تفسیری، تاخیر ۵ تا ۱۵ ثانیه‌ای | ❌ **رد شده (تخریب اجماع)** | +| **۷** | **شبیه‌ساز اندروید (Appium / UIAutomator)** | اجرای اپلیکیشن اندروید بله روی ماشین مجازی و اسکرپ متن | بیش از ۲ گیگابایت | متن خالص گفتار | شکننده | سربار بسیار سنگین CPU/RAM سرور | ❌ **رد شده** | +| **۸** | **سرویس‌های ابری خارجی (OpenAI / Chirp 2)** | ارسال فایل به API رسمی Whisper یا Google Chirp | ناچیز (سمت سرور ثالث) | متن خالص گفتار | بسیار بالا | وابستگی به ارز و ریسک تحریم/اختلال شبکه | ⭐ **پرووایدر کمکی در صورت دسترسی** | + +--- + +## ۳. فلوهای جامع تصویری (Architecture & Sequence Flows) + +### فلو ۱: جریان کلان پردازش ویس در سیستم تدارکات (End-to-End System Flow) +این دیاگرام نشان می‌دهد ویس ارسالی کاربر چگونه در خط‌لوله موازی پردازش شده و کاندید بله بدون ایجاد وقفه در سیستم ثبت استعلام می‌نشیند: + +```mermaid +sequenceDiagram + autonumber + actor User as کاربر تدارکات + participant MainBot as بات اصلی تدارکات (Bale Bot API) + participant STTPipeline as موتور چندمدله (bot/app/stt.py) + participant BaleAdapter as میکروسرویس آداپتور بله (bale-stt:8080) + participant OtherProviders as سایر پرووایدرها (OpenAI / محلی) + participant ConsensusNLU as مدل استخراج هوشمند (DeepSeek NLU) + + User->>MainBot: ارسال پیام صوتی استعلام خرید + MainBot->>STTPipeline: دریافت ویس و تبدیل فرمت به MP3 (16kHz Mono) + + par استعلام موازی از پرووایدرهای فعال + STTPipeline->>BaleAdapter: POST /v1/audio/transcriptions (بایت‌های ویس) + STTPipeline->>OtherProviders: POST /v1/audio/transcriptions + end + + critical پردازش در کانتینر ایزوله کرالر بله + BaleAdapter->>BaleAdapter: کنترل صف ترتیبی و اعمال تاخیر رفتاری (Jitter) + BaleAdapter->>BaleAdapter: ارسال صوت به اکانت کرالر و فراخوانی متد GetTranscript + BaleAdapter-->>BaleAdapter: دریافت متن پیاده‌شده و حذف پیام اسکرچ + end + + BaleAdapter-->>STTPipeline: بازگشت متن کاندید بله {"text": "..."} + OtherProviders-->>STTPipeline: بازگشت متن سایر کاندیدها + + STTPipeline->>ConsensusNLU: ارسال تمام کاندیدها به مدل اجماع‌گیر + ConsensusNLU-->>MainBot: استخراج اقلام کالا، برند، مشخصات و تعداد + MainBot-->>User: ارسال پیش‌فاکتور استعلام یا تاییدیه به کاربر +``` + +--- + +### فلو ۲: جریان رله صوتی در کرالر (Relay Bot Pattern Flow) +برای جلوگیری از فرآیند پرریسک و پیچیده «آپلود مستقیم فایل از اکانت شخصی»، از الگوی **بات رله** استفاده می‌شود: + +```mermaid +sequenceDiagram + autonumber + participant Adapter as هسته آداپتور FastAPI + participant RelayBot as بات رله اختصاصی (Bale Bot API) + participant BaleServer as سرورهای مرکزی بله + participant CrawlerUser as اکانت کاربری کرالر (WebSocket Client) + + Adapter->>Adapter: دریافت فایل صوتی از خط‌لوله بات + Adapter->>RelayBot: فراخوانی sendVoice(chat_id=کرالر, voice=audio, caption=nonce) + RelayBot->>BaleServer: ارسال ویس با مجوز رسمی بات + BaleServer-->>CrawlerUser: دریافت رویداد پیام صوتی جدید (دارای Media و MessageId و OutPeer) + + CrawlerUser->>BaleServer: فراخوانی مستقیم RPC: GetTranscript(Media, OutPeer, MessageId) + BaleServer-->>CrawlerUser: بازگشت پاسخ (mustWait / downloadSource) + + opt در صورت فعال بودن mustWait + CrawlerUser->>CrawlerUser: پولینگ سبک یا انتظار دریافت رویداد تکمیلی + end + + CrawlerUser->>BaleServer: فراخوانی DeleteMessage (حذف پیام برای حفظ بهداشت اکانت) + CrawlerUser-->>Adapter: تحویل متن خالص استخراج‌شده +``` + +--- + +### فلو ۳: فلو تاب‌آوری و عدم توقف بات (Fail-Safe & Graceful Degradation) +این فلو تضمین می‌کند که حتی در بدترین سناریوها (تغییر پروتکل بله، بن شدن اکانت یا قطعی سرور بله)، سیستم اصلی تدارکات دچار حتی یک ثانیه قطعی نخواهد شد: + +```mermaid +flowchart TD + Start["کاربر ویس می‌فرستد"] --> Multi["اجرای همزمان transcribe_multi() در stt.py"] + + Multi --> CallBale["ارسال درخواست به آداپتور بله"] + Multi --> CallOthers["ارسال درخواست به سایر پرووایدرها"] + + CallBale --> CheckBale{"آیا خطایی رخ داد؟ (بن/تغییر پروتکل/تایم‌اوت)"} + + CheckBale -- "بله" --> Resp503["آداپتور کد HTTP 503 برمی‌گرداند"] + Resp503 --> LogWarn["تابع _call_single_stt هشدار ثبت کرده و None بازمی‌گرداند"] + LogWarn --> IgnoreBale["کاندید بله بدون اختلال از لیست اجماع حذف می‌شود"] + + CheckBale -- "خیر" --> Resp200["آداپتور متن رونویسی‌شده را تحویل می‌دهد"] + Resp200 --> AddBale["متن بله به لیست کاندیدها اضافه می‌شود"] + + CallOthers --> RespOthers["متن‌های سایر پرووایدرها دریافت می‌شوند"] + + IgnoreBale --> NLU["اجماع NLU روی سایر متن‌های موجود انجام می‌شود"] + AddBale --> NLU + RespOthers --> NLU + + NLU --> Done["استعلام با موفقیت ثبت و پاسخ به کاربر داده می‌شود"] + + subgraph Rollback["عملیات قطع دائمی (در صورت لزوم)"] + Env["خالی کردن متغیرهای PROCUREMENT_BOT_STT_PROVIDER_3_*"] + ZeroDowntime["سیستم بدون ری‌استارت یا دیپلوی مجدد به کار عادی ادامه می‌دهد"] + end +``` + +--- + +### فلو ۴: سلسله‌مراتب گزینش موتور در آداپتور (Engine Fallback Hierarchy) + +```mermaid +graph TD + StartEngine["درخواست STT وارد آداپتور می‌شود"] --> ModeCheck{"بررسی تنظیم متغیر BALE_STT_ENGINE"} + + ModeCheck -- "rpc (پیش‌فرض)" --> TryRPC["اجرای موتور E1: متد باینری GetTranscript"] + TryRPC --> CheckRPC{"آیا رونویسی با موفقیت انجام شد؟"} + + CheckRPC -- "بله" --> ReturnText["بازگرداندن متن (HTTP 200)"] + CheckRPC -- "شکست در پروتکل شبکه" --> CheckFallback{"آیا فالبک خودکار مجاز است؟"} + + CheckFallback -- "بله" --> TryPW["سوئیچ به موتور E2: اتوماسیون Playwright (فالبک آخر)"] + TryPW --> CheckPW{"آیا متن از وب استخراج شد؟"} + CheckPW -- "بله" --> ReturnText + CheckPW -- "خیر" --> Fail503["بازگرداندن خطای HTTP 503 (Fail-Soft)"] + + CheckFallback -- "خیر" --> Fail503 + ModeCheck -- "playwright" --> TryPW +``` + +--- + +# بخش دوم: برای ایجنت هوش مصنوعی مجری (Technical Execution Specification) + +## ۱. شناخت ساختار کدهای موجود در پروژه + +ایجنت باید بداند بات تدارکات فعلی در فایل‌های زیر پیاده‌سازی شده و قراردادهای آن به چه صورت است: + +### ۱.۱ فایل [bot/app/stt.py](file:///c:/Users/Reza/Desktop/v1.5/bot/app/stt.py) +* **قرارداد ارتباطی:** تابع `_call_single_stt` ریکوئست `POST` به اندپوینت `{base_url}/audio/transcriptions` با هدر `Authorization: Bearer {api_key}` ارسال می‌کند. +* **فایل ارسالی:** فرم چندبخشی (Multipart) شامل کلید `file` با باینری صوتی MP3 (16kHz Mono)، `model` و `language="fa"`. +* **مدیریت پاسخ:** + * در صورت دریافت کد `200` و وجود کلید `text` غیرخالی، مقدار به عنوان کاندید معتبر ثبت می‌شود. + * در صورت دریافت هر کد خطایی (مانند `503`) یا بروز استثنا، صرفاً لاگ هشدار ثبت شده و مقدار `None` بازمی‌گردد. +* **تابع `transcribe_multi`:** تمام تسک‌ها را با `asyncio.gather(..., return_exceptions=True)` اجرا می‌کند؛ یعنی شکست یک پرووایدر هرگز کل فرآیند را متوقف نمی‌کند. + +### ۱.۲ فایل [bot/app/config.py](file:///c:/Users/Reza/Desktop/v1.5/bot/app/config.py) +* متغیرهای محیطی با پیشوند `PROCUREMENT_BOT_STT_PROVIDER_N_*` (که N اعداد ۱ تا ۳ است) خوانده می‌شوند. +* یک پرووایدر تنها زمانی فعال می‌شود که **هر دو فیلد `BASE_URL` و `API_KEY` مقداردهی شده باشند**. بنابراین با خالی کردن این دو فیلد، پرووایدر بله فوراً از مدار خارج می‌شود (Rollback آنی). + +--- + +## ۲. مشخصات قطعی کشف‌شده از پروتکل بله + +در بررسی و راستی‌آزمایی مخازن PyPI روی پکیج `bale-sdk` (نسخه 0.1.1)، متد و ساختار فیلدهای ترنسکریپشن دقیقاً استخراج شده است: + +* **فضای نام و سرویس:** `bale.turing.v1.AI` +* **متد باینری:** `GetTranscript` +* **اسکیمای درخواست (`GetTranscriptRequest`):** + ```protobuf + message GetTranscriptRequest { + Media voice = 1; // آبجکت فایل صدا (شناسه و خصوصیات باینری) + OutPeer outPeer = 2; // مقصد چت (پیر چت شخصی یا بات رله) + MessageId messageId = 3;// شناسه پیام ثبت‌شده در چت + } + ``` +* **اسکیمای پاسخ (`GetTranscriptResponse`):** + ```protobuf + message GetTranscriptResponse { + bool mustWait = 1; // فلگ بولی نشان‌دهنده در صف بودن پردازش سرور + DownloadSource downloadSource = 2; // منبع یا لینک بافر متنی خروجی + } + ``` + +--- + +## ۳. ارزیابی کتابخانه‌های پایتون و استراتژی Clean-Room + +| نام پکیج | نسخه در PyPI | مجوز حقوقی | وضعیت استفاده فنی در پروژه ما | +| :--- | :---: | :---: | :--- | +| **`bale-sdk`** | 0.1.1 | اختصاصی (All Rights Reserved) | **استفاده به عنوان مرجع پروتکل؛** به دلیل مجوز اختصاصی، کدهای آن کپی مستقیم نمی‌شوند بلکه ساختار پیام‌ها بر اساس کپچر بازسازی می‌گردد. | +| **`autobale`** | 1.0.0 | **MIT** (متن‌باز) | **مناسب برای هسته ارتباطی؛** فورک فعال و بازنویسی‌شده از `aiobale` که بر بستر WebSocket و Dispatcher ناهمگام کار می‌کند. | +| **`aiobale-py`** | 0.3.9 | عمومی | دارای لایه وب‌سوکت و هندلرهای باینری مبتنی بر `blackboxprotobuf` (`bbpb`). | +| **`bbpb` / `blackboxprotobuf`** | آخرین نسخه | MIT | ابزار دیکود و انکود فریم‌های باینری پروتوباف بدون نیاز به کامپایل فایلهای `.proto`. | + +--- + +## ۴. ورودی‌های انسانی مورد نیاز ایجنت (Agent Inputs) + +ایجنت قبل از راه‌اندازی، این ۵ مورد را از انسان دریافت می‌کند: +1. `BALE_PHONE`: شماره سیم‌کارت شرکتی اختصاصی جهت ورود به اکانت کاربری. +2. `BALE_OTP_CODE`: کد پیامکی ۶ رقمی یک‌بارمصرف در زمان اجرای اسکریپت ورود اولیه. +3. `BALE_RELAY_BOT_TOKEN`: توکن بات رله اختصاصی که از BotFather بله دریافت شده است. +4. `BALE_STT_INTERNAL_KEY`: کلید امنیتی تصادفی برای هدر `Authorization: Bearer `. +5. `CRAWLER_USER_ID`: شناسه عددی کاربری اکانت کرالر (جهت ارسال پیام توسط بات رله). + +--- + +## ۵. کدهای مرجع میکروسرویس آداپتور (`bale-stt`) + +### ۵.۱ ساختار پروژه ایجنت +``` +bale-stt/ +├── adapter/ +│ ├── main.py # میکروسرویس FastAPI سازگار با قرارداد OpenAI +│ ├── counters.py # مدیریت سقف روزانه با دیتابیس پایدار SQLite +│ ├── config.py # تنظیمات محیطی بدون مقدار پیش‌فرض خطرناک +│ ├── engines/ +│ │ ├── base.py # اینترفیس استاندارد موتورها +│ │ ├── rpc.py # موتور اولویت ۱: ارتباط RPC و الگوی بات رله +│ │ └── playwright.py # موتور فالبک آخر: اتوماسیون صفحه وب بله +│ ├── Dockerfile +│ └── requirements.txt +├── docker-compose.stt.yml +└── .env.example +``` + +### ۵.۲ مدیریت سقف روزانه (`adapter/counters.py`) +```python +import os +import sqlite3 +from contextlib import closing +from datetime import date + +DB_PATH = os.getenv("BALE_STT_DB", "/app/state/usage.db") + +def _init_db() -> 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, count INTEGER NOT NULL)") + return con + +def get_today_count() -> int: + with closing(_init_db()) as con: + row = con.execute("SELECT count FROM usage WHERE day = ?", (date.today().isoformat(),)).fetchone() + return row[0] if row else 0 + +def increment_today_count() -> None: + with closing(_init_db()) as con, con: + con.execute( + "INSERT INTO usage(day, count) VALUES (?, 1) " + "ON CONFLICT(day) DO UPDATE SET count = count + 1", + (date.today().isoformat(),), + ) +``` + +### ۵.۳ کد اصلی آداپتور (`adapter/main.py`) +```python +import os +import secrets +import asyncio +import logging +from fastapi import FastAPI, File, Form, Header, HTTPException, UploadFile + +from counters import get_today_count, increment_today_count +from engines.base import get_engine + +logging.basicConfig(level=logging.INFO) +log = logging.getLogger("bale-stt-adapter") + +app = FastAPI(title="Bale STT OpenAI Adapter") + +INTERNAL_KEY = os.environ["BALE_STT_INTERNAL_KEY"] +DAILY_CAP = int(os.getenv("BALE_STT_DAILY_CAP", "200")) +TIMEOUT_SECONDS = float(os.getenv("BALE_STT_TIMEOUT", "25.0")) + +# قفل همزمانی برای پردازش تک‌اکانت کاربری +account_lock = asyncio.Lock() +engine = get_engine() + +@app.get("/healthz") +def health_check(): + return { + "status": "ok", + "engine": engine.name, + "used_today": get_today_count(), + "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("") +): + # ۱. اعتبارسنجی امن کلید احراز هویت (مقاوم به Timing Attack) + expected_header = f"Bearer {INTERNAL_KEY}" + if not secrets.compare_digest(authorization.encode(), expected_header.encode()): + raise HTTPException(status_code=401, detail="Unauthorized") + + # ۲. کنترل سقف مصرف مجاز روزانه + if get_today_count() >= DAILY_CAP: + log.warning("Daily STT capacity reached (%d)", DAILY_CAP) + raise HTTPException(status_code=429, detail="Daily quota exhausted") + + audio_bytes = await file.read() + if not audio_bytes: + raise HTTPException(status_code=400, detail="Empty audio payload") + + # ۳. ورود به صف ترتیبی با فاصله زمانی تصادفی انسانی + async with account_lock: + try: + # اعمال Jitter بین ۱ تا ۲.۵ ثانیه + await asyncio.sleep(1.0 + (secrets.randbelow(15) / 10.0)) + + # فراخوانی موتور فعال با سقف زمانی مجاز + text = await asyncio.wait_for( + engine.transcribe(audio_bytes, filename=file.filename or "voice.mp3"), + timeout=TIMEOUT_SECONDS + ) + + cleaned_text = (text or "").strip() + if not cleaned_text: + raise RuntimeError("Received empty transcript from Bale") + + increment_today_count() + log.info("Transcription successful (length=%d)", len(cleaned_text)) + return {"text": cleaned_text} + + except Exception as exc: + log.exception("Transcription failed in engine %s: %s", engine.name, exc) + # پرتاب 503 حیاتی است: باعث می‌شود bot/app/stt.py کاندید را نادیده بگیرد + raise HTTPException(status_code=503, detail="Bale STT Service Temporarily Unavailable") +``` + +### ۵.۴ موتور RPC با الگوی بات رله (`adapter/engines/rpc.py`) +```python +import os +import uuid +import asyncio +import httpx +from engines.base import BaseEngine + +class RpcRelayEngine(BaseEngine): + name = "rpc" + + def __init__(self): + self.bot_token = os.environ["BALE_RELAY_BOT_TOKEN"] + self.crawler_user_id = int(os.environ["BALE_CRAWLER_USER_ID"]) + self.api_url = f"https://tapi.bale.ai/bot{self.bot_token}" + + async def transcribe(self, audio: bytes, filename: str) -> str: + nonce = str(uuid.uuid4())[:8] + + # ۱. ارسال ویس با Bot API رسمی به چت اکانت کرالر + async with httpx.AsyncClient(timeout=15.0) as client: + resp = await client.post( + f"{self.api_url}/sendVoice", + data={"chat_id": self.crawler_user_id, "caption": nonce}, + files={"voice": ("voice.ogg", audio, "audio/ogg")} + ) + if resp.status_code != 200: + raise RuntimeError(f"Relay bot sendVoice failed: {resp.text}") + + sent_message_id = resp.json()["result"]["message_id"] + + try: + # ۲. دریافت رویداد پیام در کلاینت کاربری و فراخوانی GetTranscript + from driver.bale_client import get_client + bale_client = await get_client() + + transcript_text = await bale_client.call_get_transcript( + message_id=sent_message_id, + nonce=nonce, + timeout=18.0 + ) + return transcript_text + + finally: + # ۳. پاکسازی بهداشتی: حذف پیام از چت + async with httpx.AsyncClient(timeout=5.0) as client: + await client.post( + f"{self.api_url}/deleteMessage", + json={"chat_id": self.crawler_user_id, "message_id": sent_message_id} + ) +``` + +--- + +## ۶. تنظیمات داکر و یکپارچه‌سازی با بات اصلی + +### ۶.۱ فایل `adapter/Dockerfile` +```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"] +``` + +### ۶.۲ فایل `docker-compose.stt.yml` +```yaml +services: + bale-stt: + build: + context: ./adapter + dockerfile: Dockerfile + container_name: bale-stt-adapter + restart: unless-stopped + environment: + - TZ=Asia/Tehran + - BALE_STT_ENGINE=rpc + - BALE_STT_INTERNAL_KEY=${BALE_STT_INTERNAL_KEY} + - BALE_STT_DAILY_CAP=200 + - BALE_RELAY_BOT_TOKEN=${BALE_RELAY_BOT_TOKEN} + - BALE_CRAWLER_USER_ID=${BALE_CRAWLER_USER_ID} + volumes: + - bale_session_data:/app/session + - bale_usage_data:/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_data: + bale_usage_data: + +networks: + bot_network: + external: true +``` + +### ۶.۳ تنظیمات متغیرهای محیطی در فایل `.env` بات اصلی +اتصال به بات اصلی **بدون دستکاری حتی ۱ خط از کدهای پایتون بات** انجام می‌پذیرد: +```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 +``` + +--- + +## ۷. چک‌لیست گام‌به‌گام پیاده‌سازی خودکار توسط ایجنت + +ایجنت مجری برای به سرانجام رساندن پروژه، این چک‌لیست عملیاتی را بدون معطلی و به ترتیب اجرا می‌کند: + +* [ ] **گام ۱ (لاگین کاربری و ذخیره سشن):** اجرای اسکریپت CLI برای ثبت شماره `BALE_PHONE`، دریافت کد `BALE_OTP_CODE` از انسان، صدور توکن پایدار JWT و ذخیره در مسیر `/app/session/session.json`. +* [ ] **گام ۲ (تست اتصال بات رله):** تست ارسال یک صوت آزمایشی با `sendVoice` از بات رله به `BALE_CRAWLER_USER_ID` و اطمینان از دریافت وب‌سوکت در کلاینت کرالر. +* [ ] **گام ۳ (اعتبارسنجی فراخوانی `GetTranscript`):** پاس دادن `Media` و `MessageId` به متد باینری و دریافت رشته متن فارسی. +* [ ] **گام ۴ (بیلد کانتینر آداپتور FastAPI):** بیلد ایمیج داکر و اجرای محلی روی پورت 8080. +* [ ] **گام ۵ (تست تطابق با قرارداد OpenAI):** ارسال ریکوئست تستی با `curl` به `/v1/audio/transcriptions` و اعتبارسنجی پاسخ `{"text": "..."}`. +* [ ] **گام ۶ (تست سناریوی خطا):** قطع عمدی اتصال بله و حصول اطمینان از بازگشت کد `HTTP 503` (جهت تضمین عدم توقف بات اصلی). +* [ ] **گام ۷ (فعال‌سازی در بات تدارکات):** قرار دادن مقادیر در `.env` بات اصلی. +* [ ] **گام ۸ (تست سراسری E2E):** ارسال پیام صوتی استعلام قیمت در پیام‌رسان بله و مشاهده لاگ‌های `bot.stt` مبنی بر رونویسی موفق کاندید بله و اجماع در مدل DeepSeek NLU. diff --git a/bale stt report/bale_stt_opensource_thirdparty_assessment (1).md b/bale stt report/bale_stt_opensource_thirdparty_assessment (1).md new file mode 100644 index 0000000..f17025f --- /dev/null +++ b/bale stt report/bale_stt_opensource_thirdparty_assessment (1).md @@ -0,0 +1,1070 @@ +# گزارش جامع و دستورکار اجرایی: کرالر 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..(...)` است و نام 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. گراف وظایف (قابل‌خواندن توسط ماشین) + +```yaml +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 جریان کلی + +```mermaid +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//.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` (شمارنده روزانه پایدار) + +```python +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` + +```python +# engines/base.py +from typing import Protocol + + +class Engine(Protocol): + name: str + + async def transcribe(self, audio: bytes, filename: str) -> str: + """متن فارسی را برگرداند؛ در هر مشکل Exception بدهد.""" +``` + +```python +# 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، سخت‌شده) + +```python +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` (**شبه‌کد**، منطق مورد نیاز) + +```python +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` + +```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` + +```yaml +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` بات (بدون تغییر کد) + +```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 گزارش دروازه (برای انسان) + +```markdown +## گزارش دروازه Gx — <تاریخ> +**نتیجه:** عبور / رد / نیازمند تصمیم شما +**آنچه اجرا شد:** (وظایف، دستورها، فایل‌های خروجی) +**یافته‌ها:** (جدول؛ هر ردیف با ✅/🟡/❌ و مدرک) +**اعداد:** (CER/WER/F1/تأخیر/موفقیت/هزینه) +**انحراف از سند:** (چه چیزی با فرض‌های سند نخواند) +**ریسک‌های تازه:** +**پیشنهاد گام بعد:** +**چیزی که از شما لازم دارم:** (H#) +``` + +### B9.2 درخواست از انسان (کوتاه، فوری، قابل‌اقدام) + +```markdown +⏸️ **نیاز به اقدام شما — 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، حدود ۳۰ دقیقه) + +```bash +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 +``` + +در پایتون (در محیط جدا): + +```python +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) +``` + +مجوز هر پکیج: + +```bash +pip download --no-deps && unzip -p '*.dist-info/METADATA' | grep -iE "^(License|Classifier: License)" +``` + +## D2. کپچر ترافیک وب (T4/T5) + +```python +# 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"", 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 کن؛ نام رویدادها بین نسخه‌ها فرق می‌کند، پس مستندات نصب‌شده را بخوان. + +رمزگشایی اولیه: + +```bash +# فریم باینری بدون پیشوند را مستقیم، یا با برداشتن ۵ بایت ابتدای فریم gRPC-Web +protoc --decode_raw < captures/.bin +# یا: pip install bbpb و استفاده از decode_message +``` + +## D3. دستور شروع برای ایجنت (قابل کپی) + +```text +نقش: تو ایجنت مجری پروژه «کرالر 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. منابع + +- مستندات رسمی بات بله: https://docs.bale.ai/ +- `bale-sdk` (PyPI): https://pypi.org/project/bale-sdk/ و https://pypi.org/project/bale-sdk/0.1.1/ +- `aiobale` (PyPI): https://pypi.org/project/aiobale ، مستندات: https://docs.aiobale.ir +- `autobale` (PyPI): https://pypi.org/project/autobale/ +- `BaleClient` (PyPI): https://pypi.org/project/BaleClient/ +- `baleself` (Selenium): https://pypi.org/project/baleself/ +- `bale-mcp` (PyPI): https://pypi.org/project/bale-mcp/ +- بات هوش مصنوعی بله روی Cloudflare Workers: https://github.com/hamedcode/bale-ai-bot +- BaleVPN: https://github.com/2077DevWave/BaleVPN +- Blackbox Protobuf (NCC Group): https://www.github.com/nccgroup/blackboxprotobuf (پکیج رسمی PyPI: `bbpb`) +- PSRB (Persian ASR benchmark): https://arxiv.org/pdf/2505.21230 و دادگان: https://huggingface.co/datasets/PartAI/PSRB +- مدل فارسی Whisper (نمونه): https://huggingface.co/vhdm/whisper-large-fa-v1 +- خبر انتشار نسخه بله با تبدیل ویس به متن (آنا): https://ana.ir/fa/news/994908 diff --git a/bale stt report/bale_stt_opensource_thirdparty_assessment.md b/bale stt report/bale_stt_opensource_thirdparty_assessment.md new file mode 100644 index 0000000..9a474a4 --- /dev/null +++ b/bale stt report/bale_stt_opensource_thirdparty_assessment.md @@ -0,0 +1,432 @@ +# گزارش جامع: ارزیابی پروژه‌های متن‌باز و سرویس‌های شخص ثالث، و ترکیب بهینه برای کرالر STT بله + +> **تاریخ:** ۱۶ مهر ۱۴۰۵ (8 Oct 2026) +> **ورودی‌ها:** دو گزارش مدیر پروژه +> - **گزارش A:** «ارزیابی تحلیلی راهکارهای استخراج صوت‌به‌متن در بله: سرویس‌های شخص ثالث، کتابخانه‌ها، پروژه‌های متن‌باز و جایگزین‌ها» +> - **گزارش B:** `bale_stt_executive_roadmap_report3.md` (سند عملیاتی برای مدیر پروژه و ایجنت مجری) +> +> **دامنه (طبق درخواست):** حریم خصوصی و امنیت در این گزارش بررسی نشده و بعداً تصمیم‌گیری می‌شود. فقط فهرست کامل راهکارها، ارزیابی، الگوبرداری از متن‌باز و بهترین ترکیب ارائه شده است. +> +> **روش کار:** هر دو گزارش را خواندم و ادعاهای قابل راستی‌آزمایی را با منابع عمومی (PyPI، مستندات رسمی بله، arXiv، Hugging Face، GitHub) تطبیق دادم. هر ادعا یکی از سه وضعیت دارد: +> ✅ **تأیید شد** | 🟡 **تأییدنشده / فقط استنباط** | ❌ **با شواهد در تناقض است** +> +> **محدودیت مهم:** شبکه sandbox من بسته است؛ پس نتوانستم پکیج‌ها را نصب کنم یا پروتوها را بخوانم. هر جا بگویم «تأییدنشده»، یعنی در منابع عمومی قابل مشاهده برای من نبود، نه لزوماً اینکه غلط باشد. + +--- + +## ۱. خلاصه اجرایی + +### پاسخ کوتاه به چهار سؤال شما + +| سؤال | پاسخ کوتاه | +|---|---| +| **نظر درباره گزارش A (متن‌باز و شخص ثالث)** | تحلیل خوبی است و دسته‌بندی ۸ راهکار درست است. ولی چند ادعای مهم آن تأییدنشده یا غلط است: نام و وضعیت پکیج `aiobale-py`، وجود متد `GetTranscript` (فقط ادعا شده، من نتوانستم ببینم)، و برتری «مدل متن‌باز» بر سرویس‌های تجاری. داده‌های مستقل خلاف این برتری را نشان می‌دهد (بخش ۶). | +| **آیا دیگران همین مشکل را داشته‌اند و چطور حل کرده‌اند؟** | **هیچ پروژه عمومی‌ای که دقیقاً خروجی STT خود بله را استخراج کند پیدا نکردم.** همان الگوهای نزدیک وجود دارد: کلاینت‌های کاربری مهندسی‌معکوس‌شده (`aiobale`، `bale-sdk`، فورک‌ها)، سلف‌بات‌ها و اسکرپرهای مبتنی بر Selenium/Playwright، و کسب‌وکارهایی که سراغ ASR مستقل رفته‌اند (بخش ۳). | +| **اگر الگوبرداری یا شخص ثالث بخواهیم، چه کنیم؟** | از متن‌باز، لایه‌های انتقال و نشست و ابزار دیکود پروتوباف را بردارید، نه خود «ترنسکرایب» را. از شخص ثالث، چند ASR را به‌عنوان کاندید موازی کنار بله بگذارید (بخش ۵ و ۶). | +| **بهترین ترکیب؟** | **«بله + یک ASR تجاری فارسی + یک مدل خودمیزبان با واژگان تخصصی + LLM اجماع‌گیر با کاتالوگ کالا»**، با کرالر بله به‌صورت adapter قابل‌سوییچ. جزئیات در بخش ۷. | + +### سه یافته‌ای که تصمیم شما را تغییر می‌دهد + +1. **«نیازی به آپلود ویس نیست.»** بات خودتان با `sendVoice(file_id)` می‌تواند همان ویس کاربر را برای حساب کرالر بفرستد. این‌طور فایل را کرالر دانلود و آپلود نمی‌کند. این طراحی از گزارش B ساده‌تر و کم‌ریسک‌تر است (بخش ۷.۲). +2. **داده بنچمارک مستقل نشان می‌دهد ASR تجاری فارسی حدود ۱۴ امتیاز WER از بهترین متن‌باز جلوتر است** (مقاله PSRB، بخش ۶.۲). این با ادعای گزارش A که «متن‌باز پایدارترین راهکار» است، همخوان نیست. البته روی داده عمومی بوده، نه واژگان تدارکات شما. +3. **کد مرجع گزارش B شبه‌کد است، نه کد اجراشدنی.** توابعی مثل `client.send_voice` و `client.raw_call` در مستندات عمومی `bale-sdk` وجود ندارند (بخش ۲.۲). + +--- + +## ۲. ارزیابی دو گزارش مدیر پروژه + +### ۲.۱ گزارش A: متن‌باز و شخص ثالث + +**نقاط قوت** +- پوشش گسترده: ۸ راهکار در یک ماتریس مقایسه (تأخیر، رم، خلوص متن، پایداری، هزینه). +- تفکیک درست «چه چیزی را از بله بگیریم» و «چه چیزی را مستقل بسازیم». +- پیشنهاد معماری دومسیره (بله + مسیر مستقل) درست و عملی است. +- معرفی `bale-sdk` و توجه به مجوز اختصاصی آن (نکته‌ای که گزارش‌های قبلی کم‌رنگ داشتند). +- اشاره درست به اینکه Playwright روی وب بله الگوی جاافتاده‌ای است. + +**نقاط ضعف** +- چند ادعای کلیدی بدون منبع است و در بررسی من تأیید نشد (جدول زیر). +- مقایسه دقت متن‌باز با تجاری فقط بر اساس WER خودگزارشی مدل‌ها روی Common Voice است. این عدد روی ویس‌های واقعی و محاوره‌ای معنی ندارد. +- ارائه‌دهندگان تجاری مهم فارسی (Avanegar/Partai، Aipa) و Google Chirp 2 اصلاً در گزارش نیستند. +- تحلیل خطای شخص ثالث ناقص است (مثلاً قیمت‌ها بدون تاریخ و منبع). + +### جدول راستی‌آزمایی ادعاهای گزارش 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 لینک شده‌اند ولی برای من باز نشد. این مهم‌ترین ادعای هر دو گزارش است و باید در ۳۰ دقیقه اول خودتان تأیید شود (بخش ۹). | +| `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 روی فارسی محاوره‌ای ضعیف است (بخش ۶). | +| `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 به‌طور محسوس دقیق‌ترند (بخش ۶.۲). متن‌باز «مستقل‌ترین» است، نه لزوماً «دقیق‌ترین». | + +### ۲.۲ گزارش B: نقشه راه اجرایی + +**نقاط قوت** +- دیاگرام‌های توالی و جریان تاب‌آوری خوب و قابل‌تحویل به مدیر پروژه‌اند. +- اصلاحات قبلی اعمال شده است: `compare_digest`، سقف روزانه، قفل ترتیبی، ۵۰۳ در خطا و متن خالی. +- ورودی‌های انسانی روشن‌اند و «تأیید دکمه روی وب» به‌عنوان ورودی گذاشته شده است. +- Rollback با خالی‌کردن env درست است. + +**ایرادها و ریسک‌های اجرایی** + +| مورد | توضیح | +|---|---| +| **کد مرجع شبه‌کد است** | `client.send_voice(...)`، `client.raw_call(service=..., method=...)` و `sent_msg.voice.to_media()` در README عمومی `bale-sdk` نیست. جدول متدهای سطح‌بالای آن ارسال رسانه ندارد. مدل دسترسی RPC خام آن این‌طور است: «namespace برابر snake_case بخش آخر نام سرویس». یعنی `bale.messaging.v2.Messaging` → `client.messaging`. پس فراخوانی احتمالی `bale.turing.v1.AI` باید چیزی شبیه `client.ai.GetTranscript(...)` باشد (**استنباط من**). | +| **آپلود ویس** | کارِ آپلود فایل و ساخت پیام صوتی (Media) را سند فرض گرفته ولی طراحی نکرده. این بخش از خود RPC ترنسکریپت سخت‌تر است. راه ساده‌تر در بخش ۷.۲ آمده. | +| **پاسخ `GetTranscript`** | طبق خود سند، پاسخ `bool mustWait` و یک `DownloadSource` دارد، نه رشته متن. یعنی احتمالاً متن باید از یک لینک/منبع دانلود خوانده شود. مثال کد این را مدیریت نمی‌کند. | +| **کلید پیش‌فرض** | `os.environ.get("BALE_STT_INTERNAL_KEY", "bale_internal_secret")` هنوز مقدار پیش‌فرض دارد؛ بدون env هم بالا می‌آید. با `os.environ[...]` خطا بدهد. | +| **شمارنده روزانه** | متغیر درون‌حافظه است: با ریست کانتینر صفر می‌شود و ریست روزانه هم ندارد (پس بعد از رسیدن به سقف تا ری‌استارت بعدی می‌ماند). | +| **`.env` نمونه** | `API_KEY=bale_internal_secret` یعنی همان مقدار پیش‌فرض ضعیف. | +| **مسیرهای محلی** | لینک‌ها به `file:///c:/Users/Reza/Desktop/...` اشاره دارند. در اشتراک سند باید پاک شوند. | +| **«بدون وقفه»** | ادعای «بدون حتی ۱ ثانیه قطعی» فقط وقتی درست است که `stt.py` در مواجهه با ۵۰۳ همان پرووایدر را بی‌خطا رد کند. این را باید در کد بات بررسی کرد، نه از روی سند. | + +--- + +## ۳. آیا دیگران هم همین مشکل را داشته‌اند؟ و چطور حل کرده‌اند؟ + +### ۳.۱ نتیجه کلی + +**پروژه عمومیِ مشخصی که خروجی 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؛ بدون هیچ راهی برای رسیدن به ترنسکریپت. | + +### ۳.۲ برداشت از همین شواهد + +1. **راه مشترک جامعه این است که STT را بیرون از بله انجام می‌دهند.** هیچ‌کس STT بله را «کرال» نکرده یا اگر کرده منتشر نشده است. پس در هر حال ریسک بالاتر و شواهد کمتری برای مسیر شماست. +2. **زیرساخت لازم برای کرالر وجود دارد** (انتقال WebSocket، OTP، نشست، ارسال و دانلود فایل)؛ پس این پروژه «ساخت از صفر» نیست. +3. **پایداری کتابخانه‌ها ضعیف است** (aiobale رها شد، `bale-sdk` نسخه 0.1.1 است). هر معماری باید فرض کند اجزای متن‌باز ممکن است از کار بیفتند. +4. **تنها چیزی که کل شرط‌بندی را توجیه می‌کند، دقت بله روی ویس‌های شماست.** بدون آزمون دستی (بخش ۸، گام ۰) نباید ساخت شروع شود. + +--- + +## ۴. کاتالوگ کامل راهکارها + +برای هر راهکار: چه کار می‌کند، مزیت، عیب، تلاش (تخمین من)، وضعیت تأیید. + +### گروه ۱: استفاده از 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)، نه حدس بزنید. | + +--- + +## ۵. اگر از پروژه‌های متن‌باز الگوبرداری کنیم: چه چیزی را می‌توان برداشت؟ + +| مؤلفه | منبع | مجوز | چه چیزی بردارید | ملاحظات | +|---|---|---|---|---| +| انتقال WebSocket، ورود با OTP، ذخیره نشست، Dispatcher | `aiobale` / `autobale` / `BaleClient` | MIT ✅ | همه چیز؛ حتی می‌توانید فورک کنید | `aiobale` رها شده؛ `autobale` فعال‌تر ادعا می‌شود. نسخه را قفل کنید | +| ارسال/دانلود فایل و رسانه | همان کتابخانه‌ها (گزارش A: حدود ۸۰ متد) | MIT | آپلود و ساخت پیام صوتی | اگر ایده بخش ۷.۲ را بپذیرید (بات `sendVoice` بفرستد) اصلاً لازم نیست | +| **تعریف 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. **تست قرارداد:** با یک «ویس طلایی» شبانه بررسی کنید پاسخ هنوز ساختار قبلی را دارد. + +--- + +## ۶. اگر شخص ثالث بخواهیم: چه کار کنیم؟ + +### ۶.۱ ساده‌ترین کار: هر سرویس یک «پرووایدر» در `stt.py` + +بات شما اکنون پرووایدرهای شماره‌دار با قرارداد OpenAI دارد. پس هر شخص ثالث را یا مستقیم (اگر OpenAI-compatible است) یا با یک adapter کوچک اضافه کنید. مزیت: چند ASR موازی، اجماع LLM، و حذف خودکار پرووایدر خراب (۵۰۳). + +### ۶.۲ داده مستقل برای انتخاب: بنچمارک 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 هم برای همین است. + +### ۶.۳ ارزیابی شخص ثالث: روش پیشنهادی (بدون وابستگی به ادعاهای فروشنده) + +1. **مجموعه طلایی:** ۱۰۰ ویس واقعی (حداقل ۳۰) با رونوشت انسانی و جدول کالا/تعداد/واحد. +2. **اجرای همه کاندیدها** روی همان ویس‌ها: بله (دستی در گام ۰)، Avanegar، Aipa، Chirp 2، Faster-Whisper، مدل فاین‌تیون فارسی. +3. **سه معیار:** CER (برای فارسی مناسب‌تر از WER)، WER و مهم‌تر **F1 استخراج اقلام** (نام کالا، تعداد، واحد). +4. **انتخاب بر اساس «بهبود اجماع»،** نه دقت تکی: هر کاندید را به مجموعه فعلی اضافه کنید و ببینید خطای نهایی استخراج اقلام چقدر کم می‌شود. +5. **هزینه:** هزینه به ازای هر ویس ۱۰–۳۰ثانیه‌ای در ماه برای حجم واقعی شما. + +### ۶.۴ چه سرویسی را کجا بگذاریم؟ (پیشنهاد مقدماتی) + +| نقش | کاندید | چرا | +|---|---|---| +| پرووایدر ۱ (تجاری فارسی) | Avanegar یا Aipa | بهترین در بنچمارک فارسی و لهجه‌ها | +| پرووایدر ۲ (ابری) | Chirp 2 (اگر دسترسی ممکن است) | تقریباً هم‌سطح؛ مکمل متفاوت | +| پرووایدر ۳ (خودمیزبان) | Faster-Whisper large-v3 + واژگان تدارکات | مستقل؛ قابل بهبود با داده خودتان | +| پرووایدر ۴ (بله) | کرالر بله (S3/S4/S5) | تنها کاندیدی که تفاوت واقعی (آموزش‌دیده روی داده‌های متفاوت) دارد؛ ارزش آن را گام ۰ تعیین می‌کند | +| پشتیبان سبک | Vosk | فقط برای اینکه همیشه یک خروجی داشته باشیم | + +--- + +## ۷. بهترین ترکیب برای کرالر شما + +### ۷.۱ اصول طراحی + +1. **کرالر بله «یک کاندید» است، نه ستون فقرات.** اگر خراب شد، سیستم باید بدون آن ادامه دهد. +2. **حداقل دو کاندید غیر بله همیشه فعال** باشند تا اجماع بدون بله هم معنی داشته باشد. +3. **تصمیم بر اساس داده خودتان**، نه ادعای مقاله و فروشنده. +4. **هر چیز بله پشت یک قرارداد ثابت (OpenAI-compatible)** باشد تا موتور داخلی عوض شود و بات دست نخورد. +5. **داده را جمع کنید** تا در بلندمدت از بله مستقل شوید (L3). + +### ۷.۲ معماری پیشنهادی + +```mermaid +flowchart TD + U[کاربر: ویس] --> BOT[بات تدارکات - Bot API رسمی] + BOT --> PRE[پیش‌پردازش: 16kHz mono، VAD، مدت] + + PRE --> P1[پرووایدر 1: ASR تجاری فارسی] + PRE --> P2[پرووایدر 2: Chirp 2 / ابری] + PRE --> P3[پرووایدر 3: Faster-Whisper + واژگان] + PRE --> RELAY[رله ویس به حساب کرالر: sendVoice با file_id] + + RELAY --> CR[کرالر بله: adapter] + CR -->|انتخاب موتور با env| E1[موتور RPC: GetTranscript] + CR -->|فالبک| E2[موتور Playwright] + CR --> DEL[حذف پیام رله] + + P1 --> NORM[نرمال‌سازی فارسی: ZWNJ، ارقام، یی/کاف] + P2 --> NORM + P3 --> NORM + E1 --> NORM + E2 --> NORM + + NORM --> LLM[LLM اجماع‌گیر + کاتالوگ کالا/برند/واحد] + LLM --> CONF{اختلاف در عدد/واحد؟} + CONF -- خیر --> OUT[ثبت استعلام] + CONF -- بله --> ASK[از کاربر تأیید بگیر] + ASK --> OUT + OUT --> DATA[(ذخیره ویس + متن تأییدشده برای فاین‌تیون)] +``` + +### ۷.۳ ایده کلیدی: «رله» به‌جای آپلود + +گزارش B فرض می‌کند کرالر ویس را به **Saved Messages** خودش آپلود می‌کند. راه ساده‌تر و سالم‌تر: + +1. حساب کرالر یک‌بار به بات شما `/start` می‌زند (گفتگوی خصوصی بات↔کرالر). +2. بات با Bot API رسمی `sendVoice(chat_id=, voice=)` می‌فرستد. طبق مستندات رسمی، ارسال با `file_id` بدون آپلود مجدد و بدون محدودیت است. (`file_id` مخصوص همان بات است؛ اینجا همان بات می‌فرستد، پس مشکلی نیست.) +3. حساب کرالر (کلاینت کاربری) آپدیت پیام جدید را می‌گیرد؛ **شیء Media ویس، `OutPeer` و `message_id` همه همان‌جاست.** دقیقاً سه ورودی `GetTranscript` (Media voice، OutPeer، MessageId). +4. کرالر `GetTranscript` را صدا می‌زند، متن را می‌گیرد. +5. پیام با `deleteMessage` رسمی بات (برای پیام خروجی در گفتگوی خصوصی مجاز است، تا ۴۸ ساعت) یا از سمت کرالر حذف می‌شود. + +**مزایا:** بدون RPC آپلود؛ بدون ساخت `Media` از صفر؛ بدون الگوی شبه‌انسانی «آپلود فایل» روی حساب؛ ID پیام و peer واقعی‌اند. +**ریسک:** اینکه RPC روی ویس **دریافتی** هم کار کند (در اپ، دکمه روی ویس دریافتی هم هست؛ ولی تأییدش با آزمون واقعی لازم است). + +### ۷.۴ لایه‌بندی موتور کرالر (به ترتیب اولویت) + +| اولویت | موتور | شرط | یادداشت | +|---|---|---|---| +| ۱ | **E1: RPC مستقیم** (transport از `aiobale`/`autobale`، پروتوی خودساخته) | RPC تأیید شد | سبک (<۲۰MB)، متن خالص | +| ۲ | **E1+: ورود با مرورگر، کار با پروتکل (S5)** | ورود/تمدید JWT ناپایدار باشد | Playwright فقط برای توکن | +| ۳ | **E2: Playwright کامل** | RPC تأیید نشد ولی دکمه وب هست | رم ۳۵۰–۵۰۰MB | +| ۴ | شبیه‌ساز اندروید | هیچ‌کدام ممکن نشد | فقط در صورت ارزش بالای بله | + +اگر RPC در باندل وب بله وجود دارد، **به احتمال زیاد دکمه هم در وب هست** (استنباط من: گزارش‌ها می‌گویند پروتوها از کلاینت وب بازیابی شده‌اند). ولی تا خودتان نبینید فرض نکنید. + +### ۷.۵ تقویت اجماع (جایی که بیشترین دقت را می‌گیرید) + +| اقدام | اثر مورد انتظار | هزینه | +|---|---|---| +| تزریق **کاتالوگ کالا/برند/واحد** به LLM اجماع‌گیر | بیشترین کاهش خطای استخراج اقلام | کم | +| `initial_prompt`/hotwords واژگان تدارکات در Faster-Whisper | بهبود نام برندها و اصطلاح‌ها | کم | +| نرمال‌سازی فارسی (ZWNJ، ارقام) پیش از اجماع | کاهش اختلاف‌های ساختگی بین کاندیدها | کم | +| قاعده «اختلاف عدد/واحد ⇒ تأیید از کاربر» | حذف سفارش اشتباه | متوسط (UX) | +| ثبت ویس + متن تأییدشده → LoRA روی Whisper | استقلال بلندمدت؛ بهبود روی لهجه و واژگان شما | بالا (ولی ارزشمندترین) | +| VAD پیش از ASR | کاهش هالوسینیشن Whisper روی سکوت/نویز (پیشنهاد مقاله PSRB) | کم | + +### ۷.۶ سناریوهای تصمیم + +| اگر… | آنگاه… | +|---|---| +| گام ۰: بله بهتر از مجموعه فعلی نیست | **کرالر را نسازید.** سرمایه را روی ASR تجاری + فاین‌تیون بگذارید. | +| بله کمی بهتر است (< ۱۰٪) | فقط مسیر سبک E1 را بسازید؛ Playwright و شبیه‌ساز را رها کنید. | +| بله به‌طور چشمگیر بهتر است (> ۱۵٪) | E1 + فالبک E2 + مسیر رسمی S1 را موازی دنبال کنید. | +| RPC پیدا نشد و دکمه وب نیست | S6 فقط اگر ارزش بله زیاد باشد؛ در غیر این صورت منصرف شوید. | +| بله خوب است ولی ناپایدار | از خروجی بله به‌عنوان **برچسب‌گذار** برای ساخت دادگان فاین‌تیون استفاده کنید (L3) تا وابستگی کم شود. | + +--- + +## ۸. نقشه راه پیشنهادی + +| روز | کار | خروجی | دروازه | +|---|---|---|---| +| ۰ | **آزمون دستی دقت:** ۳۰ ویس واقعی را در اپ بله به متن تبدیل کنید و با کاندیدهای فعلی مقایسه کنید | جدول CER/WER و F1 اقلام | G0: بهبود معنی‌دار یا توقف | +| ۰ | جست‌وجوی `GetTranscript` در `bale-sdk`؛ تأیید دکمه روی `web.bale.ai`؛ ایمیل به بله (S1) | یک صفحه یافته | G1: RPC یا دکمه وجود دارد | +| ۱ | کپچر ترافیک وب و ساخت پروتوی خودتان | `req.bin`/`resp.bin`، `findings.md` | G2 | +| ۲–۳ | پروتوتایپ E1 با رله بات→کرالر | یک ویس → یک متن | G3 | +| ۳–۴ | adapter سخت‌شده (۵۰۳، سقف روزانه پایدار، قفل، لاگ) | اتصال به `stt.py` | — | +| ۴–۵ | **موازی:** پرووایدرهای شخص ثالث (Avanegar/Aipa/Chirp، Faster-Whisper) | سنجش روی مجموعه طلایی | G4: بهبود اجماع | +| ۶–۱۰ | سواک ۵۰–۱۰۰ ویس + مانیتورینگ + مجموعه طلایی شبانه | گزارش پایداری | G5: استقرار | +| ماه ۲+ | جمع‌آوری داده و LoRA | مدل داخلی | کاهش وابستگی | + +**KPIها:** F1 استخراج اقلام، CER، نرخ اختلاف عدد/واحد، نرخ موفقیت کرالر، p95 تأخیر، هزینه به ازای ویس. + +--- + +## ۹. ضمیمه: دستورها و نمونه‌ها + +### ۹.۱ کشف سریع (۳۰ دقیقه) + +```bash +python -m venv .v && . .v/bin/activate +pip index versions aiobale-py # آیا واقعاً وجود دارد؟ +pip index versions aiobale autobale baleclient bale-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 +``` + +در پایتون: + +```python +from bale import ALL_RPCS +for r in ALL_RPCS: + s = str(r).lower() + if any(k in s for k in ("transcri", "turing", "speech")): + print(r) +``` + +### ۹.۲ ساخت پروتو از کپچر خودتان + +1. `web.bale.ai` را با حساب اختصاصی باز کنید، DevTools → Network (فیلتر WS و Fetch). +2. روی ویس «تبدیل به متن» را بزنید؛ درخواست/پاسخ را ذخیره کنید. +3. پیشوند ۵ بایتی gRPC-Web را بردارید (برای POST gRPC-Web؛ در WebSocket بله قالب را باید ببینید). +4. `protoc --decode_raw < req.bin` یا `protoscope`. + +### ۹.۳ اسکلت رله بات→کرالر (شبه‌کد؛ نام متدها را با کتابخانه واقعی تطبیق دهید) + +```python +# سمت بات (Bot API رسمی) +async def relay_voice_to_crawler(voice_file_id: str, crawler_user_id: int) -> int: + msg = await bot_api("sendVoice", chat_id=crawler_user_id, voice=voice_file_id) + return msg["message_id"] + +# سمت کرالر (کلاینت کاربری) - شبه‌کد +@client.on(NewMessage) # فقط از بات خودمان +async def on_voice(event): + media, peer, mid = event.media, event.peer, event.id + resp = await client.ai.GetTranscript(voice=media, outPeer=peer, messageId=mid) # نام‌ها = استنباط + while resp.mustWait: + await asyncio.sleep(1.5) + resp = await client.ai.GetTranscript(voice=media, outPeer=peer, messageId=mid) + text = await read_download_source(resp.downloadSource) + await client.delete_messages(peer, [mid]) + return text +``` + +### ۹.۴ قرارداد adapter (بدون تغییر در بات) + +- ورودی: `POST /v1/audio/transcriptions` (multipart: `file`, `model`, `language`). +- خروجی موفق: `{"text": "..."}`. +- هر خطا یا متن خالی: **HTTP 503** (نه ۲۰۰ با متن خالی). +- کلید داخلی: از env الزامی، بدون مقدار پیش‌فرض؛ مقایسه با `secrets.compare_digest`. +- سقف روزانه و صف ترتیبی؛ شمارنده را پایدار (Redis/فایل) و روزانه ریست‌شونده بگیرید. + +--- + +## ۱۰. پرسش‌های باز (قبل از شروع ساخت پاسخ دهید) + +1. `GetTranscript` واقعاً در `bale-sdk` وجود دارد؟ (۳۰ دقیقه، بخش ۹.۱) +2. دکمه «تبدیل به متن» روی `web.bale.ai` برای ویس **دریافتی** هست؟ +3. خروجی پاسخ: متن مستقیم است یا `DownloadSource` که باید دانلود شود؟ +4. `stt.py` با ۵۰۳ و متن خالی چه می‌کند؟ +5. حجم واقعی ویس‌های شما در روز و طول متوسط آن‌ها چقدر است؟ (برای هزینه و سقف) +6. آیا می‌توانید ۳۰–۱۰۰ ویس واقعی با رونوشت انسانی تهیه کنید؟ (بدون آن هیچ مقایسه‌ای معتبر نیست.) +7. آیا دسترسی شبکه به Chirp 2 / ارائه‌دهندگان خارجی از سرورهای شما ممکن است؟ +8. نگهداری: چه کسی مالک کرالر است اگر بله پروتکل را عوض کرد؟ + +--- + +## ۱۱. منابع + +- مستندات رسمی بات بله: https://docs.bale.ai/ +- `bale-sdk` (PyPI): https://pypi.org/project/bale-sdk/ و https://pypi.org/project/bale-sdk/0.1.1/ +- `aiobale` (PyPI): https://pypi.org/project/aiobale ، مستندات: https://docs.aiobale.ir +- `autobale` (PyPI): https://pypi.org/project/autobale/ +- `BaleClient` (PyPI): https://pypi.org/project/BaleClient/ +- `baleself` (Selenium): https://pypi.org/project/baleself/ +- `bale-mcp` (PyPI): https://pypi.org/project/bale-mcp/ +- بات هوش مصنوعی بله روی Cloudflare Workers: https://github.com/hamedcode/bale-ai-bot +- BaleVPN: https://github.com/2077DevWave/BaleVPN +- Blackbox Protobuf (NCC Group): https://www.github.com/nccgroup/blackboxprotobuf +- PSRB (Persian ASR benchmark): https://arxiv.org/pdf/2505.21230 +- مدل فارسی Whisper (نمونه): https://huggingface.co/vhdm/whisper-large-fa-v1 +- خبر انتشار نسخه بله با قابلیت تبدیل ویس به متن (آنا): https://ana.ir/fa/news/994908 diff --git a/bot/app/defaults.py b/bot/app/defaults.py index b7c6529..121275c 100644 --- a/bot/app/defaults.py +++ b/bot/app/defaults.py @@ -250,3 +250,24 @@ def propose_project_distribution(project_names: list[str], items: list[dict]) -> mat_name = it.get("material_name") or f"قلم {idx + 1}" proposals.append((mat_name, assigned_p)) return proposals + + +# --------------------------------------------------------------------------- +# Authorized Whitelist Users (Static Role Configuration) +# --------------------------------------------------------------------------- + +AUTHORIZED_USERS: dict[int, dict] = { + 1461166742: { + "user_id": "03091956-99ad-4729-923a-a6aa1c5c13be", + "display_name": "مدیر تدارکات", + "role": "procurement_manager", + "is_admin": True, + }, + 1839839922: { + "user_id": "03091956-99ad-4729-923a-a6aa1c5c13be", + "display_name": "همکار / تست", + "role": "procurement_manager", + "is_admin": False, + }, +} + diff --git a/bot/app/handlers/__init__.py b/bot/app/handlers/__init__.py index 6107ec1..0e33b33 100644 --- a/bot/app/handlers/__init__.py +++ b/bot/app/handlers/__init__.py @@ -39,6 +39,10 @@ async def handle_update(ctx: BotContext, update: dict) -> None: async def handle_message(ctx: BotContext, chat_id: int, message: dict) -> None: + user = await _gate(ctx, chat_id, message) + if user is None: + return + text = (message.get("text") or message.get("caption") or "").strip() source = "text" voice_transcripts: list[dict] | None = None @@ -56,10 +60,6 @@ async def handle_message(ctx: BotContext, chat_id: int, message: dict) -> None: return ctx.pending.pop(chat_id) # stale/irrelevant flow → fall through - user = await _gate(ctx, chat_id, message) - if user is None: - return # gate either started linking or is mid-linking - reply_to = message.get("reply_to_message") reply_to_msg_id = reply_to.get("message_id") if reply_to else None @@ -272,25 +272,58 @@ async def handle_message(ctx: BotContext, chat_id: int, message: dict) -> None: # helpers # --------------------------------------------------------------------------- +from datetime import datetime, timezone +import json +from pathlib import Path + +UNREGISTERED_FILE = Path(__file__).resolve().parent.parent / "unregistered_users.json" + + +def _record_unregistered_user(chat_id: int, message: dict) -> None: + from_user = message.get("from") or {} + record = { + "chat_id": chat_id, + "username": from_user.get("username"), + "first_name": from_user.get("first_name"), + "last_name": from_user.get("last_name"), + "timestamp": datetime.now(timezone.utc).isoformat(), + "text_preview": (message.get("text") or "")[:100], + } + records = [] + try: + if UNREGISTERED_FILE.exists(): + content = UNREGISTERED_FILE.read_text(encoding="utf-8").strip() + if content: + records = json.loads(content) + if not isinstance(records, list): + records = [] + except Exception: + records = [] + records.append(record) + try: + UNREGISTERED_FILE.write_text(json.dumps(records, ensure_ascii=False, indent=2), encoding="utf-8") + except Exception as e: + logger.warning("Could not write to unregistered_users.json: %s", e) + + async def _gate(ctx: BotContext, chat_id: int, message: dict) -> LinkedUser | None: - """Link or admin gate. Returns None when the message was consumed by linking.""" - from . import links + """Whitelist gate (Casdoor bypassed). Unregistered users are blocked and logged.""" + from app.defaults import AUTHORIZED_USERS - link = await ctx.links.get(chat_id) - if link and link.user_id: - return LinkedUser(chat_id, link.user_id, link.display_name, link.role, is_admin=False) - if chat_id in ctx.admin_chat_ids: - return LinkedUser(chat_id, None, None, None, is_admin=True) + # 1. Whitelist check (only registered whitelist IDs) + if chat_id in AUTHORIZED_USERS: + u = AUTHORIZED_USERS[chat_id] + return LinkedUser( + chat_id=chat_id, + user_id=u.get("user_id"), + display_name=u.get("display_name"), + role=u.get("role", "procurement_manager"), + is_admin=u.get("is_admin", True), + ) - flow = ctx.pending.get(chat_id) - if flow and flow.kind == "link_password": - await links.finish_link(ctx, chat_id, flow, text=None, password=message.get("text") or "") - return None - if flow and flow.kind == "link_username": - await links.finish_username_step(ctx, chat_id, flow, message.get("text") or "") - return None - await ctx.send(chat_id, LINK_START) - ctx.pending.set(chat_id, "link_username", {}) + # 2. Block unregistered user and record log + _record_unregistered_user(chat_id, message) + await ctx.send(chat_id, "دسترسی شما هنوز تعریف نشده است. درخواست شما جهت بررسی ثبت شد.") return None diff --git a/bot/app/handlers/callbacks.py b/bot/app/handlers/callbacks.py index 03d3521..a60b129 100644 --- a/bot/app/handlers/callbacks.py +++ b/bot/app/handlers/callbacks.py @@ -29,12 +29,18 @@ async def handle_callback(ctx, cq: dict) -> None: async def _gate(ctx, chat_id: int) -> LinkedUser | None: - link = await ctx.links.get(chat_id) - if link and link.user_id: - return LinkedUser(chat_id, link.user_id, link.display_name, link.role, is_admin=False) - if chat_id in ctx.admin_chat_ids: - return LinkedUser(chat_id, None, None, None, is_admin=True) - await ctx.send(chat_id, UNLINKED) + from app.defaults import AUTHORIZED_USERS + + if chat_id in AUTHORIZED_USERS: + u = AUTHORIZED_USERS[chat_id] + return LinkedUser( + chat_id=chat_id, + user_id=u.get("user_id"), + display_name=u.get("display_name"), + role=u.get("role", "procurement_manager"), + is_admin=u.get("is_admin", True), + ) + await ctx.send(chat_id, "دسترسی شما هنوز تعریف نشده است. درخواست شما جهت بررسی ثبت شد.") return None diff --git a/bot/app/handlers/commands.py b/bot/app/handlers/commands.py index 4278942..20390c6 100644 --- a/bot/app/handlers/commands.py +++ b/bot/app/handlers/commands.py @@ -31,12 +31,15 @@ async def handle_command(ctx, chat_id: int, text: str) -> None: async def _start(ctx, chat_id: int) -> None: + from app.defaults import AUTHORIZED_USERS + await ctx.send(chat_id, WELCOME) + if chat_id in AUTHORIZED_USERS or chat_id in ctx.admin_chat_ids: + return link = await ctx.links.get(chat_id) if not link or not link.user_id: - if chat_id not in ctx.admin_chat_ids: - await ctx.send(chat_id, "برای شروع، حساب خود را متصل کن — نام کاربری Casdoor را بفرست:") - ctx.pending.set(chat_id, "link_username", {}) + await ctx.send(chat_id, "برای شروع، حساب خود را متصل کن — نام کاربری Casdoor را بفرست:") + ctx.pending.set(chat_id, "link_username", {}) async def _drafts(ctx, chat_id: int) -> None: @@ -71,6 +74,13 @@ async def _cancel(ctx, chat_id: int) -> None: async def _whoami(ctx, chat_id: int) -> None: + from app.defaults import AUTHORIZED_USERS + + if chat_id in AUTHORIZED_USERS: + u = AUTHORIZED_USERS[chat_id] + role = ROLE_FA.get(u.get("role") or "", u.get("role") or "مدیر تدارکات") + await ctx.send(chat_id, f"اتصال (لیست سفید): {esc(u.get('display_name'))} — {role}") + return link = await ctx.links.get(chat_id) if chat_id in ctx.admin_chat_ids: await ctx.send(chat_id, "حالت ادمین (بدون اتصال Casdoor).") diff --git a/bot/app/nlu.py b/bot/app/nlu.py index 774edf4..00d12aa 100644 --- a/bot/app/nlu.py +++ b/bot/app/nlu.py @@ -151,6 +151,13 @@ async def understand(settings: Settings, text: str | list[dict], draft_summary: lines.append(f"- نسخه {idx} ({name}): «{t}»") lines.append("\nبا تلفیق این نسخه‌ها و تصحیح خطاهای شنیداری، پیام را تحلیل کن و consensus_text را به همراه intent و patch بساز.") user_content = "\n".join(lines) + elif isinstance(text, str) and ("مدل 1" in text or "مدل ۱" in text) and ("مدل 2" in text or "مدل ۲" in text): + user_content = ( + "پیام صوتی کاربر توسط چند مدل تبدیل گفتار به متن (STT) به صورت زیر پیاده‌سازی شده است:\n" + + normalize_digits(text) + + "\n\nبا تلفیق این نسخه‌ها و تصحیح خطاهای شنیداری، پیام را تحلیل کن و consensus_text را به همراه intent و patch بساز." + ) + first_text = text else: user_content = normalize_digits(str(text)) first_text = str(text) diff --git a/bot/app/unregistered_users.json b/bot/app/unregistered_users.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/bot/app/unregistered_users.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/bot/tools/send_analysis_to_bot.py b/bot/tools/send_analysis_to_bot.py new file mode 100644 index 0000000..1c96cd4 --- /dev/null +++ b/bot/tools/send_analysis_to_bot.py @@ -0,0 +1,77 @@ +import asyncio +import json +import logging +import sys +from pathlib import Path + +# Add project root to sys.path +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from app.config import settings +from app.bale.client import BaleClient +from app.api.client import ErpClient +from app.runtime import BotContext, LinkedUser +from app.nlu import understand +from app.handlers import quote +from app.defaults import AUTHORIZED_USERS + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s — %(message)s") +logger = logging.getLogger("send_analysis") + +CHAT_ID = 1461166742 + +TRANSCRIPTS = [ + { + "name": "مدل 1 (Whisper)", + "text": "الان شبت بخیر می خواستیم که بیست و پنج تا کیسه عرض به خدمت شما که گچ و خاک از مصطفوی بخریم و کنارش هم یه پنجاه تا کیسه سیمان پنجاه کیلویی که اونا تقریبا در می آد پامون دونه ای بیست و پنج میلیون سیمانی ها پامون در می آد حدودا چهل میلیون اینا دونه ای بهمون گفته که می تونین سه قسط بهم بدید کلی پولش ها در طول زمان و یا بیاید یه بار پولش رو امروز بهم بدین یه تخفیف ده درصدی تقریبا براتون لحاظ می کنم عددش رو کم می کنم خریدمون هم عمدتا برای منشیانه و دفتر پیش فاکتورم گفته تا حدودا بیست روزی نامه براتون اوکیه همین", + }, + { + "name": "مدل 2 (Vosk)", + "text": "سلام شبت بخیر. میخواستیم که 25 تا کیسه عرض به خدمت شما که گچ و خاک از مستفدی بخریم و کنارشم یه 50 تا کیسه سیمان 50 کیلویی که اونا تقریباً در میاد پامون دونهای 25 میلیون، سیمانیا پامون در میاد حدوداً 40 میلیون اینا دونهای. بهمون گفته که میتونین سه قسم بدین کلی پولشا در طول زمان و یا بیاین یه باره پولشو امروز بم بدین یه تخفیف 10 درصدی تقریباً بره دون، لحاظ میکنم عددشو کم میکنم. خریدم اونم اومدن برای منشیانه و دفتر. پیشفاکتورم گفته تا حدود یه 20 روز اینا برادون اوکیه. همین.", + }, +] + + +async def run(): + print(f"Connecting to Bale and ERP API for Chat ID: {CHAT_ID}...") + bale = BaleClient(settings.BALE_API_BASE, settings.BOT_TOKEN) + api = ErpClient(settings.API_BASE_URL, settings.API_TIMEOUT_SECONDS, settings.SERVICE_TOKEN) + ctx = BotContext(bale, api) + + user_info = AUTHORIZED_USERS.get(CHAT_ID, {}) + user = LinkedUser( + chat_id=CHAT_ID, + user_id=user_info.get("user_id"), + display_name=user_info.get("display_name", "مدیر تدارکات"), + role=user_info.get("role", "procurement_manager"), + is_admin=user_info.get("is_admin", True), + ) + + print("Sending transcripts to NLU (LLM) for multi-model consensus and entity extraction...") + result = await understand(settings, TRANSCRIPTS) + print("NLU Result:") + print(json.dumps(result, ensure_ascii=False, indent=2)) + + consensus_text = result.get("consensus_text") or "" + patch = result.get("patch") or {} + + intro_msg = ( + f"🤖 **تحلیل هوشمند فایل صوتی (تجمیع خروجی چند مدل STT):**\n\n" + f"📝 **متن استخراج‌شده و تصحیح‌شده (Consensus):**\n«{consensus_text}»\n\n" + f"📋 پیش‌نویس استعلام خرید با مشخصات استخراج‌شده صادر گردید:" + ) + await ctx.send(CHAT_ID, intro_msg) + + # Create and render the draft quote + await quote.handle_entities( + ctx, + CHAT_ID, + user, + patch, + source="voice", + ) + print("Draft quote sent successfully to Bale chat!") + + +if __name__ == "__main__": + asyncio.run(run()) diff --git a/bot/tools/smoke_test.py b/bot/tools/smoke_test.py index a5dd5ff..e9e7902 100644 --- a/bot/tools/smoke_test.py +++ b/bot/tools/smoke_test.py @@ -24,7 +24,7 @@ try: except ImportError: sys.modules.setdefault("asyncpg", types.ModuleType("asyncpg")) -CHAT = 100200300 +CHAT = 1461166742 _passed = _failed = 0 @@ -297,8 +297,10 @@ def build_ctx(): return ctx, bale, api -def text_update(update_id, text, reply_to=None): - msg = {"message_id": update_id, "chat": {"id": CHAT}, "text": text, "from": {"id": CHAT}} +def text_update(update_id, text, reply_to=None, chat_id=None, from_user=None): + cid = chat_id or CHAT + fuser = from_user or {"id": cid, "first_name": "مدیر تدارکات", "username": "proc_manager"} + msg = {"message_id": update_id, "chat": {"id": cid}, "text": text, "from": fuser} if reply_to: msg["reply_to_message"] = {"message_id": reply_to} return {"update_id": update_id, "message": msg} @@ -318,12 +320,22 @@ async def main() -> int: _install_fake_stores() from app.drafts import build_quote_request, compute_quote_missing, merge_quote_patch, new_quote_payload import app.handlers as H - from app.nlu import _extract_json, normalize_digits + from app.nlu import _extract_json, normalize_digits, understand + from app.config import settings # ---- pure logic ------------------------------------------------------- check("digits: ۵۰۰→500", normalize_digits("۵۰۰ کیلو") == "500 کیلو") check("json extract: fenced", (_extract_json('```json\n{"a": 1}\n```') or {}) == {"a": 1}) + # ---- multi-STT understand pipeline support --------------------------- + multi_stt_samples = [ + {"name": "whisper", "text": "25 کیسه گچ از مصطفوی"}, + {"name": "vosk", "text": "بیست و پنج کیسه گچ از مصطفوی"}, + ] + stt_res = await understand(settings, multi_stt_samples) + check("multi-stt: pipeline accepted list input", isinstance(stt_res, dict) and "consensus_text" in stt_res) + check("multi-stt: consensus text generated", bool(stt_res.get("consensus_text"))) + payload = new_quote_payload() merge_quote_patch(payload, { "items": [{"material_name": "میلگرد A3", "quantity": 500, "unit_price": 45000}], @@ -341,15 +353,39 @@ async def main() -> int: body, problems = build_quote_request(payload) check("build: blocked without supplier", body is None) - # ---- pipeline: linking gate ------------------------------------------- + # ---- pipeline: whitelist gate & unregistered blocking ----------------- ctx, bale, api = build_ctx() - await H.handle_update(ctx, text_update(1, "سلام")) - check("gate: unlinked asked username", "نام کاربری" in bale.last_text()) + UNAUTH_CHAT = 999999999 + from app.handlers import UNREGISTERED_FILE + if UNREGISTERED_FILE.exists(): + try: + UNREGISTERED_FILE.unlink() + except Exception: + pass - await H.handle_update(ctx, text_update(2, "mohaghegh")) - await H.handle_update(ctx, text_update(3, "secret")) - check("link: login called", api.login_calls and api.login_calls[0]["username"] == "mohaghegh") - check("link: linked greeting", "اتصال انجام شد" in bale.last_text()) + # 1. Unregistered user is blocked and recorded + await H.handle_update( + ctx, + text_update( + 1, + "سلام من متقاضی جدید هستم", + chat_id=UNAUTH_CHAT, + from_user={"id": UNAUTH_CHAT, "first_name": "کاربر متفرقه", "username": "intruder"}, + ), + ) + check("gate: unauthorized user blocked", "دسترسی شما هنوز تعریف نشده است" in bale.last_text()) + + import json + unreg_records = json.loads(UNREGISTERED_FILE.read_text(encoding="utf-8")) if UNREGISTERED_FILE.exists() else [] + check( + "gate: unregistered logged to JSON file", + any(u.get("chat_id") == UNAUTH_CHAT and u.get("first_name") == "کاربر متفرقه" for u in unreg_records), + ) + + # 2. Whitelisted user (1461166742) bypasses Casdoor completely + await H.handle_update(ctx, text_update(2, "/whoami")) + check("gate: whitelisted user active without casdoor", "مدیر تدارکات" in bale.last_text()) + check("gate: no casdoor login prompted", "نام کاربری Casdoor" not in bale.last_text()) # ---- voice → draft quote ---------------------------------------------- NLU_QUEUE.append({"intent": "register_quote", "confidence": 0.9, "patch": {