open atlas
← Все проекты

backend · advanced · 9d

Сервис RAG с опорой на источники

Демо RAG, отвечающее по корпусу, собирается легко; сервис RAG, которому ты доверишь живых пользователей, — нет. Сложность не в поиске, а в опоре на источники: заставить модель говорить только то, что подтверждает найденный текст, прикреплять цитаты, которые читатель может проверить, и доказать на eval-наборе, что ответы не уплывают в уверенную выдумку. Ты соберёшь весь цикл — нарезка, эмбеддинги, хранилище, retrieval top-k, опора, цитаты, оценка — и нащупаешь, где именно он течёт.

RAG — это стандартный способ, которым команды ставят LLM поверх собственных знаний, и демо-версия обманчиво проста — именно поэтому столько выпущенных RAG-фич тихо галлюцинируют. Сеньорский навык — не подключить вызов эмбеддингов к векторному хранилищу; это всё, что ниже по течению от поиска: принуждение к опоре на источники, проверяемость цитат и удержание числа faithfulness, которое говорит правду, когда ты что-то меняешь. Собери это однажды, увидь, как модель цитирует чанк, который она на самом деле не подтверждает, а затем увидь, как твой барьер её ловит, — и ты больше никогда не спутаешь «демо ответило правильно» с «этому сервису можно доверять».

Результат

Работающий сервис, который загружает корпус в векторное хранилище и отдаёт эндпоинт /query, возвращающий обоснованный ответ с встроенными цитатами на исходные чанки, плюс eval-харнес, который оценивает faithfulness по фиксированному набору вопросов и роняет сборку, когда оценка падает ниже порога.

Этапы

