Гайд · TNWS AI

Как ограничить размер RAG-контекста по байтам без обрезки цитаты

6 мин—

Собираем RAG-контекст целыми фрагментами с лимитом UTF-8-байтов, не разрезая цитату или русскую букву.

Задача и граница применения

Не оборвать посреди слова или цитаты найденный фрагмент при сборке ограниченного контекста. Этот пример использует лимит UTF-8-байтов для локального транспорта, а не лимит токенов модели: это разные единицы. Разрешённые chunks идут в уже согласованном порядке. Каждый добавляется только целиком вместе с заголовком; если следующий не помещается, сборка останавливается. В реальном RAG нужны ещё поля источника и резерв для системных инструкций, но их байты должны считаться тем же способом. Лимит из примера рассчитан на синтетические строки и не является ограничением API. В синтетическом бюджете размер заголовка и разделителя входит в блок. Это важно: если считать только text, итоговая сериализация выйдет за ограничение. Дополнительно проверьте строку с кириллицей и сравните её byte length с количеством символов, чтобы обнаружить неверную единицу измерения. Когда появится новая секция вроде ссылки или номера страницы, добавьте её в render, а не приклеивайте после отбора. Иначе проверка размера станет ложноположительной. При пустом out не отправляйте пустой контекст модели как будто поиск успешно завершён. Этот практический сценарий рассчитан на команду, которая контролирует данные для поискового слоя RAG и может хранить синтетические фикстуры в репозитории. Он не требует отправлять реальные клиентские документы стороннему сервису. Все ID, вопросы и численные результаты ниже сконструированы только для демонстрации. Проверка официальной документации Python и синтаксиса GitHub Actions: 19.09.2026; в собственном окружении перепроверьте версии перед запуском.

Как подготовить фикстуру

Есть два фрагмента: C-1 «Оплата сегодня» и C-2 «Возврат завтра». Бюджет равен размеру первого; ожидается только C-1 без обрезанного C-2. Сначала отделите эталон, согласованный человеком, от фактической выдачи поисковой системы. Не исправляйте gold по результатам модели: иначе тест перестанет быть независимым. Стабильный идентификатор вопроса позволит повторить сбой без публикации текста закрытой заявки. Если документы меняются, записывайте номер версии индекса и дату действия содержимого, иначе повторная проверка через месяц будет сравнивать разные базы.

Шаблон входа для переноса примера на свою коллекцию: для каждого вопроса заведите поля id, question, expected_doc_ids, retrieved_doc_ids и, где требуется, index_version. Согласуйте с предметным владельцем, что именно считается релевантным, доступным и актуальным источником. Это рабочая схема сбора данных, а не универсальный формат API. В таблице ревью отдельно отметьте спорные случаи, которые нельзя разрешить одной лишь строковой операцией.

Пошаговая проверка

  1. Создайте файл scripts/check_retrieval.py; скопируйте Python-шаблон целиком. Использованные названия библиотечных функций сверены с официальными страницами в конце статьи.
  2. Запустите python scripts/check_retrieval.py локально. Сопоставьте консольный вывод с ожидаемым результатом в описании входа; демонстрация обязана проходить без внешнего API и ключей.
  3. Скопируйте шаблон и подтвердите, что первый фрагмент занимает весь демонстрационный бюджет. В своём приложении сначала сформируйте конечный формат блока вместе с source_id, заголовком и разделителями, затем считайте размер этого блока, а не исходного text отдельно. При нехватке места не обрезайте ссылку на источник; выберите иной фрагмент или явно сформируйте отказ. Проверяйте отдельно токены используемой модели её официальным способом: этот байтовый лимит ничего о них не говорит.
  4. Для регрессии положите тот же файл в репозиторий и добавьте workflow из второго блока в .github/workflows/rag-retrieval.yml. Проверка запускается на pull_request с разрешением contents: read; не передавайте в неё настоящие секреты и закрытые документы.
  5. Откройте job retrieval-check в Actions на конкретном коммите. Сравните исходные ID и обнаруженные нарушения с локальным прогоном. При расхождении проверьте версию фикстур и скрипта, а не меняйте эталон до выяснения причины.
