Базы знаний (RAG)
RAG-слой платформы: объявляете Knowledge и источники данных, поиск сам вплетается в агентный цикл, ответы несут обязательные цитаты, пассажи можно наполнять программно.
База знаний — индексированный корпус документов. Агент ищет по ней семантически: по смыслу, а не по подстроке.
Собирается в три приёма. Объявите базу примитивом Knowledge. Подключите источники (DataSource). Передайте базу в агентный цикл. Дальше платформа делает всё сама: даёт агенту инструмент поиска, подставляет найденные пассажи в контекст, требует ссылки на источники.
Модель ничего не «помнит» о ваших документах. Она обращается к базе на каждый запрос, где это уместно. Так знания (данные, которые меняются) отделяются от логики бота (код, который вы ревьюите).
Объявите базу знаний
Knowledge описывает именованный корпус и его источники. Держите объявление в src/knowledge/index.ts.
import { Knowledge, DataSource } from '@holocronlab/botruntime-runtime'
const guides = DataSource.Directory.fromPath('src/knowledge', {
id: 'guides',
filter: (filePath) => filePath.endsWith('.md') || filePath.endsWith('.pdf'),
})
export const HelpDesk = new Knowledge({
name: 'help-desk',
description: 'Регламенты поддержки, тарифы и ответы на частые вопросы.',
sources: [guides],
})Поля Knowledge:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
name | string | да | Уникальное имя базы в пределах бота. По нему агент выбирает, где искать. |
description | string | нет | Одна фраза «что здесь лежит». Попадает в подсказку инструменту поиска — пишите содержательно. |
sources | DataSource[] | да | Один или несколько источников данных (см. ниже). |
description — не украшение. Агент читает описания всех баз, чтобы решить, стоит ли вообще искать и в какой базе. Расплывчатое описание — это пропущенные обращения к знаниям.
Подключите источники
Источник (DataSource) — откуда платформа берёт документы и как их обновляет. Импортируйте DataSource из рантайма. Выбирайте фабрику под тип источника.
Каталог с файлами
DataSource.Directory.fromPath(path, options) индексирует локальные файлы проекта (.md, .pdf и другие). Путь резолвится относительно корня агент-проекта.
const policies = DataSource.Directory.fromPath('src/knowledge/policies', {
id: 'policies',
filter: (filePath) => filePath.endsWith('.md'),
})| Опция | Тип | Описание |
|---|---|---|
id | string | Стабильный идентификатор источника внутри базы. По умолчанию выводится из пути. |
filter | (filePath: string) => boolean | Отбор файлов. Вернувшие false не индексируются. |
Каталог поддерживает только filter. Преобразование содержимого перед индексацией (transform) есть у источников-таблиц и сайтов, но не у каталога.
Каталоги индексируются на этапе разработки и деплоя, а не в рантайме. При
brt dev и brt deploy --adk платформа сканирует папку, считает sha256 каждого
файла и заливает только изменившиеся. Задеплоенный бот в production уже ищет по
готовому индексу — сканировать локальную ФС ему нечем. Поменяли файлы знаний —
повторите явный production deploy.
Сайт
DataSource.Website тянет содержимое веб-страниц. Выбирайте фабрику по способу обхода:
const docs = DataSource.Website.fromSitemap('https://example.com/sitemap.xml', {
id: 'public-docs',
})DataSource.Website.fromSitemap(sitemapUrl, options)— из карты сайта.DataSource.Website.fromUrls(urls, options)— явный список URL.DataSource.Website.fromLlmsTxt(llmsTxtUrl, options)— из файлаllms.txt.
Опции обхода: maxPages (1–50000), maxDepth (1–20), filter (по context.url).
Таблица
DataSource.Table.fromTable(table, options) строит источник поверх вашей таблицы. Табличные записи попадают в семантический поиск.
import { FaqTable } from '../tables'
const faq = DataSource.Table.fromTable(FaqTable, {
id: 'faq',
transform: ({ row }) => `Вопрос: ${row.question}\nОтвет: ${row.answer}`,
})transform превращает строку таблицы в текст для индексации. filter отсеивает строки.
Источник-таблица пока экспериментальный и не доведён до конца. Для продакшена опирайтесь на каталог и сайт. Таблицу держите как forward-looking паттерн.
Подключите базу к агенту
Передайте базы в execute({ knowledge }). Агентный цикл сам получит инструмент поиска. Регистрировать вручную ничего не нужно.
import { Conversation } from '@holocronlab/botruntime-runtime'
import { HelpDesk } from '../knowledge'
export default new Conversation({
channel: '*',
handler: async ({ execute }) => {
await execute({
instructions:
'Ты — оператор поддержки. Отвечай по-русски, кратко и по существу. ' +
'Если ответа нет в базе знаний — честно скажи, что не знаешь.',
knowledge: [HelpDesk],
})
},
})Что платформа делает под капотом, когда в execute передан непустой knowledge:
Добавляет инструмент search_knowledge. В его описание попадают имена и description всех переданных баз, чтобы модель понимала, где что искать.
Дописывает инструкции. К вашим instructions добавляется блок с перечнем баз и указанием предпочитать знания из них общей эрудиции модели.
Возвращает пассажи с цитатами. Каждый найденный пассаж приходит модели обёрнутым в тег-источник (с file и title). Модель обязана проставить эти цитаты инлайн в ответе.
Ведёт себя честно при пустом результате. Если по запросу ничего не нашлось, инструмент прямо велит модели не выдумывать ответ, а сказать, что данных нет.
Цитаты — часть контракта поиска. Если пассажи найдены, модель добавляет ссылки на источники. Проверяемые факты должны опираться на цитату из документа.
Как устроено хранилище
При индексации документ режется на пассажи — короткие фрагменты с эмбеддингами. Поиск семантический. Запрос сравнивается с векторами пассажей, наверх всплывают близкие по смыслу, а не по словам. Пассажи и метаданные живут на стороне платформы. Бот обращается к ним через API.
Искать можно и прямо из кода бота — через client.searchFiles. Он ищет по всему файловому хранилищу бота и возвращает пассажи с метаданными для своей обработки:
import { client } from '@holocronlab/botruntime-runtime'
const { passages } = await client.searchFiles({
query: 'как вернуть предоплату',
limit: 10,
contextDepth: 2,
})
for (const p of passages) {
console.log(p.file.key, '→', p.content)
}Каждый пассаж несёт content, score (близость), meta (type, pageNumber, position) и file (key, contentType, tags). tags в запросе фильтруют по плоским строковым тегам файлов. contextDepth (0–10) приклеивает соседние пассажи к каждому попаданию.
API базы знаний
Базы знаний и пассажи доступны и по admin-API — для программного наполнения и инспекции. Все эндпоинты bot-scoped. Авторизация api-ключом бота: Authorization: Bearer <api-key>.
Создать базу знаний
POST /v1/files/knowledge-bases
| Параметр | Тип | Обязательно | Описание |
|---|---|---|---|
name | string | да | Имя базы. |
tags | object | нет | Плоская карта строковых тегов для фильтрации. |
curl -X POST https://botruntime.ru/v1/files/knowledge-bases \
-H "Authorization: Bearer $BOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "help-desk", "tags": { "env": "prod" } }'{
"knowledgeBase": {
"id": "142",
"name": "help-desk",
"tags": { "env": "prod" }
}
}Список, обновление, удаление
| Метод и путь | Назначение |
|---|---|
GET /v1/files/knowledge-bases | Список баз. Параметры: tags, pageSize, nextToken (keyset-пагинация). |
PUT /v1/files/knowledge-bases/{id} | Переименовать базу / заменить теги (name обязателен). |
DELETE /v1/files/knowledge-bases/{id} | Удалить базу. |
Наполнить файл пассажами
PUT /v1/files/{id}/passages — задаёт пассажи файла явно (полная замена), в обход авто-нарезки. Полезно, когда фрагменты вы готовите сами.
curl -X PUT https://botruntime.ru/v1/files/kb%2Fdogovor.md/passages \
-H "Authorization: Bearer $BOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"passages": [
{ "content": "Возврат можно оформить в течение 14 дней после получения.", "type": "paragraph" },
{ "content": "Условия возврата товара", "type": "title" }
]
}'type — один из title, subtitle, paragraph, blockquote, list, table, code, image (по умолчанию paragraph). Пустой content отклоняется с 400. Ключ файла в пути передаётся %2F-экранированным.
Прочитать пассажи файла: GET /v1/files/{id}/passages (постранично, pageSize + nextToken).
Семантический поиск по файлам
GET /v1/files/search — поиск пассажей по всему хранилищу файлов бота.
| Параметр | Тип | Обязательно | Описание |
|---|---|---|---|
query | string | да | Поисковый запрос. |
limit | number | нет | Максимум пассажей в ответе. |
contextDepth | number (0–10) | нет | Сколько соседних пассажей приклеить к каждому попаданию. |
withContext | boolean | нет | Вернуть соседние пассажи отдельными записями с меткой preceding/current/subsequent. |
tags | object (URL-encoded JSON) | нет | Фильтр по тегам файлов. Поддержаны плоские строковые значения. |
curl -G https://botruntime.ru/v1/files/search \
-H "Authorization: Bearer $BOT_API_KEY" \
--data-urlencode "query=возврат предоплаты" \
--data-urlencode "contextDepth=2"Часть опций контракта поиска сознательно не поддержана. Она возвращает 501, а не тихо игнорируется. consolidate=true и includeBreadcrumb=true требуют дерева заголовков, которого плоское хранилище пассажей не несёт. То же с tags-фильтром: OR-массивы и not-исключения дают 501. Это fail-loud: сервер не делает вид, что применил то, что не умеет.