Перейти к содержанию

Бюджет контекста и порядок источников

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

Состояние lesson-07 добавляет context.py. Для лаборатории бюджет равен 2400 символам. Это не токены конкретной модели и не стоимость в рублях. В учёт входят текст, название, адрес, идентификатор и небольшой запас на оформление. Мы специально используем понятную величину, доступную стандартной библиотеке Python. Перед настоящим вызовом конкретной модели потребуется её токенизатор или иное проверенное ограничение входа; текущий бюджет лишь делает механизм отбора обозримым.

Почему целый фрагмент важнее обрезанной строки

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

Наша функция pack принимает упорядоченные кандидаты и перебирает их без изменения score. Для каждого вычисляет стоимость. Если целый объект помещается, он добавляется; если нет, пропускается. Обрезания текста нет. Это сохраняет внутреннее происхождение каждой части и позволяет позже взять из неё точную цитату. Однако целая часть тоже может оказаться недостаточной по смыслу: фрагментация уже могла отделить условие от вывода. Поэтому упаковка сохраняет существующую единицу, но не исправляет ошибки разбиения.

Предел не является строгим размером HTTP-тела. JSON добавляет собственные символы экранирования, prompt содержит инструкции, а модельный адаптер включает дополнительные параметры. В Python длина строки также не равна размеру её UTF-8-представления. Мы рассматриваем этот расчёт как локальную политику контекста с явным запасом, а не как доказательство соблюдения любого лимита провайдера. При переходе к конкретной модели нужно отдельно измерить сериализованный запрос и предусмотренный ею входной контекст.

Порядок и пропуск большой части

Верхний кандидат обычно важнее нижнего, поэтому начинаем с него. Но если он слишком велик и не помещается, функция продолжает обход: более короткий следующий источник всё ещё может оказаться полезным. Это жадный алгоритм, а не поиск оптимального сочетания всех фрагментов. Он не максимизирует суммарную релевантность и не пытается балансировать разнообразие тем. Для маленькой серии такой порядок помогает увидеть цену решения. Сложную оптимизацию стоит вводить только после появления измеримой проблемы на своих вопросах.

Результат иногда будет содержать несколько фрагментов одного документа. Тогда повторятся название и URL, а часть бюджета уйдёт на одинаковые метаданные. В будущем можно ограничить число частей на один doc_id или объединить соседние разделы. Но это меняет учебный контракт: объединённый текст должен сохранять перечень исходных частей и версии. Нельзя просто слить две записи и оставить только первый id, иначе ссылки ответа будут описывать не весь фактически переданный контекст. Пока мы сохраняем отдельные объекты и явно принимаем небольшое повторение.

На вопрос о черновиках могут пройти части sitemap и editorial-policy. Это полезное сочетание: первая объясняет состав карты, вторая — порядок работы с локальными материалами. Но если бюджет уменьшить слишком сильно, останется только верхний кандидат, и ответ потеряет одну сторону вопроса. Само наличие второго документа в базе не гарантирует его участие. Именно поэтому в уроке об оценке отдельно посмотрим верхние результаты поиска и фактические источники ответа. Эти наблюдения раскрывают разные места потери информации.

Метаданные тоже занимают место

Стоимость включает длину URL и идентификатора. Длинный исторический PHP-путь может занимать больше символов, чем короткий современный адрес, хотя обе ссылки одинаково важны для происхождения. Не следует сокращать такой URL до главной страницы ради красивой упаковки. Человек должен попасть в нужный материал, а сохранение старых адресов является частью задачи ProfessorWeb. Если служебная строка слишком длинна, лучше изменить формат сериализации или число кандидатов, сохранив правильную ссылку, чем подменить адрес источника.

Название помогает модели и человеку понять тему; идентификатор связывает будущую ссылку с разрешённым объектом. Удалять id из контекста опасно, если потом просить модель вернуть ссылки. Она будет вынуждена самостоятельно воспроизвести длинный URL и может ошибиться в одной части пути. В модельном шаге мы поэтому передадим короткие id, а настоящее отображение ссылок выполнит код приложения из заранее разрешённых метаданных. Такой контракт сокращает риск придуманного адреса без обещания, что каждая фраза модели истинна.

Посмотрите полный context.py ниже. Вызов pack не знает вопроса, роли или базы: он получает уже разрешённые и ранжированные объекты. Это хорошая граница для простого изменения бюджета. Доступ нельзя переносить внутрь pack, иначе часть запрещённого текста могла бы уже участвовать в поиске. Аналогично pack не должен перечитывать Markdown с диска: тогда context перестанет соответствовать версии индекса, по которой считалась релевантность. Каждый слой использует свой вход и сохраняет происхождение дальше.

Ожидаемое поведение на небольшом корпусе

При самостоятельном запуске python3 app.py "sitemap опубликованных страниц" вернутся кандидаты, которые поместились в бюджет. Их текст останется целым, а порядок — прежним относительно ранжирования. Сумма рассчитанных стоимостей не должна превышать выбранный предел. Эти действия здесь не выполнялись. Чтобы увидеть механизм вручную, выпишите длины двух частей и сравните их сумму с 2400: такой разбор полезнее слепого доверия числу, которое программа выводит или скрывает.

Для связанного варианта временно задайте budget=300 в app.py. Возможно, ни один целый фрагмент не поместится, хотя поиск нашёл подходящие документы. Пустой контекст в этом случае объясняется ресурсным ограничением, а не отсутствием знания. Интерфейс ещё не различает эти причины отдельными статусами, но разработчик должен понимать разницу. Следующий урок использует выбранные части для извлекающего ответа с точными цитатами. Это позволит проверить происхождение до подключения необязательной языковой модели.

Полный файл context.py из состояния lesson-07:

def pack(rows, budget=2400):
    selected, total = [], 0
    for row in rows:
        cost = len(row["text"]) + len(row["title"]) + len(row["url"]) + len(row["id"]) + 30
        if total + cost <= budget:
            selected.append(row)
            total += cost
    return selected

Оглавление серии · Исходники всех шагов · Учебные маршруты

Пример остаётся локальным черновиком. При подготовке он не выполнялся; все описанные эффекты являются ожидаемыми, а не результатами запущенных проверок.