RAG (Retrieval-Augmented Generation) — способ дать OpenClaw доступ к вашим документам, базе знаний или корпоративным данным без отправки их в модель целиком. Агент ищет нужные фрагменты динамически и использует только релевантный контекст.
Зачем RAG в OpenClaw
Без RAG у агента две проблемы:
- Context overflow — если загрузить всю документацию, контекст переполняется
- Конфиденциальность — документы компании не должны уходить на внешние серверы
RAG решает оба: документы хранятся локально, в модель уходит только маленький релевантный фрагмент.
Типичные кейсы:
- Поиск по внутренней документации (Confluence, Notion)
- Ответы на вопросы по кодовой базе
- Юридические документы, договоры
- База знаний поддержки
Архитектура: как работает RAG
Документы
↓
[Chunking] — разбивка на фрагменты по 500-1000 токенов
↓
[Embeddings] — векторизация каждого фрагмента
↓
[Vector Store] — ChromaDB / Qdrant / Pinecone (хранение)
---
Запрос пользователя
↓
[Embedding] — векторизация запроса
↓
[Search] — поиск похожих векторов в хранилище
↓
[Context] — топ-3 релевантных фрагмента
↓
[OpenClaw] — использует фрагменты как контекст
Реализация через MCP-сервер
Лучший способ интегрировать RAG — создать MCP-сервер с инструментом поиска:
mkdir mcp-rag && cd mcp-rag
npm init -y
npm install @modelcontextprotocol/sdk chromadb @xenova/transformers
npm install -D typescript tsx @types/node
Шаг 1: Индексация документов
// scripts/index-docs.ts
import { ChromaClient, Collection } from "chromadb";
import { pipeline } from "@xenova/transformers";
import * as fs from "fs";
import * as path from "path";
const client = new ChromaClient({ path: "http://localhost:8000" });
// Используем локальную модель для embeddings (не отправляет данные в облако)
const embedder = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
async function embed(text: string): Promise<number[]> {
const output = await embedder(text, { pooling: "mean", normalize: true });
return Array.from(output.data);
}
function chunkText(text: string, chunkSize = 800, overlap = 100): string[] {
const words = text.split(" ");
const chunks: string[] = [];
for (let i = 0; i < words.length; i += chunkSize - overlap) {
chunks.push(words.slice(i, i + chunkSize).join(" "));
if (i + chunkSize >= words.length) break;
}
return chunks;
}
async function indexDirectory(docsPath: string, collectionName: string) {
const collection = await client.getOrCreateCollection({ name: collectionName });
const files = fs.readdirSync(docsPath).filter(f => f.endsWith(".md") || f.endsWith(".txt"));
for (const file of files) {
const content = fs.readFileSync(path.join(docsPath, file), "utf-8");
const chunks = chunkText(content);
for (let i = 0; i < chunks.length; i++) {
const embedding = await embed(chunks[i]);
await collection.add({
ids: [`${file}-chunk-${i}`],
embeddings: [embedding],
documents: [chunks[i]],
metadatas: [{ source: file, chunk: i }],
});
}
console.log(`✓ ${file}: ${chunks.length} chunks indexed`);
}
}
await indexDirectory("./docs", "company-docs");
console.log("Indexing complete!");
Запуск ChromaDB локально через Docker:
docker run -d -p 8000:8000 chromadb/chroma
Индексация:
npx tsx scripts/index-docs.ts
Шаг 2: MCP-сервер с поиском
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { ChromaClient } from "chromadb";
import { pipeline } from "@xenova/transformers";
const client = new ChromaClient({ path: "http://localhost:8000" });
const embedder = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
async function embed(text: string): Promise<number[]> {
const output = await embedder(text, { pooling: "mean", normalize: true });
return Array.from(output.data);
}
const server = new Server(
{ name: "rag-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "search_docs",
description: "Поиск по внутренней документации компании. Используй когда нужно найти информацию о процессах, политиках, технических деталях.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Поисковый запрос на русском или английском" },
top_k: { type: "number", description: "Количество результатов (default: 3)", default: 3 },
},
required: ["query"],
},
},
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "search_docs") {
const { query, top_k = 3 } = request.params.arguments as { query: string; top_k?: number };
const collection = await client.getCollection({ name: "company-docs" });
const queryEmbedding = await embed(query);
const results = await collection.query({
queryEmbeddings: [queryEmbedding],
nResults: top_k,
});
const formatted = results.documents[0].map((doc, i) => {
const source = results.metadatas[0][i]?.source ?? "unknown";
return `### Источник: ${source}\n${doc}`;
}).join("\n\n---\n\n");
return {
content: [{ type: "text", text: formatted || "Документы не найдены." }],
};
}
throw new Error("Unknown tool");
});
const transport = new StdioServerTransport();
await server.connect(transport);
Шаг 3: Подключение к OpenClaw
{
"mcpServers": {
"rag-docs": {
"command": "npx",
"args": ["tsx", "/path/to/mcp-rag/src/index.ts"]
}
}
}
Теперь в OpenClaw можно спросить:
> Что говорит наша документация о процедуре онбординга новых сотрудников?
Агент автоматически вызовет search_docs и ответит на основе ваших документов.
Альтернатива: Qdrant для больших объёмов
Для корпусов от 100 000 документов ChromaDB начинает тормозить. Qdrant быстрее:
docker run -d -p 6333:6333 qdrant/qdrant
npm install @qdrant/js-client-rest
import { QdrantClient } from "@qdrant/js-client-rest";
const qdrant = new QdrantClient({ url: "http://localhost:6333" });
// Создание коллекции
await qdrant.createCollection("docs", {
vectors: { size: 384, distance: "Cosine" },
});
// Поиск
const results = await qdrant.search("docs", {
vector: queryEmbedding,
limit: 3,
with_payload: true,
});
Облачный вариант: Pinecone
Если данные не конфиденциальные и нужна масштабируемость:
npm install @pinecone-database/pinecone
import { Pinecone } from "@pinecone-database/pinecone";
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY! });
const index = pc.index("company-docs");
// Upsert
await index.upsert([{
id: "doc-1",
values: embedding,
metadata: { text: chunk, source: "docs/handbook.md" },
}]);
// Query
const results = await index.query({
vector: queryEmbedding,
topK: 3,
includeMetadata: true,
});
Качество поиска: советы
Размер чанков: 500-1000 токенов — оптимально. Слишком маленькие теряют контекст, слишком большие — шум.
Overlap между чанками: 10-15% от размера чанка. Сохраняет смысл на границах.
Hybrid search: Комбинируйте векторный поиск с полнотекстовым (BM25). Векторный хорош для смысла, полнотекстовый — для точных терминов.
Reranking: Топ-10 из векторного поиска → переранжировать маленькой cross-encoder моделью → передать топ-3 агенту.
Обновление индекса: Автоматизируйте переиндексацию при изменении документов через файловый watcher или git hook.
Безопасность
Для конфиденциальных документов:
- Используйте локальные embeddings (Xenova/transformers) — данные не покидают сервер
- Разграничьте коллекции по ролям доступа
- Логируйте все запросы к search_docs
- Никогда не включайте секреты в индексируемые документы
Итог
RAG превращает OpenClaw из универсального агента в эксперта по вашей конкретной области. Инвестиция: 4-8 часов на настройку. Результат: агент отвечает на вопросы по документации компании точнее, чем большинство новых сотрудников.
Мини-туториал: Create RAG from .md files
Самый частый сценарий — собрать RAG из папки с Markdown-файлами (база знаний, документация, заметки Obsidian). За 10 минут:
# 1. Ставим зависимости
pip install langchain chromadb sentence-transformers
# 2. Скрипт индексации (ingest.py)
python <<'PY'
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import MarkdownTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
loader = DirectoryLoader('./knowledge-base', glob='**/*.md', loader_cls=TextLoader)
docs = loader.load()
chunks = MarkdownTextSplitter(chunk_size=800, chunk_overlap=100).split_documents(docs)
embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-base")
Chroma.from_documents(chunks, embeddings, persist_directory="./rag-db")
print(f"Indexed {len(chunks)} chunks from {len(docs)} files")
PY
После этого подключите получившийся ChromaDB к OpenClaw через MCP — и агент будет искать ответы по вашей папке .md файлов. Multilingual e5 — хорошая модель для русского. Для обновления — запускайте ingest.py по cron-расписанию, см. cron-jobs в OpenClaw.
Читайте также: Как создать MCP-сервер и Docker-контейнеризация OpenClaw.