Суть Чтение реального кода RAG-pipeline — chunker, поиск по cosine similarity, стадия retrieve-then-rerank и сборщик контекста — с выбором исправления наибольшего рычага.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги RAG прячутся в коде, который работает без ошибок и возвращает правдоподобный текст. Читайте каждый сниппет так, как читали бы на ревью, и выбирайте дефект, который senior ловит до того, как тот выдаст уверенный неверный ответ.
Цель
Отработайте цикл ревью RAG-pipeline: заметить изъян chunking, баг в математике similarity, отсутствующую стадию rerank и ошибку сборки контекста, которая тихо роняет качество ответов.
Сниппет 1 — chunker
def chunk(text, size=1000): # split on a fixed character count, no overlap return [text[i:i + size] for i in range(0, len(text), size)]
Викторина
Completed
Какая проблема этого chunker для прозового корпуса даёт наибольший рычаг и каково первое исправление?
Heads-up Один гигантский чанк так разбавляет embedding, что recall на узких запросах рушится — один вектор теперь усредняет весь документ. Больше — не исправление; исправление — уважать смысловые границы с overlap.
Heads-up Разбиение по сырым байтам может ломать многобайтовые символы и делает только хуже. Реальный дефект — рассечение смысла по произвольным смещениям без overlap, а не выбор символ-vs-байт.
Heads-up Нулевой overlap гарантирует разрезание граничных фактов; overlap ~10–15% — та дешёвая страховка, которой требует юнит, а разбиение по смысловым единицам сохраняет цельные идеи.
Сниппет 2 — оценка similarity
import numpy as npdef score(query_vec, chunk_vec): # rank candidates by this value return np.dot(query_vec, chunk_vec)
Викторина
Completed
Ранжирование по сырому скалярному произведению вместо cosine similarity опасно, когда embeddings не нормализованы. Почему и каково исправление?
Heads-up Скалярное произведение определено при любой размерности. Проблема в том, что без нормализации оно смешивает величину с релевантностью, а не в числе измерений.
Heads-up Они совпадают, только когда у всех векторов одна норма. На ненормализованных embeddings величина просачивается в score и может менять порядок; сначала нормализуйте, чтобы сделать их эквивалентными.
Heads-up Скалярное произведение — дешёвая операция, на которой строятся ANN-индексы. Дефект — корректность на ненормализованных векторах, а исправление — нормализация/cosine, а не смена метрики ради скорости.
Сниппет 3 — retrieve и rerank
def answer(query): qv = embed(query) candidates = vector_store.search(qv, k=3) # top-3 by cosine context = "\n".join(c.text for c in candidates) return llm(f"Answer using:\n{context}\n\nQ: {query}")
Викторина
Completed
Senior отмечает это как причину низкого качества ответов на сложных запросах. Какое изменение даёт наибольший рычаг?
Heads-up Это чинит recall, но рушит precision: 50 чанков раздувают токены и стоимость и хоронят решающий в малозаметной середине. Широкий retrieval требует сужающей стадии rerank.
Heads-up Узкое место — то, что извлекает k=3, а не генерация. Если нужного чанка не было в top-3, ни модель крупнее, ни окно больше не порассуждают о тексте, который не извлекли.
Heads-up k=1 максимизирует хрупкость — один промах ANN даёт ноль релевантного контекста и гарантированную галлюцинацию. Паттерн — retrieve широко, затем rerank узко, а не retrieve узко.
Сниппет 4 — сборка контекста
def assemble(reranked, query): # reranked is sorted best-first blocks = [c.text for c in reranked] # best chunk first, rest after context = "\n\n".join(blocks) return f"{context}\n\nQuestion: {query}"
Викторина
Completed
Для длинного собранного контекста: что говорит lost-in-the-middle об этом порядке и как собирать вместо этого?
Heads-up Reranking задаёт, какие чанки оставить, но позиция всё ещё важна: внимание U-образное, поэтому даже релевантный текст в середине недоиспользуется. Ставьте лучшие доказательства по краям.
Heads-up Центр — наименее внимаемая зона. Ровно центр — худшее место для решающего чанка; края лучше.
Heads-up Модели хорошо внимают началу, так что вы потратите сильнейшую позицию на худший чанк. Сильные доказательства идут в начало и конец, а не слабейшие.
Итог
Каждый баг RAG читается в коде: chunking фиксированного размера без overlap рассекает факты на границах; ранжирование по сырому скалярному произведению на ненормализованных векторах даёт величине обойти смысл, поэтому используйте cosine; одиночный retrieve с k=3 без rerank — один промах ANN до галлюцинации, поэтому retrieve широко, затем rerank узко; а порядок сборки важен, потому что внимание U-образное — оставляйте мало чанков и ставьте сильнейшие доказательства по краям. Чините стадию pipeline с наибольшим рычагом, затем переоцените на отложенном наборе для подтверждения.