Files
microdao-daarion/TODO-RAG.md
Apple 00f9102e50 feat: add Ollama runtime support and RAG implementation plan
Ollama Runtime:
- Add ollama_client.py for Ollama API integration
- Support for dots-ocr model via Ollama
- Add OLLAMA_BASE_URL configuration
- Update inference.py to support Ollama runtime (RUNTIME_TYPE=ollama)
- Update endpoints to handle async Ollama calls
- Alternative to local transformers model

RAG Implementation Plan:
- Create TODO-RAG.md with detailed Haystack integration plan
- Document Store setup (pgvector)
- Embedding model selection
- Ingest pipeline (PARSER → RAG)
- Query pipeline (RAG → LLM)
- Integration with DAGI Router
- Bot commands (/upload_doc, /ask_doc)
- Testing strategy

Now supports three runtime modes:
1. Local transformers (RUNTIME_TYPE=local)
2. Ollama (RUNTIME_TYPE=ollama)
3. Dummy (USE_DUMMY_PARSER=true)
2025-11-16 02:56:36 -08:00

370 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TODO — RAG Stack (Haystack + PARSER Agent)
Цей план описує, як побудувати RAG-шар навколо PARSER (dots.ocr) та DAGI Router.
**Статус:** 🟡 Планування
---
## 1. Document Store (pgvector або Qdrant)
### 1.1. Вибір бекенду
- [ ] Обрати бекенд:
- [ ] `Postgres + pgvector` (рекомендовано, якщо в нас уже є Postgres)
- [ ] або `Qdrant` (docker-сервіс)
**Рекомендація:** Використати `pgvector` (вже є в `city-db`)
### 1.2. Ініціалізація Haystack DocumentStore
Приклад для PostgreSQL + pgvector:
```python
# services/rag-service/app/document_store.py
from haystack.document_stores import PGVectorDocumentStore
def get_document_store() -> PGVectorDocumentStore:
return PGVectorDocumentStore(
connection_string="postgresql+psycopg2://postgres:postgres@city-db:5432/daarion_city",
embedding_dim=1024, # залежить від моделі ембеддингів
table_name="rag_documents",
search_strategy="approximate",
)
```
**Завдання:**
- [ ] Створити `services/rag-service/` структуру
- [ ] Додати `app/document_store.py` з ініціалізацією
- [ ] Налаштувати підключення до `city-db`
---
## 2. Embedding-модель
### 2.1. Обрати модель
- [ ] Вибрати embedding-модель:
- [ ] `BAAI/bge-m3` (multilingual, 1024 dim)
- [ ] `sentence-transformers/all-MiniLM-L12-v2` (легка, 384 dim)
- [ ] `intfloat/multilingual-e5-base` (українська підтримка, 768 dim)
**Рекомендація:** `BAAI/bge-m3` для кращої підтримки української
### 2.2. Обгортка під Haystack
```python
# services/rag-service/app/embedding.py
from haystack.components.embedders import SentenceTransformersTextEmbedder
def get_text_embedder():
return SentenceTransformersTextEmbedder(
model="BAAI/bge-m3",
device="cuda" # або "cpu"
)
```
**Завдання:**
- [ ] Створити `app/embedding.py`
- [ ] Додати конфігурацію моделі через env
- [ ] Тестувати на українському тексті
---
## 3. Ingest-пайплайн: PARSER → RAG
### 3.1. Функція ingest_document
- [ ] Створити `services/rag-service/app/ingest_pipeline.py`:
```python
# services/rag-service/app/ingest_pipeline.py
from haystack import Pipeline
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from haystack.schema import Document
from .document_store import get_document_store
from .embedding import get_text_embedder
# 1) splitter — якщо треба додатково різати текст
splitter = DocumentSplitter(
split_by="sentence",
split_length=8,
split_overlap=1
)
embedder = get_text_embedder()
doc_store = get_document_store()
writer = DocumentWriter(document_store=doc_store)
ingest_pipeline = Pipeline()
ingest_pipeline.add_component("splitter", splitter)
ingest_pipeline.add_component("embedder", embedder)
ingest_pipeline.add_component("writer", writer)
ingest_pipeline.connect("splitter.documents", "embedder.documents")
ingest_pipeline.connect("embedder.documents", "writer.documents")
def ingest_parsed_document(dao_id: str, doc_id: str, parsed_json: dict):
"""
parsed_json — результат PARSER (mode=raw_json або qa_pairs/chunks).
Тут треба перетворити його у список haystack.Document.
"""
blocks = parsed_json.get("blocks", [])
docs = []
for b in blocks:
text = b.get("text") or ""
if not text.strip():
continue
meta = {
"dao_id": dao_id,
"doc_id": doc_id,
"page": b.get("page"),
"section_type": b.get("type"),
}
docs.append(Document(content=text, meta=meta))
if not docs:
return
ingest_pipeline.run(
{
"splitter": {"documents": docs}
}
)
```
**Завдання:**
- [ ] Створити ingest pipeline
- [ ] Додати конвертацію ParsedDocument → Haystack Documents
- [ ] Додати обробку chunks mode (якщо PARSER повертає готові chunks)
### 3.2. Інтеграція з PARSER Service
- [ ] Додати виклик `parser-service` у DevTools / CrewAI workflow:
- [ ] Завантажити файл
- [ ] Викликати `/ocr/parse?output_mode=raw_json` або `/ocr/parse_chunks`
- [ ] Передати `parsed_json` у `ingest_parsed_document`
**Завдання:**
- [ ] Створити `services/rag-service/app/parser_client.py` для виклику parser-service
- [ ] Додати endpoint `/rag/ingest` для завантаження документів
- [ ] Інтегрувати з Gateway для команди `/upload_doc`
---
## 4. Query-пайплайн: питання → RAG → LLM
### 4.1. Retriever + Generator
```python
# services/rag-service/app/query_pipeline.py
from haystack import Pipeline
from haystack.components.retrievers import DocumentRetriever
from haystack.components.generators import OpenAIGenerator # або свій LLM через DAGI Router
from .document_store import get_document_store
from .embedding import get_text_embedder
doc_store = get_document_store()
embedder = get_text_embedder()
retriever = DocumentRetriever(document_store=doc_store)
# У проді замінити на кастомний generator, що ходить у DAGI Router
generator = OpenAIGenerator(
api_key="DUMMY",
model="gpt-4o-mini"
)
query_pipeline = Pipeline()
query_pipeline.add_component("embedder", embedder)
query_pipeline.add_component("retriever", retriever)
query_pipeline.add_component("generator", generator)
query_pipeline.connect("embedder.documents", "retriever.documents")
query_pipeline.connect("retriever.documents", "generator.documents")
def answer_query(dao_id: str, question: str):
filters = {"dao_id": [dao_id]}
result = query_pipeline.run(
{
"embedder": {"texts": [question]},
"retriever": {"filters": filters},
"generator": {"prompt": question},
}
)
answer = result["generator"]["replies"][0]
documents = result["retriever"]["documents"]
return answer, documents
```
**У реальному стеку:**
- Генератором буде не OpenAI, а DAGI Router (через окремий компонент / кастомний генератор)
- Фільтри по `dao_id`, `roles`, `visibility` будуть інтегровані з RBAC
**Завдання:**
- [ ] Створити query pipeline
- [ ] Додати кастомний generator для DAGI Router
- [ ] Додати RBAC фільтри
- [ ] Створити endpoint `/rag/query`
---
## 5. Інтеграція з DAGI Router
### 5.1. Режим `mode=rag_query`
- [ ] Додати у `router-config.yml` rule:
```yaml
routing:
- id: rag_query
when:
mode: rag_query
use_provider: llm_local_qwen3_8b # або окремий RAG-provider
```
- [ ] Додати handler у `RouterApp`, який:
- До виклику LLM запускає `answer_query(dao_id, question)`
- В prompt LLM додає витягнуті документи як контекст
**Завдання:**
- [ ] Оновити `router-config.yml`
- [ ] Додати RAG provider в Router
- [ ] Створити handler для `mode=rag_query`
---
## 6. RAG Service (FastAPI)
### 6.1. Структура сервісу
- [ ] Створити `services/rag-service/`:
- [ ] `app/main.py` - FastAPI додаток
- [ ] `app/api/endpoints.py` - ендпоінти:
- [ ] `POST /rag/ingest` - інжест документу
- [ ] `POST /rag/query` - запит до RAG
- [ ] `GET /rag/health` - health check
- [ ] `app/schemas.py` - Pydantic моделі
- [ ] `requirements.txt` - залежності (haystack, pgvector, etc.)
- [ ] `Dockerfile`
### 6.2. Ендпоінти
```python
# services/rag-service/app/api/endpoints.py
@router.post("/rag/ingest")
async def ingest_document_endpoint(
doc_id: str,
dao_id: str,
parsed_doc: ParsedDocument # або doc_url для завантаження
):
"""Ingest parsed document into RAG"""
# Викликати ingest_parsed_document()
pass
@router.post("/rag/query")
async def query_endpoint(
dao_id: str,
question: str,
user_id: str
):
"""Query RAG and return answer with citations"""
# Викликати answer_query()
# Повернути відповідь + цитати
pass
```
---
## 7. Інтеграція з DAARWIZZBot / microDAO
### 7.1. Команди для бота
- [ ] Додати команди в `gateway-bot/http_api.py`:
- [ ] `/upload_doc` → інжест документу в RAG через PARSER
- [ ] Підтримка завантаження файлів через Telegram
- [ ] Виклик `parser-service``rag-service`
- [ ] `/ask_doc` → питання до бази документів DAO
- [ ] Виклик `rag-service` → DAGI Router
- [ ] Відправка відповіді з цитатами
### 7.2. RBAC
- [ ] Хто може інжестити документи (`role: admin`, `role: researcher`)
- [ ] Хто може ставити питання до приватних документів
- [ ] Перевірка прав в `microdao/rbac.py`
---
## 8. Тести
- [ ] Інжест одного PDF (наприклад, "Токеноміка MicroDAO") через PARSER → ingest
- [ ] Питання:
> "Поясни, як працює стейкінг у цьому microDAO."
- [ ] Перевірити, що Haystack знаходить потрібні фрагменти і LLM будує відповідь з цитатами
**Завдання:**
- [ ] Створити тестові фікстури (PDF документи)
- [ ] E2E тести для ingest → query
- [ ] Тести на RBAC фільтри
---
## Порядок виконання (рекомендований)
### Фаза 1: Document Store + Embeddings (1-2 дні)
1. Налаштувати pgvector в city-db
2. Створити Haystack DocumentStore
3. Вибрати та налаштувати embedding-модель
### Фаза 2: Ingest Pipeline (2-3 дні)
1. Створити ingest pipeline
2. Інтегрувати з PARSER Service
3. Створити RAG Service з endpoint `/rag/ingest`
### Фаза 3: Query Pipeline (2-3 дні)
1. Створити query pipeline
2. Інтегрувати з DAGI Router
3. Додати RBAC фільтри
### Фаза 4: Інтеграція з ботом (1-2 дні)
1. Додати команди `/upload_doc`, `/ask_doc`
2. Тестування E2E
**Загальний час:** ~6-10 днів
---
## Залежності
### Python пакети
- `haystack-ai>=2.0.0`
- `sentence-transformers>=2.2.0`
- `pgvector>=0.2.0`
- `psycopg2-binary>=2.9.0`
### Системні залежності
- PostgreSQL з pgvector (вже є в city-db)
---
## Посилання
- [PARSER Agent Documentation](./docs/agents/parser.md)
- [TODO: PARSER Implementation](./TODO-PARSER-RAG.md)
- [Haystack Documentation](https://docs.haystack.deepset.ai/)