first commit

This commit is contained in:
2026-03-19 22:54:29 +03:30
parent 1a178f39b7
commit 035bc6f74d
91 changed files with 3821 additions and 130 deletions
+393
View File
@@ -0,0 +1,393 @@
# مستند سیستم RAG — پایگاه دانش CropLogic
## فهرست
1. [معرفی کلی](#معرفی-کلی)
2. [معماری و ساختار](#معماری-و-ساختار)
3. [منابع داده](#منابع-داده)
4. [پایپ‌لاین Embedding](#پایپلاین-embedding)
5. [نحوه اجرا](#نحوه-اجرا)
6. [فلوی پیام کاربر](#فلوی-پیام-کاربر)
7. [API Endpoint](#api-endpoint)
8. [تنظیمات](#تنظیمات)
9. [ایزوله‌سازی کاربران](#ایزولهسازی-کاربران)
10. [سرویس‌های توصیه](#سرویسهای-توصیه)
---
## معرفی کلی
سیستم RAG در CropLogic یک چت هوشمند کشاورزی است که:
- **دانش پایه کشاورزی** را embed و ذخیره می‌کند
- **داده‌های خاک و هواشناسی هر کاربر** را از DB می‌خواند و embed می‌کند
- وقتی کاربر سوال می‌پرسد، **اطلاعات مرتبط** را بازیابی و به **LLM** ارسال می‌کند
**Vector Store:** Qdrant
**API Provider:** GapGPT (با fallback به Avalai) — Adapter Pattern
### پایگاه‌های دانش مجزا
سیستم از **سه پایگاه دانش** مجزا استفاده می‌کند:
| KB | توضیح | فایل Tone |
|----|-------|-----------|
| `chat` | چت عمومی و پاسخ به سوالات متنوع | `config/tones/chat_tone.txt` |
| `irrigation` | توصیه‌های آبیاری (فرمت JSON) | `config/tones/irrigation_tone.txt` |
| `fertilization` | توصیه‌های کودهی (فرمت JSON) | `config/tones/fertilization_tone.txt` |
تشخیص هوشمند KB از روی کلمات کلیدی سوال (آبیاری، آب، کود، NPK).
---
## معماری و ساختار
```
rag/
├── config.py # بارگذاری تنظیمات از rag_config.yaml
├── api_provider.py # Adapter Pattern برای GapGPT/Avalai
├── client.py # ساخت کلاینت Qdrant
├── chunker.py # تکه‌تکه کردن متن
├── embedding.py # تعبیه‌سازی متن
├── vector_store.py # ذخیره و جستجو در Qdrant (با فیلتر kb_name)
├── user_data.py # خواندن داده‌های خاک/سنسور/هواشناسی از DB
├── ingest.py # پایپ‌لاین: خواندن → چانک → embed → ذخیره
├── retrieve.py # بازیابی: embed کوئری → جستجو
├── chat.py # ساخت context و چت استریمی با LLM
├── views.py # API endpoint
├── urls.py # مسیریابی
├── tasks.py # تسک Celery
├── services/ # سرویس‌های توصیه (بدون API)
│ ├── irrigation.py # توصیه آبیاری
│ └── fertilization.py # توصیه کودهی
└── management/commands/
└── rag_ingest.py
```
فایل‌های تنظیمات:
```
config/
├── rag_config.yaml
├── tones/
│ ├── chat_tone.txt
│ ├── irrigation_tone.txt
│ └── fertilization_tone.txt
└── knowledge_base/
├── chat/
├── irrigation/
└── fertilization/
```
---
## منابع داده
سیستم از **چهار منبع** داده تغذیه می‌شود:
### 1. لحن‌های مجزا — `config/tones/`
هر KB یک فایل لحن مخصوص دارد که سبک خروجی LLM را تعریف می‌کند.
ذخیره با: `sensor_uuid = __global__`, `kb_name = chat|irrigation|fertilization`
### 2. پایگاه‌های دانش — `config/knowledge_base/`
- `chat/`: دانش عمومی کشاورزی
- `irrigation/`: دانش تخصصی آبیاری (ET0، بارش، رطوبت)
- `fertilization/`: دانش تخصصی کودهی (NPK، pH، نوع خاک)
ذخیره با: `sensor_uuid = __global__`, `kb_name = chat|irrigation|fertilization`
### 3. داده‌های خاک کاربر — از DB
برای هر سنسور:
- `SensorData`: رطوبت، دما، pH، EC، NPK
- `SoilLocation`: مختصات جغرافیایی
- `SoilDepthData`: داده‌های خاک در سه عمق
تابع `build_user_soil_text()` این داده‌ها را به متن فارسی تبدیل می‌کند.
ذخیره با: `sensor_uuid = {uuid واقعی}`, `kb_name = __all__`
### 4. داده‌های هواشناسی کاربر — از DB
- `WeatherForecast`: پیش‌بینی ۷ روز آینده (دما، بارش، رطوبت، باد، ET0)
تابع `build_user_weather_text()` این داده‌ها را به متن فارسی تبدیل می‌کند.
ذخیره با: `sensor_uuid = {uuid واقعی}`, `kb_name = __all__`
---
## پایپلاین Embedding
```
منابع → load_sources() → chunk_text() → embed_texts() → Qdrant
```
1. **بارگذاری منابع** (`ingest.py:load_sources`):
- لحن‌ها از `config/tones/`
- KB‌ها از `config/knowledge_base/`
- داده‌های کاربران از DB (`user_data.py`)
2. **چانک کردن** (`chunker.py`):
- حداکثر ۵۰۰ توکن هر چانک
- ۵۰ توکن همپوشانی
3. **Embedding** (`embedding.py`):
- استفاده از `api_provider.get_embedding_client()`
- مدل: `text-embedding-3-small`
- بچ‌سایز: ۳۲
4. **ذخیره در Qdrant** (`vector_store.py`):
- هر point: `{id, vector[1536], payload{text, source, sensor_uuid, kb_name, chunk_index}}`
---
## نحوه اجرا
### دستی
```bash
python manage.py rag_ingest --recreate
```
### دوره‌ای (Celery Beat)
تسک `rag_ingest_task` هر ۶ ساعت اجرا می‌شود و داده‌های جدید را embed می‌کند.
---
## فلوی پیام کاربر
```
POST /api/rag/chat/ {message, sensor_uuid}
1. تشخیص KB از روی کلمات کلیدی (_detect_kb_intent)
2. بارگذاری داده‌های فعلی کاربر از DB:
- build_user_soil_text(sensor_uuid)
- build_user_weather_text(sensor_uuid)
3. Embed کردن سوال (embed_single)
4. جستجو در Qdrant با فیلتر:
- sensor_uuid = {uuid کاربر} OR __global__
- kb_name = {detected_kb} OR __all__
5. ساخت context:
[داده‌های فعلی خاک] + [پیش‌بینی هواشناسی] + [متن‌های مرجع از RAG]
6. ارسال به LLM (GapGPT):
system_prompt = tone + دستورالعمل + context
7. StreamingHttpResponse → کاربر
```
---
## API Endpoint
### POST `/api/rag/chat/`
**Request:**
```json
{
"message": "وضعیت خاک من چطوره؟",
"sensor_uuid": "550e8400-e29b-41d4-a716-446655440000"
}
```
**Response:** Stream متنی (text/plain)
---
## تنظیمات
### `config/rag_config.yaml`
```yaml
embedding:
provider: "gapgpt" # gapgpt یا avalai
model: "text-embedding-3-small"
base_url: "https://api.gapgpt.app/v1"
api_key_env: "GAPGPT_API_KEY"
avalai_base_url: "https://api.avalai.ir/v1"
avalai_api_key_env: "AVALAI_API_KEY"
qdrant:
host: "localhost"
port: 6333
collection_name: "croplogic_kb"
vector_size: 1536
chunking:
max_chunk_tokens: 500
overlap_tokens: 50
llm:
model: "gpt-4o"
base_url: "https://api.gapgpt.app/v1"
api_key_env: "GAPGPT_API_KEY"
avalai_base_url: "https://api.avalai.ir/v1"
avalai_api_key_env: "AVALAI_API_KEY"
knowledge_bases:
chat:
path: "config/knowledge_base/chat"
tone_file: "config/tones/chat_tone.txt"
irrigation:
path: "config/knowledge_base/irrigation"
tone_file: "config/tones/irrigation_tone.txt"
fertilization:
path: "config/knowledge_base/fertilization"
tone_file: "config/tones/fertilization_tone.txt"
```
### متغیرهای محیطی
| متغیر | توضیح |
|-------|-------|
| `GAPGPT_API_KEY` | کلید API برای GapGPT |
| `AVALAI_API_KEY` | کلید API برای Avalai (fallback) |
| `QDRANT_HOST` | آدرس Qdrant |
| `QDRANT_PORT` | پورت Qdrant |
---
## ایزوله‌سازی کاربران
- هر چانک یک فیلد `sensor_uuid` در metadata دارد
- داده‌های عمومی: `sensor_uuid = __global__`
- داده‌های کاربر: `sensor_uuid = {uuid واقعی}`
- هنگام جستجو، فیلتر `should` اعمال می‌شود:
- `sensor_uuid = {uuid کاربر}` OR `__global__`
- `kb_name = {detected_kb}` OR `__all__`
- نتیجه: هر کاربر فقط داده‌های خودش + دانش عمومی را می‌بیند
---
## سرویس‌های توصیه
سرویس‌های آبیاری و کودهی **بدون API** هستند و از RAG استفاده می‌کنند.
### توصیه آبیاری
```python
from rag.services import get_irrigation_recommendation
result = get_irrigation_recommendation(
sensor_uuid="550e8400-...",
query="توصیه آبیاری برای مزرعه من چیست؟" # اختیاری
)
```
**خروجی:**
```python
{
"irrigation_needed": True,
"amount_mm": 25.0,
"reason": "رطوبت خاک پایین و بارش پیش‌بینی نشده",
"next_check_date": "2026-03-20",
"raw_response": "..."
}
```
### توصیه کودهی
```python
from rag.services import get_fertilization_recommendation
result = get_fertilization_recommendation(
sensor_uuid="550e8400-...",
query="توصیه کودهی برای مزرعه من چیست؟" # اختیاری
)
```
**خروجی:**
```python
{
"fertilizer_needed": True,
"fertilizer_type": "NPK 20-10-10",
"amount_kg_per_hectare": 150.0,
"reason": "سطح ازت پایین",
"npk_status": {
"nitrogen": "low",
"phosphorus": "normal",
"potassium": "normal"
},
"raw_response": "..."
}
```
---
## نمودار معماری
```
┌─────────────────────────────────────────────────────────┐
│ منابع داده │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ tones/ │ │ knowledge_ │ │ Django DB │ │
│ │ 3 files │ │ base/ │ │ SensorData │ │
│ │ │ │ chat/irrig/ │ │ SoilLocation │ │
│ │ │ │ fertiliz/ │ │ SoilDepthData │ │
│ │ │ │ │ │ WeatherForecast │ │
│ └────┬─────┘ └──────┬───────┘ └────────┬──────────┘ │
│ │ │ │ │
│ └───────────┬────┘ │ │
│ __global__ sensor_uuid │
│ kb_name=chat/ kb_name=__all__ │
│ irrigation/ │
│ fertilization │
└───────────────┬────────────────────────┬────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ ingest pipeline │
│ │
│ load_sources() → chunk_text() → embed_texts() │
│ (با Adapter Pattern: GapGPT/Avalai) │
│ │
│ کامند: python manage.py rag_ingest --recreate │
│ تسک: rag_ingest_task.delay(recreate=True) │
└────────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Qdrant │
│ collection: croplogic_kb │
│ │
│ هر point = {id, vector[1536], payload{text, │
│ source, sensor_uuid, kb_name, │
│ chunk_index}} │
└────────────────────────┬────────────────────────────────┘
(هنگام سوال کاربر)
┌─────────────────────────────────────────────────────────┐
│ فلوی پاسخ به کاربر │
│ │
│ 1. POST /api/rag/chat/ {message, sensor_uuid} │
│ 2. تشخیص KB از کلمات کلیدی (_detect_kb_intent) │
│ 3. build_user_soil_text() + build_user_weather_text() │
│ 4. embed_single(message) → query vector │
│ 5. Qdrant search با فیلتر sensor_uuid + kb_name │
│ 6. system_prompt = tone + دستورالعمل + context │
│ 7. GapGPT LLM (gpt-4o) → streaming response │
│ 8. StreamingHttpResponse → کاربر │
└─────────────────────────────────────────────────────────┘
```
---
**تغییرات اخیر:**
- ✅ Adapter Pattern برای سوئیچ بین GapGPT و Avalai
- ✅ سه پایگاه دانش مجزا (chat/irrigation/fertilization)
- ✅ داده‌های هواشناسی embed می‌شوند
- ✅ فیلتر `kb_name` در جستجوی Qdrant
- ✅ سرویس‌های توصیه آبیاری و کودهی (بدون API)