# scripts/check_retrieval.py
rows=[("C-1","Оплата сегодня"),("C-2","Возврат завтра")]
def render(row):
    return row[0]+": "+row[1]+"\n"
budget=len(render(rows[0]).encode("utf-8"))
out=[]; used=0
for row in rows:
    block=render(row)
    size=len(block.encode("utf-8"))
    if used+size>budget: break
    out.append(block); used+=size
assert out==[render(rows[0])]
assert used==budget
print("Included IDs:",[r[0] for r in rows[:len(out)]])
name: RAG retrieval contract
on:
  pull_request:
permissions:
  contents: read
jobs:
  retrieval-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: '3.13'
      - run: python scripts/check_retrieval.py

Реалистичный пример результата

Вход: Есть два фрагмента: C-1 «Оплата сегодня» и C-2 «Возврат завтра». Бюджет равен размеру первого; ожидается только C-1 без обрезанного C-2. После запуска ожидается исход, явно проверяемый инструкциями assert в коде. Не публикуйте вымышленное значение как измеренную характеристику вашей поисковой системы. Для рабочего отчёта приложите версию индекса, выбранные фильтры, размер размеченного набора, дату фиксации эталона и список ID провалившихся случаев. По каждому ID откройте первичный документ, прежде чем менять конфигурацию ранжирования: неправильно размеченный источник может выглядеть как технический дефект.

Типовые ошибки и ограничения

Алгоритм break может не включить поздний короткий фрагмент, даже если он поместился бы; это сознательная политика сохранения ранга. Для оптимального отбора нужна отдельная задача. UTF-8-байты не равны символам или токенам; не называйте результат лимитом контекстного окна. При расширении теста сначала добавьте отрицательный случай: намеренно испортите один элемент фикстуры и убедитесь, что проверка обнаружила его. Затем верните исходный набор и проверьте положительный случай. Это важнее зелёного CI на данных, где ошибка случайно отсутствует. Не логируйте полный ответ модели, содержимое закрытых документов или персональные сведения только ради диагностики.

Отдельно разделяйте ошибку данных, ошибку ранжирования и ошибку генерации. Если тест выполняется на другом индексе или другой редакции источника, результат нельзя напрямую сравнить с предыдущим. Снимок исходной выдачи, код и эталон должны относиться к одному запросу и одной версии доступных документов. Указанный демонстрационный результат не устанавливает порог качества для бизнеса; порог согласуют по отдельному размеченному набору и цене ошибки.

Чек-лист финальной проверки

  • Задача теста и применимость понятны владельцу корпуса.
  • Входные ID и эталон не взяты из ответа оцениваемой модели.
  • Демо запускается локально и даёт ожидаемый результат.
  • Преднамеренно неверный вход обнаруживается проверкой.
  • Зафиксированы версия индекса, редакция документов и условия доступа.
  • В логах CI нет приватных документов и секретов.
  • Разделены ошибка поиска, ошибка эталона и ошибка генерации.
  • После исправления проверены соседние пограничные случаи.

FAQ

Почему считаются байты?

В этой задаче контролируется локальный размер сериализованного текста.

Можно ли заменить на len(block)?

Нет, кириллица в UTF-8 занимает другое число байтов.

Почему не резать последний chunk?

При обрезке можно потерять смысл или ссылку.

Что если первый chunk не помещается?

Верните пустой список с явным статусом и пересмотрите стратегию.

Что делать, если тест не прошёл

Сохраните ID вопроса и конкретные ID найденных источников. Повторите запрос на той же версии индекса и проверьте фильтры доступа, затем откройте эталон и убедитесь, что он по-прежнему действует. Если неверен сам эталон, сначала получите предметное подтверждение исправления; если неверен поиск, оставьте проваливающийся тест и только потом меняйте индекс или алгоритм ранжирования. После исправления повторите весь набор, включая отрицательный пример. В публичном отчёте опишите методику и происхождение тестовых данных, а не только красивую среднюю цифру.

Официальные первоисточники

Проверено 19.09.2026: документ 1, документ 2, документ 3, документ 4. Описание и примеры написаны самостоятельно; при переносе на другое окружение перепроверьте названия функций и синтаксис workflow.

Читайте также

Комментарии

Пока тихо. Скажите первое слово