# دستیار هوشمند سایت (چت‌بات) سرویس گفتگوی محتوای سایت «گروه ساختمانی آرامش» — بازدیدکننده به فارسی سؤال می‌پرسد و دستیار **فقط از روی محتوای خود سایت** (مقاله‌ها، پروژه‌ها، خدمات، سؤالات متداول، اطلاعات تماس و…) پاسخ می‌دهد؛ اگر پاسخ در محتوا نباشد، به‌جای ساخت جواب، صادقانه اعلام می‌کند. این سرویس پورت خط لولهٔ پروژهٔ `llm-srerver-chatbot` (نسخه v13) با چهار تغییر اصلی برای این سایت است: 1. **پایگاه sparse به‌جای SPLADE از BM25 استفاده می‌کند** — SPLADE انگلیسی‌محور است و روی فارسی عملاً بی‌فایده؛ BM25 با نرمال‌سازی فارسی (ی/ک عربی، اعراب، ارقام، نیم‌فاصله) جایگزین شده است. 2. **ذخیره‌ساز برداری pgvector است، نه FAISS** — بردارها در یک دیتابیس جدا (`aramesh_chatbot`) روی همان نمونه Postgres نگهداری می‌شوند؛ همگام‌سازی افزایشی است (فقط سطرهای جدید/تغییریافته امبد می‌شوند)، بکاپ‌گیری با خود دیتابیس است و فایل ایندکس جدا وجود ندارد. 3. **تأخیر کمتر** — مسیریابی `alpha` با LLM و گسترش چند‌پرسشی (multi-query) به‌صورت پیش‌فرض خاموش‌اند (با متغیر محیطی قابل فعال‌سازی)؛ بازنویسی پرسش فقط وقتی تاریخچه گفتگو وجود دارد اجرا می‌شود. 4. **استریم واقعی توکن‌به‌توکن** با `astream_events` به‌جای دیف پیام‌های کامل. ## خط لوله هر پیام ``` پرسش کاربر │ ├─[اگر تاریخچه هست] بازنویسی پرسش با LLM (حل ضمیرها و اشاره‌ها: «اون دومی» → موضوع واقعی) │ ├─ بازیابی ترکیبی: pgvector (معنایی، کسینوسی روی HNSW — MiniLM چندزبانه) │ + BM25 (کلیدواژه، متن نرمال‌شده فارسی) │ نمره‌ها min-max → ترکیب α×معنایی + (1-α)×کلیدواژه │ ├─ برش خوشه‌ای (K-Means K=2 + silhouette ≥ 0.55) — مرز طبیعی کیفیت │ ├─ ری‌رنک با BAAI/bge-reranker-v2-m3 → نگه‌داشتن ۳ سند برتر │ └─ تولید پاسخ فارسی با LLM (قواعد تکیه بر داده + معرفی صفحه مرتبط + پیام جایگزین مشخص) ``` ایندکس از محتوای PostgreSQL سایت ساخته می‌شود (`articles` منتشرشده، `projects`، `services`، `faqs`، `brand`، `offices`، `investment_models`، `process_steps`، `why_us`، `team`، `testimonials`، `timeline`، `story`). متن‌های بلند به قطعات ~۷۰۰ نویسه‌ای هم‌پوشان برش می‌خورند. - **ایندکس برداری در pgvector**: جدول `chatbot_chunks` در دیتابیس اختصاصی `aramesh_chatbot` (روی همان نمونه Postgres) با ستون `embedding vector(384)` و ایندکس HNSW کسینوسی؛ هر قطعه `doc_id` پایدار و `content_hash` دارد و همگام‌سازی فقط سطرهای جدید/تغییریافته را امبد می‌کند. دیتابیس و جدول در شروع سرویس خودکار ساخته می‌شوند. - **بازسازی خودکار**: یک حلقهٔ پس‌زمینه هر `INDEX_REFRESH_SECONDS` ثانیه محتوا را چک می‌کند و اگر ادمین چیزی را در پنل عوض کرده باشد، فقط تغییرات همگام می‌شود (امبد اجباری همه: `POST /chat/reindex`). - **ماندگاری گفتگو**: در حافظهٔ همان پروسه (MemorySaver لنگ‌گراف) — با ری‌استارت پاک می‌شود. ## متغیرهای محیطی (در `.env` ریشه پروژه) | متغیر | پیش‌فرض | توضیح | |---|---|---| | `LLM_BASE_URL` | خالی | **اجباری** — نشانی سرویس LLM سازگار با OpenAI. بدون آن چت‌بات بالا می‌آید ولی پاسخ نمی‌دهد | | `LLM_API_KEY` | خالی | کلید API (برای سرور داخلی `not-needed` بگذارید) | | `LLM_MODEL` | `openai/gpt-oss-120b` | نام مدل | | `CHAT_REINDEX_TOKEN` | خالی | توکن `POST /chat/reindex` — اگر خالی باشد آن مسیر ۴۰۳ می‌دهد | | `CHATBOT_DB_NAME` | `aramesh_chatbot` | نام دیتابیس اختصاصی چت‌بات روی همان نمونه Postgres (بردارها اینجا می‌روند) | | `EMBEDDING_DIM` | `384` | بعد بردار مدل امبدینگ — با تعویض مدل امبدینگ باید هماهنگ شود | | `RERANKER_ENABLED` | `true` | روی سرور کم‌رم (زیر ~۴GB) `false` بگذارید | | `ALPHA_DENSE` | `0.5` | وزن جست‌وجوی معنایی در ترکیب نمره‌ها (۰ تا ۱) | | `BEST_OF_K` | `3` | تعداد سند نهایی که به LLM می‌رود | | `INDEX_REFRESH_SECONDS` | `600` | بازسازی خودکار ایندکس پس از تغییر محتوا (۰ = خاموش) | | `RATE_LIMIT_MESSAGES` / `RATE_LIMIT_WINDOW` | `20` / `300` | سقف پیام هر IP در پنجره زمانی (ثانیه) | `DATABASE_URL` در داکر از `docker-compose.yml` تزریق می‌شود — دیتابیس سایت فقط خوانده می‌شود و نوشتن (بردارها) فقط در دیتابیس جدا `aramesh_chatbot` انجام می‌گیرد. ## پیش‌نیاز pgvector سرویس `db` استک از ایمیج `pgvector/pgvector:pg17` استفاده می‌کند (همان postgres:17 + افزونهٔ vector). اگر Postgres سایت روی نمونهٔ دیگری است، باید افزونهٔ pgvector روی آن فعال باشد؛ در غیر این صورت شروع چت‌بات با خطای واضح متوقف می‌شود. ## مسیرها | مسیر | توضیح | |---|---| | `GET /chat/stream?message=…&thread_id=…` | استریم SSE توکن‌به‌توکن پاسخ (`{"content": …}` و در پایان `{"done": true}`) | | `GET /chat/suggestions` | ۴ پیشنهاد پرسش (کش ۱۰ دقیقه؛ جایگزین: خود سؤالات متداول) | | `GET /chat/eval?message=…` | مخصوص ارزیابی RAGAS — پاسخ + اسناد بازیابی‌شده با thread تازه | | `GET /chat/healthz` | وضعیت ایندکس/LLM/تعداد اسناد | | `POST /chat/reindex` | بازسازی اجباری ایندکس (هدر `X-Reindex-Token`) | مسیرهای `GET /chat/{scope}/stream|eval|suggestions` هم برای سازگاری با اسکریپت `eval_ragas.py` قدیمی موجود است — کافی است `--url` را به نشانی این سرویس بدهید. ## اجرا با داکر (بخشی از استک اصلی است — `docker compose up -d --build`): ```bash # در .env ریشه: LLM_BASE_URL=http://172.16.22.21:4460/v1 # یا هر سرویس سازگار با OpenAI LLM_API_KEY=not-needed ``` اولین بوت مدل‌ها را دانلود می‌کند (~۱.۵GB: امبدینگ + ری‌رنکر) و در والیوم `chatbot_cache` کش می‌شود؛ بوت‌های بعدی چند ثانیه طول می‌کشد. اجرای محلی بدون داکر: ```bash cd chatbot python -m venv .venv && source .venv/bin/activate # ویندوز: .venv\Scripts\activate pip install -r requirements.txt export DATABASE_URL="postgresql+psycopg://aramesh:PASS@localhost:5432/arameshplaza" export LLM_BASE_URL="http://…" LLM_MODEL="openai/gpt-oss-120b" uvicorn chatbot.app:api --port 8000 # از ریشه پوشه والد ``` ## منابع مورد نیاز - **رم**: با ری‌رنکر ~۴GB (مدل ۵۶۸M پارامتری روی CPU)؛ بدون ری‌رنکر ~۲GB - **CPU**: امبدینگ و ری‌رنک روی CPU اجرا می‌شوند؛ GPU لازم نیست - **شبکه**: دسترسی به PostgreSQL و به نشانی `LLM_BASE_URL` ## نکات امنیتی - دیتابیس سایت فقط **خواندن**؛ نوشتن فقط در دیتابیس اختصاصی `aramesh_chatbot` انجام می‌شود - محدودسازی نرخ per-IP فعال است (پیش‌فرض ۲۰ پیام در ۵ دقیقه) - CORS باز است ولی کوکی/اعتبارنامه‌ای رد و بدل نمی‌شود؛ در تولید همه‌چیز هم‌مبدأ (پشت Traefik) است