Въведение
За да използваш embeddings, превърни всеки документ (текст, код, описания на продукти) в вектор, запази векторите във vector store (например Postgres + pgvector или специализирана услуга) и при търсене вгради (embed) заявката, намери най-близките вектори и върни най-добрите пасажи/документи. Embeddings са практичният „мост“ между естествен език и математическо търсене по смисъл.
Embeddings са основата на семантичното търсене, препоръки, класификация по сходство, deduplication и Retrieval-Augmented Generation (RAG). Идеята е проста: еднакви по смисъл текстове се оказват близки като вектори, а различните се отдалечават.
Ако ти трябва търсене „какво има предвид човекът“, embeddings почти винаги са по-точни от чисто търсене по ключови думи.
В тази HOW-TO статия ще минем през целия pipeline: подготовка на данни, chunking, избор на модел, съхранение, индексиране, kNN търсене, re-ranking и практики за продукция.
Стъпка 1: Дефинирай задачата и метриката за успех
Преди да пишеш код, фиксирай какво оптимизираш. Най-честите сценарии:
- Семантично търсене в база знания (FAQ, документи, вътрешни политики)
- RAG за чатбот (намиране на пасажи + генерация)
- Откриване на дубликати/почти дубликати (near-duplicate)
- Препоръки (подобни продукти/статии)
- Клъстеризация и тематично групиране
Избери метрика за оценка. Практични варианти:
Precision@kиRecall@kза търсенеMRR(Mean Reciprocal Rank) за „колко високо е правилният резултат“- Човешка оценка на релевантност по 3-точкова скала (нерелевантно/частично/релевантно)
- За RAG: „answer accuracy“ + „groundedness“ (дали отговорът е подкрепен от пасажи)
Събери малък тестов набор: 30-100 реални заявки + очаквани документи. Това ще ти спести дни „тунинг на сляпо“.
Стъпка 2: Подготви съдържанието (cleaning + chunking)
Embeddings работят най-добре, когато входът е чист и последователен. Минимални правила:
- Премахни навигация, футъри, повторяеми менюта
- Нормализирай whitespace
- Запази заглавията и йерархията (H1/H2/H3), защото дават контекст
- Извади таблици като текст (или отделни „редове“), ако са важни за търсене
Chunking стратегия
„Chunk“ е парче текст, което вграждаш като един вектор. Добрият chunking решава повече проблеми от смяната на модел.
Практични подходи:
- По параграфи/секции (предпочитано за документация)
- По изречения с плъзгащ прозорец (ако текстът е неструктуриран)
- По токени с overlap (например 20-25% припокриване)
Насоки:
- Ако chunks са твърде дълги, търсенето става „размазано“ (векторът описва твърде много теми)
- Ако са твърде къси, губиш контекст и получаваш фалшиви съвпадения
Добави метаданни към всеки chunk:
doc_id,source,url(ако имаш),section_title,updated_at,language- за код:
repo,path,symbol,commit
В продукция точността най-често пада заради лош chunking и мръсни данни, не заради „лош модел“.
Стъпка 3: Генерирай embeddings (API и batching)
Най-честият вариант е да използваш готов embedding модел (например през API). Важно е:
- Вградиш и документите, и заявките със същия модел
- Да batch-ваш заявки (по-ниска цена и по-висока производителност)
- Да кешираш embeddings за документи (генерираш при ingest, не при всяко търсене)
Пример (Python, псевдо-практичен)
from openai import OpenAI
client = OpenAI()
def embed_texts(texts):
# texts: list[str]
resp = client.embeddings.create(
model="text-embedding-3-small",
input=texts,
)
return [d.embedding for d in resp.data]
Пример (Node.js)
import OpenAI from "openai";
const client = new OpenAI();
export async function embedTexts(texts) {
const resp = await client.embeddings.create({
model: "text-embedding-3-small",
input: texts,
});
return resp.data.map(d => d.embedding);
}
Практика: използвай отделен job за ingest (ETL), който:
- чете документите
- прави chunking
- генерира embeddings
- записва в базата
Така търсенето става евтино и бързо: при заявка embed-ваш само query-то.
Стъпка 4: Съхрани embeddings във vector store (pgvector вариант)
Ако вече използваш Postgres, pgvector е често най-прагматичният избор: една база, транзакции, лесни join-ове с метаданни.
Минимална схема
CREATE TABLE doc_chunks (
id bigserial PRIMARY KEY,
doc_id text NOT NULL,
chunk_index int NOT NULL,
content text NOT NULL,
embedding vector(1536),
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
created_at timestamptz NOT NULL DEFAULT now()
);
-- Индекс за ANN (пример: HNSW)
CREATE INDEX doc_chunks_embedding_hnsw
ON doc_chunks
USING hnsw (embedding vector_cosine_ops);
Забележки:
- Размерът
vector(1536)е пример. Провери размерността на избрания модел и я фиксирай в схемата. - Избери дистанция: cosine е най-често удобна за текст.
- Ако имаш много данни, тествай HNSW срещу IVFFlat и настрой параметрите спрямо latency/recall.
Стъпка 5: Семантично търсене (kNN) + филтри + re-ranking
Базово kNN търсене
SELECT
doc_id,
chunk_index,
content,
1 - (embedding <=> $1) AS score
FROM doc_chunks
WHERE metadata->>'language' = 'bg'
ORDER BY embedding <=> $1
LIMIT 10;
Тук $1 е embedding на заявката.
Филтри по метаданни
Embeddings рядко са достатъчни сами. Добави филтри:
- само последна версия (
updated_at) - само конкретен продукт/модул
- само определен тип документ (например policy, FAQ, tutorial)
Re-ranking (силен ъпгрейд на качеството)
Често работи много добре комбинация:
- Vector retrieval за top 20-50
- Re-rank с по-силен модел (cross-encoder / LLM scoring) за топ 5-10
Това пази latency разумен и качва точността.
Hybrid search (вектори + ключови думи)
За някои заявки хората очакват точни съвпадения (номера на фактура, кодове, имена). Hybrid подход:
- BM25/FTS за точни термини
- vector search за смисъл
- смесване на резултатите (например линейна комбинация на рангове)
Стъпка 6: Превърни pipeline-а в „продукционно качество“
Embeddings проектите често работят „на лаптоп“, но се чупят в продукция. Минимален production checklist:
- Версионирай ingestion pipeline (код + конфиг)
- Версионирай embeddings (model name + дата) в metadata
- Добави backfill стратегия при смяна на модел (batch job, постепенна миграция)
- Наблюдавай качество: drift (нови теми), празни/шумни резултати, latency
- Кеширай query embeddings за повтарящи се заявки (с TTL)
- За чувствителни данни: криптиране, RLS/ACL, audit лог
Най-важният „трик“ за стабилно качество е да измерваш Recall@k на реални заявки всяка седмица и да следиш регресии след ingest промени.
Съвети за по-добри резултати
- Започни с малък корпус (например 200-500 документа) и тестов набор от заявки.
- Използвай overlap при chunking, ако документите имат дълги обяснения.
- Съхранявай оригиналния текст и заглавията, за да можеш да показваш „цитирания“ в UI.
- Ако работиш на български, не смесвай езици в един chunk без нужда.
- Поддържай „stoplist“ за шумни секции (повторяеми legal текстове, менюта).
Чести грешки, които да избягваш
- Да embed-ваш цели PDF-и като един chunk.
- Да смениш модела без backfill и без да отбележиш версията.
- Да тестваш само с 5 „примерни“ заявки вместо реални.
- Да връщаш директно топ 1 резултат без re-ranking или без да показваш пасажи.
- Да игнорираш филтрите по метаданни (често те носят най-големия lift).
Често задавани въпроси
1) Какъв vector store да избера: Postgres + pgvector или специализирана услуга?
Ако вече имаш Postgres и данните ти не са огромни, pgvector е отличен старт. Ако имаш много милиони вектори, сложни multi-tenant нужди или искаш управлявано скалиране, специализирана услуга може да е по-лесна.
2) Трябва ли да нормализирам векторите?
При cosine similarity обикновено работиш с нормализирани вектори или с оператори, които го управляват. Важно е да следваш препоръките на конкретния store/индекс.
3) Колко голям да е chunk-ът?
Няма универсално число. Започни със секции/параграфи и тествай с реални заявки. Ако често връщаш „не това“, намали размера; ако губиш контекст, увеличи и добави overlap.
4) Как да подобря качеството без смяна на модел?
Първо оправи cleaning + chunking, после добави метаданни филтри и re-ranking. Това често носи по-голям ефект от смяна на embedding модел.
5) Как да правя миграция към нов embedding модел?
Запази старите вектори, добави нова колона/таблица за новите embeddings и прави dual-retrieval за период. После премини към новите, когато метриките са стабилни.
Актуалност и източници
Цени и наличности на embedding модели и услуги се променят. Провери официалните страници за pricing и документацията на избрания vector store (например OpenAI pricing/model docs и документацията на pgvector) преди да фиксираш архитектурни решения.
Пример: end-to-end embeddings pipeline за вътрешно търсене (semantic search)
Ето работеща рамка, която можеш да приложиш за вътрешна база знания (Confluence/Notion/Wiki), сайт с документация или help center:
- Ingest (ежедневно/на час): изтегляш нови и променени страници, чистиш HTML, извличаш само полезното съдържание.
- Chunking: разделяш по секции (заглавия + параграфи). За всяко парче добавяш метаданни като продукт, версия, език, URL и дата на последна промяна.
- Embedding: генерираш вектори за всеки chunk на партиди и ги записваш (включително
modelиmodel_versionв metadata). - Indexing: изграждаш ANN индекс (например HNSW) и периодично правиш
ANALYZE/поддръжка, ако си на Postgres. - Query: при търсене embed-ваш заявката, филтрираш по език/продукт, връщаш top 20.
- Re-rank + UI: преоценяваш топ резултатите (с по-силен модел или правила), показваш 2-3 пасажа с подчертани извадки, а не цял документ.
- Feedback loop: събираш „клик/доволство“ или прост бутон „полезно/неполезно“ и го използваш за подобряване на chunking и филтри.
Кеширане и цена
- Кеширай embeddings за документи завинаги (до следващата промяна в документа).
- За заявки кеширай най-честите (например 24 часа), защото хората често търсят едни и същи неща.
- Ако имаш много ingestion, обмисли batch режим (когато доставчикът го предлага) за по-ниска цена на 1M tokens.
Кога embeddings НЕ са правилният инструмент
- Ако търсиш точни идентификатори (номера, SKU, EGN, кодове), първо използвай keyword/структурирано търсене.
- Ако данните са 100% структурирани (таблици с ясни колони), класически SQL филтри и индекси често са по-добри.
- Ако имаш много малко съдържание (под 20-30 документа), правилно написано keyword търсене може да е достатъчно.