Знания и RAG
Ingestion pipeline
Документы, загруженные в AgentCore, проходят через многоэтапный пайплайн обработки: извлечение текста, очистка PII, разбиение на чанки, генерация embeddings и синтетических Q&A-пар.
Этапы пайплайна
Upload → Parse → PII Scrub → Chunk → Embed → Synthetic Q&A → Store
1. Загрузка
Документы загружаются через POST /api/v1/knowledge/upload (multipart) или создаются по URL через POST /api/v1/knowledge/documents. Файл сохраняется в KB_UPLOAD_DIR, создаётся запись Document со статусом pending.
2. Очередь
BullMQ-задача попадает в очередь knowledge-ingest для асинхронной обработки. У очереди:
- параллелизм 1 (последовательная обработка);
- повторные попытки с exponential backoff;
- graceful shutdown по SIGTERM.
3. Извлечение текста
Обрабатывается в src/knowledge/parsers.ts:
| Тип | Парсер | Примечания |
|---|---|---|
pdf-parse | Извлечение полного текста | |
| DOCX | mammoth | Конвертирует в plain text |
| TXT | Прямое чтение файла | UTF-8 |
| URL | HTTP fetch + HTML-стрип | Убирает теги |
| Изображения | OpenAI Vision API | OCR через gpt-4o |
4. Очистка PII (односторонняя)
До чанкинга вся PII необратимо маскируется. Это гарантирует, что сырые персональные данные никогда не попадают в базу знаний.
Распознаваемые паттерны:
- email-адреса;
- номера телефонов;
- номера кредитных карт;
- IP-адреса;
- IBAN;
- денежные суммы.
Заменяются на [REDACTED_EMAIL], [REDACTED_PHONE] и т.д.
Подробности — в разделе PII Scrubber.
5. Чанкинг
Текст режется на чанки специальным юридическим чанкёром:
- максимальный размер чанка по умолчанию — 2000 символов;
- overlap между чанками для сохранения контекста;
- распознавание украинской юридической структуры (Розділ, Стаття, п.);
- отслеживание section path для иерархического контекста.
Подробности — в разделе Семантический чанкёр.
6. Embeddings
Каждый чанк векторизуется через OpenAI text-embedding-3-small:
- размерность вектора 1536;
- batch size — 32 чанка на вызов API;
- хранится в PostgreSQL через pgvector.
7. Синтетические Q&A
Для каждого чанка gpt-4o-mini генерирует 3 синтетических вопроса, на которые этот чанк может ответить. Эти вопросы:
- векторизуются отдельно (1536-dim);
- хранятся в таблице
ChunkQuestion; - используются как дополнительный путь retrieval при RAG-запросах.
8. Завершение
При успехе:
- статус документа становится
ready; - метаданные обновляются (
chunkCount,textLength); - временный файл загрузки удаляется.
При ошибке:
- статус документа становится
failed; - ошибка пишется в
metadata.lastError; - BullMQ делает retry, если попытки остались.
Мониторинг
Отслеживайте статус ingestion через:
GET /api/v1/knowledge/documents?status=processing— задачи в работе;GET /api/v1/knowledge/documents?status=failed— упавшие документы;- Langfuse-трейсы по спанам
ingestion.synthetic-qa.
RAG pipeline
Retrieval-augmented generation pipeline сочетает гибридный поиск (vector + keyword) с LLM-генерацией, чтобы давать точные, заземлённые на источнике ответы. Вызывается общим agent-task executor при обычной обработке сообщений и эндпоинтом RAG draft для ручных тестов.
Архитектура
Query → Injection Guard → Embed → Hybrid Retrieval → Rank → Context Assembly → LLM → PII Restore
Реализация: src/knowledge/rag.ts (OpenAiRagPipeline).
Стратегия retrieval
Гибридный поиск
Объединяются три пути retrieval:
| Путь | Вес по умолчанию | Кандидаты | Описание |
|---|---|---|---|
| Chunk vectors | 85% от vector-бюджета | Top 18 | Косинусная близость embeddings чанков |
| Question vectors | 15% от vector-бюджета | Top 18 | Косинусная близость embeddings синтетических Q&A |
| Keyword search | 35% (дефолт keyword) | Top 18 | PostgreSQL full-text search по содержимому чанка |
По умолчанию распределение: vector 65% + keyword 35% (настраивается per-KB через config.ragWeights, поля vector и keyword). Внутри vector-бюджета 85% на chunk embeddings и 15% на synthetic Q&A embeddings.
Скоринг
Каждый кандидат получает гибридный скор:
score = (vector × 0.85) × vecScore + (vector × 0.15) × qScore + keyword × keyScore
Скоры нормализуются в каждом пути перед объединением.
Top-K selection
После ранжирования берутся 6 лучших чанков. Применяется лимит максимальной длины контекста, чтобы уложиться в токен-бюджет LLM.
Сборка контекста
Выбранные чанки форматируются с:
- заголовком документа;
- section path (из метаданных чанкёра);
- содержимым чанка.
LLM-генерация
| Параметр | Значение |
|---|---|
| Модель | OPENAI_MODEL (дефолт gpt-4o) |
| Temperature | 0.2 |
| Max tokens | 700 |
| Streaming | Поддерживается |
В prompt входит:
- System prompt — из конфигурации namespace.
- Профиль сотрудника — краткое описание, текущие проекты, предпочтения (если есть).
- История разговора — последние 6 сообщений (нормализованные).
- RAG-контекст — собранные чанки с метаданными источника.
- Запрос пользователя.
Обработка PII
- PII в сообщении пользователя заменяется плейсхолдерами до отправки в LLM.
- Плейсхолдеры хранятся зашифрованными в
PiiRedactionMap. - После генерации плейсхолдеры в ответе заменяются обратно на реальные значения.
См. PII Scrubber.
Защита от инъекций
Перед сборкой контекста и сообщение пользователя, и фрагменты 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?"}'
Настройка per-KB
RAG-веса настраиваются на уровне knowledge base через config.ragWeights:
{
"ragWeights": {
"vector": 0.65,
"keyword": 0.35
}
}
См. RAG-веса.
Семантический чанкёр
Чанкёр разбивает документы на чанки с сохранением контекста, опираясь на юридическую структуру документа. Оптимизирован под украинские юридические тексты.
Реализация
src/knowledge/chunker.ts — splitLegalTextIntoChunks().
Распознавание юридической структуры
Чанкёр узнаёт заголовки украинских юридических документов:
| Паттерн | Пример | Уровень |
|---|---|---|
| Розділ N / Розділ I | Розділ 3. Оплата | Раздел |
| Стаття N / Стаття N.N | Стаття 15. Відповідальність | Стаття |
| Пункт N.N / п. N | п. 5 | Пункт |
| N.N. (самостоятельная нумерация) | 1.1. Загальні положення | Нумерованный |
| N) | 1) перший варіант | Элемент списка |
| а) / б) (кириллица) | а) перша умова | Подпункт |
Стратегия резки
- Резка по заголовкам — валидные заголовки создают естественные границы чанков.
- Section path tracking — каждый чанк хранит цепочку навигации (например,
["Розділ 3", "Стаття 15", "п. 2"]). - Контроль размера — чанки длиннее
maxChunkSize(дефолт 2000 символов) режутся по границам абзацев или предложений. - Overlap — настраиваемое перекрытие между соседними чанками сохраняет контекст через границы.
- Метаданные — в каждом чанке лежат
chunkIndex,startChar,endChar,sectionPath.
Fallback
Если юридическая структура не найдена (не-юридические документы), чанкёр откатывается к:
- Резке по абзацам (двойной перенос).
- Резке по предложениям (точка + пробел).
- Жёсткой резке по максимальному размеру.
Чанкёр вызывается во время ingestion pipeline после извлечения текста и PII-очистки. Напрямую через API не доступен.