# سند جامع، تحلیلی و عملیاتی کرالر تبدیل صوت‌به‌متن (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.