0/6 · 0%
  1. 01Нарежь корпус и построй эмбеддинги

    Качество поиска решается здесь, ещё до первого запроса. Возьми корпус и нарежь его на чанки, достаточно большие, чтобы нести законченную мысль, но достаточно малые, чтобы top-k из нескольких штук покрыл вопрос, не зарывая модель в шум. Наивная нарезка фиксированного размера режет предложения пополам и отрывает заголовки, поэтому уважай структуру — абзацы, заголовки, пункты списков — и добавь немного перекрытия, чтобы факт, попавший на границу, уцелел хотя бы в одном чанке. Дай каждому чанку стабильный id и сохрани исходный документ, позицию и путь заголовков в метаданных; именно эта точная привязка к источнику понадобится позже для цитат. Затем построй эмбеддинг для каждого чанка и убедись, что векторы приходят с той размерностью, которую ждёт хранилище. Не поленись глазами просмотреть несколько чанков — если они читаются как мусор для тебя, то и искаться будут как мусор.

    Критерии готовности
    • Корпус нарезан на чанки со стабильными id, метаданными источника и заданным перекрытием, а выборка чанков читается как связные единицы.
    • У каждого чанка есть вектор-эмбеддинг ожидаемой размерности, воспроизводимо полученный из того же входа.
  2. 02Сохрани векторы и доставай top-k

    Теперь сделай векторы доступными для запросов. Загрузи каждый эмбеддинг вместе с метаданными в векторное хранилище, затем возьми входящий вопрос, построй для него эмбеддинг тем же способом и достань top-k ближайших чанков по косинусной близости или скалярному произведению. Первая ловушка — несовпадение метрики или нормализации между тем, как ты хранил векторы, и тем, как их запрашиваешь: расстояния выглядят правдоподобно, но ранжируют не те чанки первыми, и ты этого не заметишь, пока опора не поедет вкось. Вторая ловушка — относиться к top-k как к магическому числу: слишком мало — и подтверждающий факт оказывается чуть вне досягаемости, слишком много — и ты набиваешь контекст почти-промахами, которые соблазняют модель уплыть. Прозондируй: задай несколько вопросов с известными ответами, выведи найденные чанки с их оценками и убедись, что нужный фрагмент реально оказывается у верха. Этот слой поиска — фундамент, на котором стоит каждый следующий этап.

    Критерии готовности
    • Все векторы чанков и метаданные сохранены, а запрос возвращает свои top-k ближайших чанков с оценками близости.
    • Для набора известных вопросов чанк, реально содержащий ответ, попадает в возвращённый top-k.
  3. 03Обоснуй ответ цитатами

    Это этап, который превращает строку поиска в сервис RAG. Передай найденные чанки модели с промптом, который строго делает две вещи: отвечает только по предоставленному контексту и указывает id чанка за каждым утверждением. Когда в контексте нет ответа, правильный вывод — «по этим источникам не знаю», а не уверенная догадка, сшитая из претрейна модели. Сделай цитаты контрактом, а не пожеланием: распарси вывод модели, проверь, что каждый процитированный id реально был в найденном наборе, и отклони или почини ответ, который ссылается на чанк, который ты не отправлял. Структурированный вывод или tool-calling здесь помогает, потому что свободный текст «Источники: ...» расползается и его мучительно проверять. Сам сверь несколько ответов с процитированными чанками — в момент, когда поймаешь модель на цитировании чанка 7 под предложение, которое чанк 7 не подтверждает, ты поймёшь, почему опору нужно принуждать, а не доверять ей.

    Критерии готовности
    • Эндпоинт /query возвращает ответ, каждое утверждение которого несёт цитату на id чанка из найденного набора.
    • Когда поиск не даёт ничего релевантного, сервис воздерживается, а не выдумывает ответ.
  4. 04Оцени faithfulness на eval-наборе

    Пока ты не можешь это измерить, «ответы выглядят хорошо» — это ощущение, а не гарантия, и ощущения тихо деградируют в следующий раз, когда ты подправишь промпт или сменишь модель эмбеддингов. Собери фиксированный eval-набор: список вопросов по корпусу, каждый в паре с эталонным подтверждающим фрагментом и ожидаемой сутью. Затем оценивай каждый ответ на faithfulness — действительно ли каждое утверждение прослеживается до процитированного чанка? — наряду с более дешёвыми сигналами вроде recall поиска (нашёлся ли нужный чанк вообще?) и корректности воздержания (сказал ли он «не знаю» ровно тогда, когда должен?). Оценивать можно LLM-судьёй, которому дают ответ и его источники, но зафиксируй промпт судьи и выборочно проверяй его вердикты, ведь ненадёжный оценщик просто отмывает проблему. Свяжи харнес так, чтобы он запускался по требованию и выдавал единственное число faithfulness; именно это число позволяет менять что угодно в пайплайне и сразу видеть, стало ли лучше или тихо хуже.

    Критерии готовности
    • Eval-набор вопросов с эталонными источниками прогоняется от начала до конца и даёт единое число faithfulness плюс recall поиска.
    • Изменение ручки пайплайна (размер чанка, top-k, промпт) заметно двигает оценку, а регрессия ниже выбранного порога обнаруживается.
  5. 05Добавь гибридный поиск

    У чистого векторного поиска есть слепое пятно: он силён в смысле, но слаб на точных токенах — артикул, код ошибки, редкое имя собственное, которое эмбеддинг размазывает в соседние понятия. Добавь лексический ретривер (BM25 или полнотекстовый поиск твоего хранилища) и слей его результаты с векторными попаданиями, например через reciprocal rank fusion, чтобы чанк, который и означает нужное, и содержит точный термин, поднимался наверх. По желанию переранжируй слитых кандидатов кросс-энкодером для финального прохода на точность. Дисциплина тут — доказать, что гибрид реально помогает: перепрогони eval-набор с прошлого этапа и покажи, что recall поиска и faithfulness растут, а не стоят на месте. Если нет — ты добавил задержку и сложность впустую; скажи это честно, а не держи более тяжёлый пайплайн потому, что он звучит солиднее.

    Критерии готовности
    • Поиск сливает лексические и векторные результаты в единый ранжированный список, отдаваемый шагу опоры.
    • Eval-набор показывает измеримое улучшение (или честно зафиксированное отсутствие улучшения) по сравнению с чисто векторным поиском.
  6. 06Поставь барьер против необоснованных утверждений и наблюдай

    Eval-набор ловит регрессии пачкой; барьер ловит их на живом запросе, прежде чем плохой ответ дойдёт до пользователя. Добавь проверочный проход, который по черновику ответа и процитированным чанкам проверяет, что каждое утверждение действительно следует из найденного, — проверка вторым моделью «подтверждает ли это источник?» или более строгий разбор, отбрасывающий любое предложение, чья цитата не выдерживает. Когда утверждение не проходит, сервис должен деградировать безопасно: срезать неподтверждённое предложение, понизить уверенность или воздержаться — но никогда не выпускать выдумку. Затем сделай всё это наблюдаемым, ведь обоснованный сервис, который падает молча, — это просто уверенный лжец с лишними шагами: логируй на каждый запрос id и оценки найденных чанков, какие утверждения прошли или были отброшены, сработал ли барьер и стоимость в токенах, чтобы по жалобе на неверный ответ ты мог воспроизвести ровно то, что было найдено и почему модель сказала то, что сказала. Считай частоту срабатывания барьера продакшен-сигналом, за которым следишь, а не галочкой, которую поставил однажды.

    Критерии готовности
    • Ответ, чьё утверждение не подтверждается процитированным чанком, обрезается, помечается или отклоняется до возврата.
    • Каждый запрос логирует id найденных чанков, исход барьера и стоимость в токенах так, что зафиксированный сбой можно воспроизвести.

