Знання та RAG
Ingestion pipeline
Документи, завантажені в AgentCore, проходять багатоетапний ingestion pipeline: витяг тексту, маскування PII, розбиття на чанки, генерація ембедингів і синтетичних Q&A-пар.
Етапи pipeline
Upload → Parse → PII Scrub → Chunk → Embed → Synthetic Q&A → Store
1. Upload
Документи завантажуються через POST /api/v1/knowledge/upload (multipart) або створюються з URL через POST /api/v1/knowledge/documents. Файл зберігається у KB_UPLOAD_DIR, створюється Document-запис зі статусом pending.
2. Черга
У чергу knowledge-ingest додається BullMQ-джоб для асинхронної обробки. Характеристики черги:
- Concurrency: 1 (послідовна обробка)
- Retry з exponential backoff
- Graceful shutdown по SIGTERM
3. Витяг тексту
Реалізація у src/knowledge/parsers.ts:
| Тип | Парсер | Примітки |
|---|---|---|
pdf-parse | Витяг повного тексту | |
| DOCX | mammoth | Конверсія у plain text |
| TXT | Пряме читання файлу | UTF-8 |
| URL | HTTP fetch + HTML strip | Прибирає теги |
| Image | OpenAI Vision API | OCR через gpt-4o |
4. PII Scrub (незворотний)
Перед чанкінгом усі PII-дані незворотно маскуються. Це гарантує, що база знань ніколи не зберігає сирі персональні дані.
Виявлювані патерни:
- Email-адреси
- Номери телефонів
- Номери кредитних карт
- IP-адреси
- IBAN-номери
- Грошові суми
Замінюються на [REDACTED_EMAIL], [REDACTED_PHONE] тощо.
Деталі — у PII Scrubber.
5. Chunking
Текст розбивається на чанки через chunker, що розуміє структуру юридичних документів.
- Default max chunk size: 2000 символів
- Overlap між чанками зберігає контекст
- Виявлення української юридичної структури (Розділ, Стаття, п.)
- Трекінг section-path для ієрархічного контексту
Деталі — у Semantic chunker.
6. Embed
Кожен чанк ембедиться через OpenAI text-embedding-3-small:
- Розмірність вектора: 1536
- Batch size: 32 чанки на API-виклик
- Зберігається у PostgreSQL через pgvector
7. Генерація синтетичних Q&A
Для кожного чанка gpt-4o-mini генерує 3 синтетичних питання, на які цей чанк може відповісти. Ці питання:
- Ембедяться окремо (1536-dim вектори)
- Зберігаються у таблиці
ChunkQuestion - Використовуються як додатковий шлях пошуку під час RAG-запитів
8. Завершення
При успіху:
- Статус документа оновлюється на
ready - Метадані оновлюються полями
chunkCount,textLength - Тимчасовий upload-файл видаляється
При невдачі:
- Статус документа оновлюється на
failed - Помилка пишеться у
metadata.lastError - BullMQ ретраїть, якщо лишились спроби
Моніторинг
Треккайте статус ingestion через:
GET /api/v1/knowledge/documents?status=processing— джоби в процесіGET /api/v1/knowledge/documents?status=failed— невдалі документи- Langfuse-трейси для спанів
ingestion.synthetic-qa
RAG pipeline
RAG-pipeline (Retrieval-Augmented Generation) поєднує гібридний пошук (вектор + keyword) з LLM-генерацією для точних grounded-відповідей. Викликається agent-task воркером у звичайному flow обробки повідомлень та RAG draft-ендпоінтом для ручних тестів.
Архітектура
Query → Injection Guard → Embed → Hybrid Retrieval → Rank → Context Assembly → LLM → PII Restore
Реалізація: src/knowledge/rag.ts (OpenAiRagPipeline)
Стратегія пошуку
Гібридний пошук
Комбінуються три шляхи пошуку:
| Шлях | Дефолтна вага | Кандидати | Опис |
|---|---|---|---|
| Chunk vectors | 85% від вектор-бюджету | Top 18 | Cosine similarity по ембедингах чанків |
| Question vectors | 15% від вектор-бюджету | Top 18 | Cosine similarity по ембедингах синтетичних Q&A |
| Keyword search | 35% (дефолтна вага keyword) | Top 18 | PostgreSQL full-text search по контенту чанків |
Дефолтний розподіл: векторний пошук 65% vs keyword-пошук 35% (налаштовується для бази знань через config.ragWeights, поля vector і keyword). У вектор-бюджеті 85% іде на content chunk embeddings, 15% — на synthetic Q&A embeddings.
Scoring
Кожен кандидат отримує гібридний score:
score = (vector × 0.85) × vecScore + (vector × 0.15) × qScore + keyword × keyScore
Scores нормалізуються по кожному шляху перед обʼєднанням.
Top-K selection
Після ранжування вибирається top 6 чанків. Застосовується обмеження максимальної довжини контексту, щоб влізти у token-limit LLM.
Context assembly
Вибрані чанки форматуються з:
- Назвою документа
- Section path (з метаданих chunker)
- Контентом чанка
LLM generation
| Параметр | Значення |
|---|---|
| Model | OPENAI_MODEL (дефолт: gpt-4o) |
| Temperature | 0.2 |
| Max tokens | 700 |
| Streaming | Підтримується |
Промпт включає:
- System prompt — з персона-конфігу простору імен
- Employee profile — summary, поточні проекти, преференції (якщо є)
- Історія розмови — останні 6 повідомлень (нормалізовані)
- RAG context — зібрані чанки з метаданими джерел
- User query
PII handling
- PII у повідомленні користувача замінюється плейсхолдерами перед відправкою до LLM
- Плейсхолдери зберігаються у зашифрованому вигляді в
PiiRedactionMap - Після генерації плейсхолдери у відповіді відновлюються до реальних значень
Див. PII Scrubber.
Injection guard
І повідомлення користувача, і RAG-контекст чанки скануються на патерни prompt injection перед збиранням промпта. Якщо виявлено:
- Запит помічається (
injectionDetected: true) - Записується причина (
injectionReason) - Повідомлення маршрутизується на HITL незалежно від trust-статусу
Див. Injection Guard.
Тестування
Використовуйте POST /api/v1/rag/draft, щоб перевірити RAG-запити без відправки у канал:
curl -X POST http://localhost:3000/api/v1/rag/draft \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"departmentId": "<department-id>", "text": "What is the leave policy?"}'
Конфігурація на рівні бази знань
RAG-ваги налаштовуються для кожної бази знань через config.ragWeights:
{
"ragWeights": {
"vector": 0.65,
"keyword": 0.35
}
}
Див. RAG weights.
Semantic chunker
Chunker розбиває документи на чанки зі збереженням контексту, враховуючи структуру юридичних документів. Оптимізований під українські правові тексти.
Реалізація
src/knowledge/chunker.ts — splitLegalTextIntoChunks()
Виявлення юридичної структури
Chunker розпізнає заголовки українських правових документів:
| Патерн | Приклад | Рівень |
|---|---|---|
| Розділ N / Розділ I | Розділ 3. Оплата | Розділ |
| Стаття N / Стаття N.N | Стаття 15. Відповідальність | Стаття |
| Пункт N.N / п. N | п. 5 | Пункт |
| N.N (самостійний номер) | 1.1. Загальні положення | Нумерований |
| N) | 1) перший варіант | Елемент списку |
| а) / б) (кирилиця) | а) перша умова | Підпункт |
Стратегія чанкінгу
- Split по заголовках: юридичні заголовки формують природні межі чанків
- Трекінг section-path: кожен чанк несе навігаційний шлях (наприклад,
["Розділ 3", "Стаття 15", "п. 2"]) - Обмеження розміру: чанки, що перевищують
maxChunkSize(дефолт 2000 символів), додатково розбиваються по межах абзацу/речення - Overlap: налаштовуваний overlap між послідовними чанками зберігає контекст на межах
- Метадані: кожен чанк зберігає
chunkIndex,startChar,endChar,sectionPath
Fallback
Якщо юридична структура не виявлена (неправові документи), chunker переходить на:
- Split по абзацах (подвійний newline)
- Split по реченнях (крапка + пробіл)
- Hard split на максимальному розмірі
Chunker викликається під час ingestion-pipeline після витягу тексту та PII-скраба. Напряму через API не експонується.