Архитектура

RAG и базы знаний

Как Cradle обосновывает ответы агентов вашими документами — chunking, embedding, retrieval, цитирование и веб-краулинг.

Cradle не отвечает от обобщённой облачной модели. Когда у агента есть одна или несколько баз знаний, входящее сообщение эмбеддится, сопоставляется с локальным векторным хранилищем, а top-фрагменты вставляются в промпт с inline-цитатами.

Поток от начала до конца

Сообщение пользователя


TriageOrchestrator
  ├── оценка риска
  ├── router выбирает агента
  └── если agent.knowledgeBaseIds.length > 0:
        ├── acquireEmbedder()
        ├── search(query, embedder, { kbIds, topK: 6 })
        │     embed(query) → vector
        │     sqlite-vec MATCH → top chunks
        │     join documents + knowledge_bases
        ├── Hits → SourceSnippet[]
        └── buildPrompt(agent, turns, sources)
              «Используй нумерованные источники ниже, если они релевантны. Цитируй как [1].»
              [1] Заголовок документа (источник)
              содержимое...
              [2] ...


runner.generate → черновик с цитатами → Inbox / автоответ

Векторное хранилище

Cradle использует sqlite-vec для векторного поиска. Каждая база знаний получает собственную виртуальную таблицу под размерность embedding-модели:

CREATE VIRTUAL TABLE vec_kb_<kbId> USING vec0(
  chunk_id TEXT PRIMARY KEY,
  embedding FLOAT[<dim>]
);

Старые базы знаний без registry-записи продолжают работать через legacy общую таблицу vec_chunks.

Таблицы

knowledge_bases   (id, name, description, embedding_model_id, embedding_dim, created_at)
documents         (id, kb_id, source_kind, source_uri, title, mime, sha256,
                   status, char_count, chunk_count, error_message, ingested_at)
document_chunks   (id, document_id, kb_id, position, content, token_count, metadata)

agents.knowledge_base_ids — JSON-список id баз знаний, доступных агенту.

Стратегия chunking

Paragraph-first chunker держит связанный текст вместе:

  1. Разбивает по пустым строкам на абзацы.
  2. Накапливает абзацы, пока сумма не превышает 2000 символов.
  3. Если абзац больше лимита — разбивает по границам предложений.
  4. Если отдельное предложение всё ещё больше лимита — жёсткий обрез по символам.
  5. Сохраняет overlap 200 символов между соседними chunks.

Каждый chunk хранит content, position и оценочное число токенов (chars / 4).

Ingestion

await ingestText({
  kbId,
  title,
  text,
  sourceKind: 'upload' | 'url' | 'crawl' | 'inline',
  sourceUri,
  mime,
  embedder,
  batchSize = 16,
})
  • Идемпотентность по SHA-256 текста.
  • Chunks вставляются со статусом indexing, эмбеддятся батчами, затем переводятся в ready.
  • Ошибки откатывают документ в status='error' с сохранённым сообщением.
  • Прогресс отчитывается между батчами.

Парсеры

РасширениеРеализация
.md, .markdownчитается как plain text
.txt, .log, .csvчитается как plain text
.pdfpdf-parse v2
.docxmammoth.extractRawText
.xlsx, .xlsexceljs, одна текстовая строка на строку

parseFile(filePath) возвращает { text, title, mime }. isSupported(filePath) проверяет расширение перед попыткой ingestion.

Retrieval

await search(query, embedder, { kbIds, topK = 6, maxDistance })
  1. Эмбеддим запрос.
  2. Выполняем SELECT chunk_id, distance FROM vec_kb_... WHERE embedding MATCH ?.
  3. JOIN с document_chunks и documents для обогащения.
  4. Фильтруем по запрошенным базам знаний.
  5. Опционально фильтруем по maxDistance.
  6. Возвращаем topK результатов как SearchHit[].

Хелперы deleteDocument(id) и deleteKnowledgeBase(id) каскадно чистят chunks. Drizzle FK cascades покрывают большинство таблиц, но sqlite-vec virtual tables чистятся явно.

Цитирование

Каждый chunk, использованный в ответе, сохраняется в ticket_sources со снимком:

ticket_sources (
  id, ticket_id, message_id, position,
  document_id, chunk_id, kb_id,
  document_title, source_uri,
  content_preview, content_full,
  chunk_position, distance, created_at
)

Снимки делают тикеты воспроизводимыми, даже если оригинальный документ позже удалён или переиндексирован. UI показывает нумерованные бейджи, совпадающие с [1], [2], ... которые модель ставит в тексте ответа.

Веб-краулинг

Базы знаний можно наполнять веб-краулингом:

  • rag:ingestUrl({ kbId, url }) — одна страница или документ.
  • rag:crawlSite({ kbId, seedUrl, maxPages, maxDepth, sameDomain, followDocs, respectRobots }) — BFS-задача с live-прогрессом.

Модули краулера в src/core/rag/crawler/:

  • url.ts — нормализация URL и проверка same-domain.
  • html.ts — cheerio + turndown extraction, удаляет script/style/nav/footer/aside.
  • robots.ts — кешированный robots.txt для каждого origin.
  • fetcher.ts — fetch HTML или документов.
  • crawler.ts — BFS с политeness-задержкой 500 мс, лимитами depth/pages, follow-документами.

Лимиты по умолчанию: HTML до 10 MB, документы до 50 MB, таймаут запроса 20 с.

RAG + finetuning

RAG поставляет факты (прайсы, спецификации, пункты договоров). Finetuning поставляет стиль и паттерны рассуждений. Они дополняют друг друга; см. finetune.