Рубрика

Джуниор Миддл Сеньор
Стратегия нарезки и качество поиска Чанки — фиксированные разбивки, режущие предложения пополам; метаданные (исходный документ, позиция) не хранятся, прослеживаемость источника невозможна. Чанки уважают структурные границы (абзацы, заголовки) с заданным перекрытием; каждый несёт стабильный id, исходный документ и позицию; для набора известных вопросов правильный чанк попадает в top-k. Размер чанка и перекрытие проверяются на eval-наборе, а не угадываются. Гибридный поиск (BM25 + вектор, слитый через reciprocal rank fusion) добавлен, и eval-набор доказывает улучшение recall для точно-токеновых запросов (артикулы, коды ошибок) по сравнению с чисто векторным, или отсутствие улучшения честно зафиксировано с объяснением.
Принуждение к опоре и контракт цитат Модель просят отвечать по контексту, но цитаты необязательные или в виде свободного текста без проверки; модель генерирует уверенные ответы, даже когда контекст не содержит ответа. Промпт принуждает к структурированным цитатам (id чанка на утверждение через structured output или tool-calling); каждый процитированный id проверяется против найденного набора; когда контекст пуст или не по теме, сервис возвращает явное воздержание, а не выдумку. Пост-поисковый проход барьера проверяет каждое утверждение против процитированного чанка (проверка вывода или строгий разбор), обрезает или помечает необоснованные предложения до того, как они дойдут до пользователя, а частота срабатывания барьера логируется на каждый запрос как продакшен-сигнал, а не настраивается однажды и больше не смотрится.
Eval-набор faithfulness и детект регрессий Качество оценивается вручную чтением нескольких ответов; количественного измерения нет и нет способа понять, улучшило или ухудшило изменение промпта. Фиксированный eval-набор вопросов с эталонными подтверждающими фрагментами прогоняется от начала до конца и даёт оценку faithfulness и recall поиска; изменение размера чанка или top-k заметно двигает оба числа. Харнес — это CI-гейт: прогоняется на каждом изменении пайплайна и падает ниже заявленного порога. Промпт LLM-судьи зафиксирован и выборочно сверяется с человеческими вердиктами, чтобы убедиться, что сам не галлюцинирует качество. Ты можешь назвать базовый уровень faithfulness, порог и ручку, изменение которой вызвало наибольшую регрессию в процессе разработки.
Наблюдаемость и защита от устаревшего контекста Сбои видны только в логах ошибок; нет пер-запросной записи о том, что было найдено или сработал ли барьер, что делает жалобу на неверный ответ невоспроизводимой. Каждый запрос логирует id и оценки найденных чанков, исход барьера и стоимость в токенах; жалоба на неверный ответ воспроизводится воспроизведением залогированного контекста поиска. Обновления корпуса переэмбеддят только изменённые документы и инвалидируют старые векторы атомарно (устаревший чанк с тем же id не должен оставаться после редактирования документа); eval-набор перепрогоняется после каждого обновления корпуса для подтверждения, что ни одна галлюцинация из старого контекста не просочилась. График частоты срабатывания барьера — часть дашборда, а не запоздалая мысль.
Эталонный разбор (спойлер)

