Запрос → эмбеддинг → top-k: мини-retrieval
Собираем рабочий retrieval-шаг целиком: от текста запроса до списка релевантных чанков.
Retrieval (извлечение) — на запрос пользователя находим top-k наиболее близких по смыслу чанков, чтобы подать их в промпт.
Три действия на каждый запрос
- Эмбеддим вопрос той же моделью, что и документы.
- Считаем близость вектора вопроса со всеми векторами чанков.
- Берём top-k самых близких — это и есть контекст.
Запускаемый мини-retriever
Сделаем игрушечный, но честный retriever без библиотек. «Эмбеддинг» — мешок слов (bag-of-words): вектор по словарю, где число = сколько раз слово встретилось. Метрика — косинус. Это упрощение реальных эмбеддингов, но весь поток retrieval тут настоящий.
import math, re
from collections import Counter
DOCS = [
"Питон — интерпретируемый язык программирования с динамической типизацией.",
"Списки в питоне изменяемы, а кортежи неизменяемы.",
"Кошки спят до шестнадцати часов в сутки.",
"Для веб-разработки на питоне популярны Django и Flask.",
]
def tokenize(t):
return re.findall(r"[а-яёa-z]+", t.lower())
vocab = sorted({w for d in DOCS for w in tokenize(d)})
idx = {w: i for i, w in enumerate(vocab)}
def embed(text):
v = [0.0] * len(vocab)
for w, c in Counter(tokenize(text)).items():
if w in idx:
v[idx[w]] = c
return v
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a)) or 1e-9
nb = math.sqrt(sum(x * x for x in b)) or 1e-9
return dot / (na * nb)
doc_vecs = [embed(d) for d in DOCS]
def retrieve(query, k=2):
qv = embed(query)
scored = sorted(
((cosine(qv, dv), d) for dv, d in zip(doc_vecs, DOCS)),
reverse=True,
)
return scored[:k]
query = "какие фреймворки для веб на питоне"
print("Запрос:", query)
for score, d in retrieve(query, k=2):
print(f" {score:.3f} {d}")Вывод:
Запрос: какие фреймворки для веб на питоне 0.667 Для веб-разработки на питоне популярны Django и Flask. 0.189 Списки в питоне изменяемы, а кортежи неизменяемы.
Retriever без всякого ИИ нашёл правильный документ первым: у запроса и нужного чанка много общих слов («веб», «питоне»). Второй результат притянулся словом «питоне» — он менее релевантен, и его близость заметно ниже. В настоящем RAG bag-of-words заменяют на эмбеддинги, и тогда найдётся даже документ без общих слов, но про то же по смыслу.
Параметр k
k — сколько чанков забрать. Мало (k=1–2) — рискуем не захватить нужный факт. Много (k=10+) — в промпт попадёт шум, ответ размоется, и вырастет цена. Типичный старт — k от 3 до 5, дальше подбор.
Итог
- Retrieval: эмбеддинг запроса → близость со всеми → top-k.
- Запрос эмбеддится той же моделью, что и документы.
- k балансирует полноту контекста и шум/цену; старт — 3–5.