30 KiB
سند جامع، تحلیلی و عملیاتی کرالر تبدیل صوتبهمتن (STT) بله
نسخه: ۳.۰ (جامع، تجمیعی و نهایی)
مخاطب اول: مدیر پروژه (تصمیمگیری کلان، ماتریسهای مقایسهای، جریانهای داده و فلوهای اجرایی)
مخاطب دوم: ایجنت هوش مصنوعی مجری (شناخت پروژه، معماری فنی، ساختار پروتکل، کدهای مرجع و چکلیست راهاندازی)
وضعیت نیازمندیها: محدودیت حریم خصوصی موقتاً برداشته شده؛ صحت و کیفیت بالای موتور بله در استعلامهای تدارکاتی توسط کاربر اثبات شده؛ وجود دکمه تبدیل متن در وب (web.bale.ai) تأیید شده؛ رویکرد Playwright به عنوان فالبک آخر (Last Resort) و رویکرد RPC به عنوان اولویت اول تعیین شده است؛ هیچ تخمین زمانی تقویمی وجود ندارد چون تمامی پیادهسازیها توسط ایجنت انجام میشود.
بخش اول: برای مدیر پروژه (دید کلان، مقایسهها و فلوهای تصویری)
۱. صورتمسئله و دلایل توجیهی پروژه
در سامانه استعلام هوشمند تدارکات، کاربران پیامهای صوتی (ویس) حاوی درخواستهای پیچیده خرید (شامل نام کالا، برند، مشخصات فنی، مقادیر، واحدها و اصطلاحات بازار) ارسال میکنند.
بات تدارکات برای رونویسی این پیامها از یک خطلوله چندمدله (Multi-Provider STT Pipeline) استفاده میکند؛ به این معنا که ویس همزمان به چند سرویس تبدیل صوت ارسال شده و متنهای دریافتشده در اختیار یک مدل تحلیلی هوش مصنوعی (DeepSeek NLU) قرار میگیرد تا با ایجاد اجماع (Consensus)، خطاهای واژگانی برطرف و ساختار استاندارد استعلام استخراج گردد.
چرا کرال کردن موتور بله ارزش بالایی دارد؟
- کیفیت اثباتشده در آزمون واقعی: تستهای تجربی روی ویسهای واقعی استعلام تدارکات نشان داد که موتور تشخیص گفتار بله تسلط بسیار بالایی بر محاورات عامیانه فارسی، اصطلاحات بازاری و لهجهها دارد.
- فقدان متد در رابط رسمی (Bot API): در مستندات رسمی بله (
docs.bale.ai)، شیءVoiceارسالی به رباتها فاقد فیلد ترنسکریپت بوده و تنها مشخصاتی نظیرfile_idرا برمیگرداند؛ بنابراین دسترسی به این موتور صرفاً از طریق شبیهسازی نشست کاربری امکانپذیر است. - تأیید وجود دکمه در وب بله: بررسی محیط
web.bale.aiاثبات کرد که قابلیت تبدیل ویس به متن در کلاینت تحت وب بله فعال است؛ این یافته امکان کپچر ترافیک و مهندسی معکوس و نیز استفاده از وب به عنوان پشتیبان را تضمین میکند. - دستور مدیریت پروژه در چینش اولویتها:
- اولویت اول: ارتباط مستقیم در لایه شبکه و پروتکل کاربری (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)
این دیاگرام نشان میدهد ویس ارسالی کاربر چگونه در خطلوله موازی پردازش شده و کاندید بله بدون ایجاد وقفه در سیستم ثبت استعلام مینشیند:
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)
برای جلوگیری از فرآیند پرریسک و پیچیده «آپلود مستقیم فایل از اکانت شخصی»، از الگوی بات رله استفاده میشود:
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)
این فلو تضمین میکند که حتی در بدترین سناریوها (تغییر پروتکل بله، بن شدن اکانت یا قطعی سرور بله)، سیستم اصلی تدارکات دچار حتی یک ثانیه قطعی نخواهد شد:
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)
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
- قرارداد ارتباطی: تابع
_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
- متغیرهای محیطی با پیشوند
PROCUREMENT_BOT_STT_PROVIDER_N_*(که N اعداد ۱ تا ۳ است) خوانده میشوند. - یک پرووایدر تنها زمانی فعال میشود که هر دو فیلد
BASE_URLوAPI_KEYمقداردهی شده باشند. بنابراین با خالی کردن این دو فیلد، پرووایدر بله فوراً از مدار خارج میشود (Rollback آنی).
۲. مشخصات قطعی کشفشده از پروتکل بله
در بررسی و راستیآزمایی مخازن PyPI روی پکیج bale-sdk (نسخه 0.1.1)، متد و ساختار فیلدهای ترنسکریپشن دقیقاً استخراج شده است:
- فضای نام و سرویس:
bale.turing.v1.AI - متد باینری:
GetTranscript - اسکیمای درخواست (
GetTranscriptRequest):message GetTranscriptRequest { Media voice = 1; // آبجکت فایل صدا (شناسه و خصوصیات باینری) OutPeer outPeer = 2; // مقصد چت (پیر چت شخصی یا بات رله) MessageId messageId = 3;// شناسه پیام ثبتشده در چت } - اسکیمای پاسخ (
GetTranscriptResponse):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)
ایجنت قبل از راهاندازی، این ۵ مورد را از انسان دریافت میکند:
BALE_PHONE: شماره سیمکارت شرکتی اختصاصی جهت ورود به اکانت کاربری.BALE_OTP_CODE: کد پیامکی ۶ رقمی یکبارمصرف در زمان اجرای اسکریپت ورود اولیه.BALE_RELAY_BOT_TOKEN: توکن بات رله اختصاصی که از BotFather بله دریافت شده است.BALE_STT_INTERNAL_KEY: کلید امنیتی تصادفی برای هدرAuthorization: Bearer <KEY>.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)
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)
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)
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
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
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 بات اصلی
اتصال به بات اصلی بدون دستکاری حتی ۱ خط از کدهای پایتون بات انجام میپذیرد:
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.