528 lines
15 KiB
Markdown
528 lines
15 KiB
Markdown
|
|
# توضیح خیلی ساده منطق RAG در پروژه
|
||
|
|
|
||
|
|
این فایل قرار است خیلی ساده بگوید RAG در این پروژه چطور کار میکند.
|
||
|
|
|
||
|
|
## اول: RAG یعنی چه؟
|
||
|
|
|
||
|
|
RAG یعنی:
|
||
|
|
|
||
|
|
1. سوال کاربر را میگیریم
|
||
|
|
2. متنهای مرتبط را از حافظه دانشی پیدا میکنیم
|
||
|
|
3. آن متنها را کنار سوال میگذاریم
|
||
|
|
4. بعد از مدل زبانی میخواهیم جواب بدهد
|
||
|
|
|
||
|
|
یعنی مدل فقط از حافظه خودش جواب نمیدهد؛
|
||
|
|
قبل از جواب دادن، اطلاعات مرتبط پروژه را هم میبیند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نقش فایل `rag/apps.py`
|
||
|
|
|
||
|
|
فایل `rag/apps.py` فقط اپ Django مربوط به RAG را ثبت میکند.
|
||
|
|
|
||
|
|
کار اصلیاش این است:
|
||
|
|
|
||
|
|
- اسم اپ را مشخص میکند: `rag`
|
||
|
|
- نام نمایشی اپ را مشخص میکند
|
||
|
|
|
||
|
|
پس:
|
||
|
|
|
||
|
|
- `rag/apps.py` منطق اصلی RAG را پیادهسازی نمیکند
|
||
|
|
- فقط میگوید این اپ در پروژه وجود دارد
|
||
|
|
|
||
|
|
منطق اصلی RAG بیشتر در این فایلهاست:
|
||
|
|
|
||
|
|
- `rag/views.py`
|
||
|
|
- `rag/chat.py`
|
||
|
|
- `rag/retrieve.py`
|
||
|
|
- `rag/ingest.py`
|
||
|
|
- `rag/embedding.py`
|
||
|
|
- `rag/vector_store.py`
|
||
|
|
- `rag/user_data.py`
|
||
|
|
- `rag/config.py`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## تصویر خیلی ساده از کل جریان
|
||
|
|
|
||
|
|
RAG در این پروژه دو بخش اصلی دارد:
|
||
|
|
|
||
|
|
### 1) آمادهسازی دانش
|
||
|
|
|
||
|
|
در این بخش سیستم اطلاعات را جمع میکند و داخل دیتابیس برداری ذخیره میکند.
|
||
|
|
|
||
|
|
مراحل:
|
||
|
|
|
||
|
|
1. فایلهای دانش را میخواند
|
||
|
|
2. متنها را خرد میکند
|
||
|
|
3. هر تکه را تبدیل به embedding میکند
|
||
|
|
4. embeddingها را داخل Qdrant ذخیره میکند
|
||
|
|
|
||
|
|
این کار بیشتر در `rag/ingest.py` انجام میشود.
|
||
|
|
|
||
|
|
### 2) جواب دادن به سوال کاربر
|
||
|
|
|
||
|
|
در این بخش وقتی کاربر سوال میپرسد:
|
||
|
|
|
||
|
|
1. سوال embedding میشود
|
||
|
|
2. متنهای نزدیک و مرتبط پیدا میشوند
|
||
|
|
3. دادههای کاربر هم اضافه میشود
|
||
|
|
4. همه اینها به مدل زبانی داده میشود
|
||
|
|
5. مدل جواب را به صورت stream برمیگرداند
|
||
|
|
|
||
|
|
این کار بیشتر در `rag/chat.py` و `rag/retrieve.py` انجام میشود.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## بخش اول: سیستم چطور دانش را آماده میکند؟
|
||
|
|
|
||
|
|
### فایل اصلی: `rag/ingest.py`
|
||
|
|
|
||
|
|
این فایل کارش این است که اطلاعات را وارد سیستم RAG کند.
|
||
|
|
|
||
|
|
### از کجا اطلاعات میآید؟
|
||
|
|
|
||
|
|
سیستم این منابع را میخواند:
|
||
|
|
|
||
|
|
- فایلهای پایگاه دانش
|
||
|
|
- فایل لحن یا tone
|
||
|
|
- دادههای خاک هر کاربر
|
||
|
|
- دادههای هواشناسی هر کاربر
|
||
|
|
|
||
|
|
### پایگاه دانش یعنی چه؟
|
||
|
|
|
||
|
|
پایگاه دانش یعنی متنهایی که پروژه از قبل دارد.
|
||
|
|
|
||
|
|
مثلا:
|
||
|
|
|
||
|
|
- اطلاعات عمومی چت
|
||
|
|
- اطلاعات آبیاری
|
||
|
|
- اطلاعات کودهی
|
||
|
|
|
||
|
|
در تنظیمات، برای هر بخش یک knowledge base تعریف شده است.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## مرحله 1: خواندن منابع
|
||
|
|
|
||
|
|
تابع `load_sources()` در `rag/ingest.py` منابع را جمع میکند.
|
||
|
|
|
||
|
|
خروجی این تابع تقریبا این شکلی است:
|
||
|
|
|
||
|
|
- شناسه منبع
|
||
|
|
- متن منبع
|
||
|
|
- شناسه سنسور یا کاربر
|
||
|
|
- نام پایگاه دانش
|
||
|
|
|
||
|
|
نکته مهم:
|
||
|
|
|
||
|
|
- دادههای عمومی با `__global__` ذخیره میشوند
|
||
|
|
- دادههای شخصی هر کاربر با `sensor_uuid` خودش ذخیره میشوند
|
||
|
|
- دادههای کاربری معمولا با `__all__` در `kb_name` علامت میخورند
|
||
|
|
|
||
|
|
این کار باعث میشود بعدا سیستم بداند هر متن برای چه کسی یا چه بخشی بوده است.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## مرحله 2: خرد کردن متن
|
||
|
|
|
||
|
|
### فایل: `rag/chunker.py`
|
||
|
|
|
||
|
|
متنهای طولانی مستقیم وارد جستجو نمیشوند.
|
||
|
|
اول آنها را به تکههای کوچکتر تبدیل میکنیم.
|
||
|
|
|
||
|
|
چرا؟
|
||
|
|
|
||
|
|
چون:
|
||
|
|
|
||
|
|
- جستجو دقیقتر میشود
|
||
|
|
- embedding بهتر میشود
|
||
|
|
- مدل فقط بخشهای لازم را میبیند
|
||
|
|
|
||
|
|
مثلا یک فایل بلند به چند chunk تبدیل میشود.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## مرحله 3: ساخت embedding
|
||
|
|
|
||
|
|
### فایل: `rag/embedding.py`
|
||
|
|
|
||
|
|
هر chunk متنی به یک لیست عددی تبدیل میشود.
|
||
|
|
به این لیست عددی میگوییم embedding.
|
||
|
|
|
||
|
|
خیلی ساده:
|
||
|
|
|
||
|
|
- متن شبیه به هم -> embedding شبیه به هم
|
||
|
|
- متن متفاوت -> embedding متفاوت
|
||
|
|
|
||
|
|
پس بعدا اگر کاربر سوالی شبیه یک متن بپرسد،
|
||
|
|
سیستم میتواند آن متن را پیدا کند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## مرحله 4: ذخیره در Qdrant
|
||
|
|
|
||
|
|
### فایل: `rag/vector_store.py`
|
||
|
|
|
||
|
|
بعد از ساخت embedding، دادهها داخل Qdrant ذخیره میشوند.
|
||
|
|
|
||
|
|
Qdrant در این پروژه نقش حافظه برداری را دارد.
|
||
|
|
|
||
|
|
برای هر chunk این چیزها ذخیره میشود:
|
||
|
|
|
||
|
|
- خود متن
|
||
|
|
- embedding
|
||
|
|
- منبع متن
|
||
|
|
- شماره chunk
|
||
|
|
- `sensor_uuid`
|
||
|
|
- `kb_name`
|
||
|
|
|
||
|
|
این metadata خیلی مهم است؛
|
||
|
|
چون کمک میکند بعدا فقط دادههای مرتبط برگردند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## دستور ورود اطلاعات
|
||
|
|
|
||
|
|
### فایل: `rag/management/commands/rag_ingest.py`
|
||
|
|
|
||
|
|
این فایل یک command جنگو دارد که ingestion را اجرا میکند.
|
||
|
|
|
||
|
|
یعنی اگر این دستور اجرا شود:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python manage.py rag_ingest
|
||
|
|
```
|
||
|
|
|
||
|
|
سیستم:
|
||
|
|
|
||
|
|
- منابع را میخواند
|
||
|
|
- chunk میکند
|
||
|
|
- embedding میسازد
|
||
|
|
- داخل Qdrant ذخیره میکند
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## بخش دوم: وقتی کاربر سوال میپرسد چه میشود؟
|
||
|
|
|
||
|
|
### ورودی API
|
||
|
|
|
||
|
|
### فایل: `rag/views.py`
|
||
|
|
|
||
|
|
در این فایل endpoint چت وجود دارد.
|
||
|
|
|
||
|
|
کارش این است که از کاربر این اطلاعات را بگیرد:
|
||
|
|
|
||
|
|
- `service_id`
|
||
|
|
- `query`
|
||
|
|
- `user_id` یا `sensor_uuid`
|
||
|
|
|
||
|
|
بعد چند بررسی انجام میشود:
|
||
|
|
|
||
|
|
- آیا سوال خالی نیست؟
|
||
|
|
- آیا `service_id` معتبر است؟
|
||
|
|
- اگر سرویس نیاز به داده کاربر دارد، آیا `user_id` داده شده؟
|
||
|
|
|
||
|
|
اگر همه چیز درست باشد،
|
||
|
|
در نهایت `chat_rag_stream()` صدا زده میشود.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## `service_id` چرا مهم است؟
|
||
|
|
|
||
|
|
چون سیستم چند نوع سرویس دارد.
|
||
|
|
|
||
|
|
مثلا:
|
||
|
|
|
||
|
|
- سرویس چت عمومی
|
||
|
|
- سرویس آبیاری
|
||
|
|
- سرویس کودهی
|
||
|
|
|
||
|
|
هر سرویس میتواند اینها را مشخص کند:
|
||
|
|
|
||
|
|
- از کدام knowledge base استفاده شود
|
||
|
|
- از چه مدل زبانی استفاده شود
|
||
|
|
- آیا دادههای شخصی کاربر لازم است یا نه
|
||
|
|
- چه tone یا system prompt استفاده شود
|
||
|
|
|
||
|
|
این تنظیمات در `rag/config.py` و فایل `config/rag_config.yaml` مدیریت میشوند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ساخت context
|
||
|
|
|
||
|
|
### فایل اصلی: `rag/chat.py`
|
||
|
|
|
||
|
|
مهمترین بخش پاسخگویی همینجاست.
|
||
|
|
|
||
|
|
تابع مهم: `build_rag_context()`
|
||
|
|
|
||
|
|
این تابع یک context برای مدل میسازد.
|
||
|
|
|
||
|
|
این context از چند بخش ساخته میشود:
|
||
|
|
|
||
|
|
1. داده فعلی خاک کاربر
|
||
|
|
2. داده هواشناسی کاربر
|
||
|
|
3. متنهای مرتبط پیدا شده از RAG
|
||
|
|
|
||
|
|
یعنی مدل فقط سوال را نمیبیند؛
|
||
|
|
بلکه این اطلاعات کمکی را هم میبیند.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## داده خاک و هواشناسی کاربر از کجا میآید؟
|
||
|
|
|
||
|
|
### فایل: `rag/user_data.py`
|
||
|
|
|
||
|
|
این فایل اطلاعات کاربر را از دیتابیس پروژه میسازد.
|
||
|
|
|
||
|
|
دو تابع مهم:
|
||
|
|
|
||
|
|
- `build_user_soil_text(sensor_uuid)`
|
||
|
|
- `build_user_weather_text(sensor_uuid)`
|
||
|
|
|
||
|
|
کار این توابع:
|
||
|
|
|
||
|
|
- دادههای مدلهای پروژه را میخوانند
|
||
|
|
- آنها را به متن ساده تبدیل میکنند
|
||
|
|
|
||
|
|
چرا به متن؟
|
||
|
|
|
||
|
|
چون سیستم RAG در نهایت با متن کار میکند.
|
||
|
|
|
||
|
|
پس حتی دادههای دیتابیس هم به متن تبدیل میشوند تا:
|
||
|
|
|
||
|
|
- embed شوند
|
||
|
|
- یا مستقیم داخل context قرار بگیرند
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## پیدا کردن متنهای مرتبط
|
||
|
|
|
||
|
|
### فایل: `rag/retrieve.py`
|
||
|
|
|
||
|
|
در اینجا تابع `search_with_query()` کار اصلی بازیابی را انجام میدهد.
|
||
|
|
|
||
|
|
مراحلش ساده است:
|
||
|
|
|
||
|
|
1. سوال کاربر embedding میشود
|
||
|
|
2. یک جستجوی شباهت در Qdrant انجام میشود
|
||
|
|
3. فقط متنهای مجاز برگردانده میشوند
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## چرا گفتیم "متنهای مجاز"؟
|
||
|
|
|
||
|
|
چون این پروژه داده کاربر دارد و نباید اطلاعات یک کاربر به کاربر دیگر برسد.
|
||
|
|
|
||
|
|
برای همین موقع جستجو فیلتر گذاشته میشود.
|
||
|
|
|
||
|
|
فیلترها معمولا اینها هستند:
|
||
|
|
|
||
|
|
- `sensor_uuid`
|
||
|
|
- `kb_name`
|
||
|
|
|
||
|
|
یعنی سیستم فقط اینها را برمیگرداند:
|
||
|
|
|
||
|
|
- دادههای عمومی (`__global__`)
|
||
|
|
- دادههای همان کاربر
|
||
|
|
- دادههای همان knowledge base
|
||
|
|
|
||
|
|
پس این بخش برای امنیت و جداسازی اطلاعات خیلی مهم است.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## جستجو در Qdrant چطور انجام میشود؟
|
||
|
|
|
||
|
|
### فایل: `rag/vector_store.py`
|
||
|
|
|
||
|
|
تابع `search()` در این فایل:
|
||
|
|
|
||
|
|
- query vector را میگیرد
|
||
|
|
- فیلترها را میسازد
|
||
|
|
- از Qdrant نتیجه میگیرد
|
||
|
|
|
||
|
|
بعد نتیجهها را به شکل ساده برمیگرداند:
|
||
|
|
|
||
|
|
- `id`
|
||
|
|
- `score`
|
||
|
|
- `text`
|
||
|
|
- `metadata`
|
||
|
|
|
||
|
|
`score` یعنی میزان شباهت.
|
||
|
|
هرچه بیشتر باشد، یعنی متن به سوال نزدیکتر است.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## بعد از بازیابی چه میشود؟
|
||
|
|
|
||
|
|
### دوباره در `rag/chat.py`
|
||
|
|
|
||
|
|
بعد از این که متنهای مرتبط پیدا شدند:
|
||
|
|
|
||
|
|
- متنهای مرجع جمع میشوند
|
||
|
|
- داده کاربر هم کنار آنها قرار میگیرد
|
||
|
|
- tone و system prompt هم اضافه میشود
|
||
|
|
|
||
|
|
در آخر یک پیام system ساخته میشود که به مدل میگوید:
|
||
|
|
|
||
|
|
- از دادههای خاک استفاده کن
|
||
|
|
- از متنهای مرجع استفاده کن
|
||
|
|
- با زبان کاربر جواب بده
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## تولید جواب نهایی
|
||
|
|
|
||
|
|
### تابع: `chat_rag_stream()`
|
||
|
|
|
||
|
|
این تابع:
|
||
|
|
|
||
|
|
1. تنظیمات سرویس را میخواند
|
||
|
|
2. context را میسازد
|
||
|
|
3. پیام system و user را آماده میکند
|
||
|
|
4. به مدل زبانی درخواست میفرستد
|
||
|
|
5. جواب را به صورت stream برمیگرداند
|
||
|
|
|
||
|
|
پس جواب نهایی فقط از خود مدل نیست؛
|
||
|
|
بلکه از ترکیب اینها ساخته میشود:
|
||
|
|
|
||
|
|
- سوال کاربر
|
||
|
|
- دادههای فعلی کاربر
|
||
|
|
- متنهای مرجع RAG
|
||
|
|
- لحن و دستور سیستم
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## tone چیست؟
|
||
|
|
|
||
|
|
tone یعنی لحن پاسخ.
|
||
|
|
|
||
|
|
مثلا سیستم میتواند مشخص کند:
|
||
|
|
|
||
|
|
- رسمی جواب بده
|
||
|
|
- ساده جواب بده
|
||
|
|
- تخصصی جواب بده
|
||
|
|
|
||
|
|
فایلهای tone از روی knowledge base یا service خوانده میشوند.
|
||
|
|
|
||
|
|
پس tone روی سبک جواب اثر دارد،
|
||
|
|
نه روی اصل جستجو.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نقش `rag/config.py`
|
||
|
|
|
||
|
|
این فایل تنظیمات را بارگذاری میکند.
|
||
|
|
|
||
|
|
مثلا:
|
||
|
|
|
||
|
|
- مدل embedding چیست
|
||
|
|
- Qdrant کجاست
|
||
|
|
- اندازه vector چقدر است
|
||
|
|
- chunking چگونه باشد
|
||
|
|
- سرویسها چه هستند
|
||
|
|
- هر سرویس از کدام knowledge base استفاده کند
|
||
|
|
|
||
|
|
یعنی این فایل مغز تنظیمات سیستم است.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## خلاصه خیلی ساده کل مسیر
|
||
|
|
|
||
|
|
اگر بخواهیم خیلی خلاصه بگوییم:
|
||
|
|
|
||
|
|
### مرحله آمادهسازی
|
||
|
|
|
||
|
|
1. فایلها و دادههای کاربر خوانده میشوند
|
||
|
|
2. متنها chunk میشوند
|
||
|
|
3. embedding ساخته میشود
|
||
|
|
4. داخل Qdrant ذخیره میشوند
|
||
|
|
|
||
|
|
### مرحله پاسخگویی
|
||
|
|
|
||
|
|
1. کاربر سوال میپرسد
|
||
|
|
2. سوال embedding میشود
|
||
|
|
3. متنهای مشابه پیدا میشوند
|
||
|
|
4. داده خاک و هواشناسی کاربر هم اضافه میشود
|
||
|
|
5. همه اینها به LLM داده میشود
|
||
|
|
6. LLM جواب نهایی را میسازد
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## فرق این پروژه با یک چت ساده
|
||
|
|
|
||
|
|
اگر چت ساده بود:
|
||
|
|
|
||
|
|
- مدل فقط با دانستههای خودش جواب میداد
|
||
|
|
|
||
|
|
ولی اینجا:
|
||
|
|
|
||
|
|
- مدل به دادههای واقعی پروژه دسترسی دارد
|
||
|
|
- دادههای همان کاربر را میبیند
|
||
|
|
- از متنهای مرجع واقعی استفاده میکند
|
||
|
|
|
||
|
|
پس جوابها:
|
||
|
|
|
||
|
|
- دقیقتر میشوند
|
||
|
|
- شخصیتر میشوند
|
||
|
|
- به دادههای واقعی نزدیکتر میشوند
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## فایلها را خیلی ساده به خاطر بسپار
|
||
|
|
|
||
|
|
- `rag/apps.py` -> فقط ثبت اپ
|
||
|
|
- `rag/views.py` -> گرفتن درخواست کاربر
|
||
|
|
- `rag/chat.py` -> ساخت context و گرفتن جواب از مدل
|
||
|
|
- `rag/retrieve.py` -> جستجوی متن مرتبط
|
||
|
|
- `rag/ingest.py` -> وارد کردن دانش به سیستم
|
||
|
|
- `rag/embedding.py` -> تبدیل متن به embedding
|
||
|
|
- `rag/vector_store.py` -> ذخیره و جستجو در Qdrant
|
||
|
|
- `rag/user_data.py` -> ساخت متن از دادههای کاربر
|
||
|
|
- `rag/config.py` -> تنظیمات کل RAG
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## یک مثال خیلی ساده
|
||
|
|
|
||
|
|
فرض کن کاربر بپرسد:
|
||
|
|
|
||
|
|
`آیا خاک من برای آبیاری مناسب است؟`
|
||
|
|
|
||
|
|
سیستم این کارها را میکند:
|
||
|
|
|
||
|
|
1. سوال را میگیرد
|
||
|
|
2. میفهمد باید از سرویس یا دانش آبیاری استفاده کند
|
||
|
|
3. داده خاک همان کاربر را از دیتابیس میگیرد
|
||
|
|
4. داده هواشناسی را هم میگیرد
|
||
|
|
5. متنهای مرتبط آبیاری را از Qdrant پیدا میکند
|
||
|
|
6. همه را به مدل میدهد
|
||
|
|
7. مدل جواب میدهد
|
||
|
|
|
||
|
|
پس جواب نهایی فقط یک حدس عمومی نیست؛
|
||
|
|
بلکه بر اساس:
|
||
|
|
|
||
|
|
- اطلاعات خاک
|
||
|
|
- اطلاعات هوا
|
||
|
|
- متنهای مرجع آبیاری
|
||
|
|
|
||
|
|
ساخته میشود.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## نتیجه نهایی
|
||
|
|
|
||
|
|
منطق RAG این پروژه به زبان خیلی ساده این است:
|
||
|
|
|
||
|
|
- اول دانش را آماده میکند
|
||
|
|
- بعد موقع سوال، دانش مرتبط را پیدا میکند
|
||
|
|
- دادههای واقعی کاربر را هم اضافه میکند
|
||
|
|
- و در آخر از مدل میخواهد با این اطلاعات جواب بدهد
|
||
|
|
|
||
|
|
و یادت باشد:
|
||
|
|
|
||
|
|
- `rag/apps.py` فقط فایل ثبت اپ است
|
||
|
|
- منطق واقعی RAG در فایلهای `chat`, `retrieve`, `ingest`, `vector_store`, `user_data` و `views` قرار دارد
|
||
|
|
|