Создание высокоточного корпоративного конвейера RAG: семантический сплиттинг документов, векторные эмбеддинги, векторный индекс HNSW в расширении pgvector, гибридный поиск (BM25 + Dense) с алгоритмом Reciprocal Rank Fusion (RRF) и финальный реранкинг через модель Cohere Rerank.
Наивный (Naive) RAG, состоящий только из векторного поиска по косинусному расстоянию, в реальных корпоративных проектах дает низкую точность (accuracy менее 60%):
ERR-4091-B), так как эмбеддинг размывает специфические лексемы в общее семантическое пространство.Гибридный корпоративный конвейер (Enterprise Hybrid RAG) объединяет точность полнотекстового индекса PostgreSQL (BM25 / tsvector), глубину семантического понимания векторных эмбеддингов и точность кросс-энкодера Cohere Rerank, отсекающего до 90% нерелевантного шума.
Конвейер поиска и генерации разделен на два независимых потока: индексация документов и двухфазная выборка при запросе пользователя:
+-------------------------------------------------------------------------------+
| INGESTION PIPELINE (Индексация) |
| PDF / Markdown -> Semantic Chunker -> text-embedding-3-small (1536 dim) |
| -> PostgreSQL (pgvector + HNSW Index + Full-Text Search tsvector) |
+-------------------------------------------------------------------------------+
|
+---------------------------------------v---------------------------------------+
| RETRIEVAL & GENERATION PIPELINE |
| |
| Пользовательский запрос: "Каковы условия SLA по инцидентам категории P1?" |
| |
| +------------------------------------+-----------------------------+ |
| | | | |
| v v | |
| 1. Dense Vector Search (pgvector) 2. Sparse Text Search (tsvector) | |
| Топ-25 векторных совпадений Топ-25 точных ключевых слов | |
| | | | |
| +-----------------> [RRF FUSION] <--+ | |
| | |
| v |
| Топ-20 объединенных чанков |
| | |
| v |
| 3. Cohere Rerank 3.5 (Cross-Encoder) |
| Глубокий анализ соответствия запрос <-> документ |
| | |
| v |
| Топ-4 эталонных чанка высокой точности |
| | |
| v |
| 4. LLM Generation (Gemini 2.5 / Claude 3.5 Sonnet) |
| Фактологически точный ответ с прямыми цитатами и источниками |
+-------------------------------------------------------------------------------+Активация расширения vector, создание таблицы chunks со столбцами content, embedding vector(1536) и fts tsvector.
Создание индекса HNSW (Hierarchical Navigable Small World) для векторов и GIN индекса для русского/английского полнотекстового поиска.
Написание функции match_documents_hybrid на PL/pgSQL, вычисляющей Reciprocal Rank Fusion со сглаживающим коэффициентом K=60.
Фильтрация предварительной выборки через Cohere API (модель rerank-v3.5) с отбором топ-3...топ-5 наиболее релевантных чанков.
Подача очищенного контекста в языковую модель с системным промптом, требующим строгого цитирования номеров разделов и отказа от ответа при нехватке данных.
Почему в промышленной эксплуатации pgvector для векторного поиска рекомендуется использовать индекс HNSW вместо IVFFlat?
Ниже представлен SQL-скрипт создания расширенной схемы в Supabase/PostgreSQL и TypeScript-модуль оркестрации гибридного пайплайна.
-- Включаем расширение pgvector
CREATE EXTENSION IF NOT EXISTS vector;
-- Создаем таблицу чанков корпоративной базы знаний
CREATE TABLE knowledge_chunks (
id BIGSERIAL PRIMARY KEY,
document_id TEXT NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
embedding vector(1536), -- Размерность text-embedding-3-small
fts tsvector GENERATED ALWAYS AS (to_tsvector('russian', content)) STORED,
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Создаем HNSW индекс для косинусного сходства векторов
CREATE INDEX knowledge_chunks_hnsw_idx
ON knowledge_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Создаем GIN индекс для полнотекстового поиска
CREATE INDEX knowledge_chunks_fts_idx
ON knowledge_chunks
USING gin (fts);
-- Хранимая функция гибридного поиска на базе Reciprocal Rank Fusion (RRF)
CREATE OR REPLACE FUNCTION match_knowledge_hybrid (
query_text TEXT,
query_embedding vector(1536),
match_count INT DEFAULT 20,
rrf_k INT DEFAULT 60
)
RETURNS TABLE (
id BIGINT,
document_id TEXT,
title TEXT,
content TEXT,
metadata JSONB,
combined_score FLOAT
)
LANGUAGE sql
AS $$
WITH vector_search AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY embedding <=> query_embedding) AS rank
FROM knowledge_chunks
ORDER BY embedding <=> query_embedding
LIMIT 50
),
text_search AS (
SELECT id, ROW_NUMBER() OVER (ORDER BY ts_rank_cd(fts, plainto_tsquery('russian', query_text)) DESC) AS rank
FROM knowledge_chunks
WHERE fts @@ plainto_tsquery('russian', query_text)
LIMIT 50
)
SELECT
k.id,
k.document_id,
k.title,
k.content,
k.metadata,
COALESCE(1.0 / (rrf_k + v.rank), 0.0) + COALESCE(1.0 / (rrf_k + t.rank), 0.0) AS combined_score
FROM knowledge_chunks k
LEFT JOIN vector_search v ON k.id = v.id
LEFT JOIN text_search t ON k.id = t.id
WHERE v.id IS NOT NULL OR t.id IS NOT NULL
ORDER BY combined_score DESC
LIMIT match_count;
$$;import { createClient } from '@supabase/supabase-js';
import { CohereClient } from 'cohere-ai';
interface RetrievedChunk {
id: number;
document_id: string;
title: string;
content: string;
metadata: Record<string, unknown>;
combined_score: number;
}
export interface FinalRAGResult {
content: string;
documentId: string;
relevanceScore: number;
}
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const cohere = new CohereClient({
token: process.env.COHERE_API_KEY!
});
/**
* Генерация эмбеддинга запроса (OpenAI text-embedding-3-small)
*/
async function generateQueryEmbedding(query: string): Promise<number[]> {
const response = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
input: query,
model: 'text-embedding-3-small'
})
});
const json = await response.json();
return json.data[0].embedding;
}
/**
* Выполнение Enterprise Hybrid RAG с Re-ranking
*/
export async function executeEnterpriseRAG(
userQuery: string,
topK: number = 4
): Promise<FinalRAGResult[]> {
// 1. Параллельно вычисляем эмбеддинг для пользовательского запроса
const embedding = await generateQueryEmbedding(userQuery);
// 2. Вызываем хранимую процедуру гибридного поиска (BM25 + pgvector)
const { data: rawChunks, error } = await supabase.rpc('match_knowledge_hybrid', {
query_text: userQuery,
query_embedding: embedding,
match_count: 20, // Предварительная широкая выборка
rrf_k: 60
});
if (error || !rawChunks || rawChunks.length === 0) {
console.error('Ошибка гибридного поиска Supabase:', error);
return [];
}
const chunks = rawChunks as RetrievedChunk[];
// 3. Финальный кросс-энкодер Re-ranking через Cohere
const rerankResponse = await cohere.rerank({
model: 'rerank-v3.5',
query: userQuery,
documents: chunks.map(c => ({ text: `${c.title}\n\n${c.content}` })),
topN: topK
});
// 4. Формирование отфильтрованного эталонного контекста
const results: FinalRAGResult[] = rerankResponse.results.map(r => {
const originalChunk = chunks[r.index];
return {
content: originalChunk.content,
documentId: originalChunk.document_id,
relevanceScore: r.relevanceScore
};
});
return results;
}Нарезка текста наивным chunk.slice(0, 500) безжалостно режет предложения пополам, разрывает программный код, таблицы Markdown и списки определений. Модель эмбеддингов теряет семантику обрезанного фрагмента. Всегда используйте рекурсивное чанкование (RecursiveCharacterTextSplitter) с приоритетом разделения по заголовкам (###, ##), абзацам (\n\n) и предложениям, сохраняя перекрытие (overlap) в 10–15% токенов.
Передача 20 нереранжированных чанков перегружает контекстное окно языковой модели. Из-за феномена «Lost in the Middle» LLM обращает внимание лишь на первый и последний чанки, игнорируя критически важную информацию в середине. Использование кросс-энкодера Cohere с отбором топ-3...топ-5 документов является строгим стандартом enterprise-архитектур.
Выполнение векторного поиска без обязательного условия WHERE organization_id = current_org_id приводит к катастрофической утечке закрытых документов между клиентами сервиса. Убедитесь, что в SQL-процедуру передается идентификатор организации, а в PostgreSQL настроены политики Row Level Security (RLS).
Почему кросс-энкодер (Cross-Encoder, например Cohere Rerank) дает кардинально более высокую точность релевантности, чем био-энкодеры (Bi-Encoder embeddings)?