Нарезка — это место, где устанавливается качество поиска: наивные фиксированные разбивки разлучают предложения и заголовки, а факт, попавший на границу, исчезает из каждого чанка. Перекрытие (10–20% от размера чанка) возвращает пограничные факты. Правильный размер чанка — то, что говорит eval-набор, а не угадка.

Цитаты должны быть контрактом, а не пожеланием: свободный текст «Источники: ...» расползается и его мучительно проверять. Structured output или tool-calling заставляет модель выводить машинно-проверяемую пару (утверждение, chunk_id). Затем пост-поисковый барьер спрашивает «действительно ли chunk_id подтверждает это утверждение?» — и сервис деградирует безопасно, когда нет.

Faithfulness без числа — это ощущение: «ответы выглядят хорошо» тихо деградирует в следующий раз, когда промпт или модель эмбеддингов меняется. Фиксированный eval-набор с порогом превращает качество в инвариант CI. Промпт LLM-судьи сам должен быть зафиксирован и выборочно проверен — ненадёжный оценщик отмывает проблему, а не ловит её.

Устаревший контекст — это скрытый режим отказа живого RAG-сервиса: устаревший чанк, который больше не отражает текущий документ, продолжает поступать в поиск и цитироваться долго после изменения факта. Единственный честный фикс — инкрементальное переэмбеддинование с инвалидацией вектора при обновлении: обновление корпуса, которое просто добавляет новые чанки без удаления старых, создаёт музей прошлых фактов, которые модель будет уверенно цитировать.

Сделай по-сеньорски

  • Добавь путь обновления корпуса, который перенарезает и переэмбеддит только изменённые документы и инвалидирует их старые векторы, чтобы индекс оставался корректным без полной пересборки, — и докажи на eval-наборе, что устаревшие чанки не протекают в ответы.
  • Преврати eval-харнес в CI-гейт: прогоняй его на каждое изменение пайплайна и роняй сборку, когда faithfulness или recall поиска падает ниже согласованного порога, чтобы «небольшая правка промпта» никогда не могла тихо испортить опору.
  • Нагрузи барьер состязательно: составь вопросы, ответа на которые нет в корпусе, и убедись, что сервис воздерживается, а не конфабулирует, и измерь, во сколько проверочный проход обходится по задержке и токенам против тех плохих ответов, что он предотвращает.

Навыки

chunking a corpus with overlap and stable idsembedding text and storing vectors with metadatatop-k similarity retrieval and rerankinggrounding an LLM answer with enforced citationsbuilding an eval set that scores faithfulnesswriting a guardrail that blocks unsupported claims

Рекомендуемый стек

node or pythonan embedding model (OpenAI text-embedding-3, or a local sentence-transformer)a vector store (pgvector, sqlite-vss, or a managed index)an LLM with structured output / tool-callinga small eval runner (custom or promptfoo/ragas-style)