Инженерия контекста приобрела значительную значимость с развитием больших языковых моделей, способных справляться со сложными задачами. Поначалу основные беседы на эту тему сосредотачивались на инженерии промптов: настройке одного промпта для достижения оптимальных результатов в рамках одной задачи. Но по мере роста возможностей языковых моделей инженерия промптов эволюционировала в инженерию контекста: оптимизации всех данных, подаваемых в модель, чтобы добиться максимальной эффективности при выполнении многоэтапных операций.
В этой публикации мы углубимся в агентную инженерию контекста, которая подразумевает оптимизацию контекста именно для агентов. Это отличается от классической инженерии контекста тем, что агенты обычно выполняют цепочки задач на протяжении длительного времени. Поскольку тема агентной инженерии контекста обширна, в этой статье мы разберем указанные ниже аспекты, а в последующей публикации затронем дополнительные вопросы.
- Конкретные рекомендации по инженерии контекста
- Сокращение или обобщение контекста
- Использование инструментов
Почему важно заниматься агентной инженерией контекста
Прежде чем перейти к деталям инженерии контекста, рассмотрим, почему агентная инженерия контекста имеет ключевое значение. Мы разделим это на две части:
- Причины применения агентов
- Необходимость инженерии контекста для агентов
Причины применения агентов
Во-первых, агенты применяются потому, что они превосходят статические вызовы языковых моделей в выполнении задач. Агент может получить запрос от пользователя, например:
Исправить эту ошибку, о которой сообщил пользователь {отчет об ошибке}
Такой сценарий невозможно реализовать в рамках одного вызова модели, поскольку требуется глубже разобраться в проблеме (возможно, уточнить детали у автора отчета), определить место в коде, где возникает ошибка, и, возможно, получить логи ошибок. Здесь на помощь приходят агенты.
Агент анализирует отчет об ошибке, использует инструмент для уточняющего вопроса пользователю, например: В какой части приложения проявляется эта ошибка? Затем агент находит соответствующее место в кодовой базе, запускает код для чтения логов ошибок и внедряет исправление. Все это предполагает серию вызовов модели и инструментов до полного разрешения проблемы.
Необходимость инженерии контекста для агентов
Теперь, когда понятно, зачем нужны агенты, разберемся, почему им требуется инженерия контекста. Главная причина в том, что языковые модели всегда демонстрируют лучшие результаты, если их контекст насыщен релевантной информацией и лишен шума (неактуальных данных). Кроме того, контекст агентов быстро разрастается при последовательных вызовах инструментов, например, при получении логов ошибок. Это приводит к набуханию контекста, когда в нем скапливается множество нерелевантных сведений. Необходимо устранять этот шум из контекста модели и гарантировать наличие всех необходимых данных.
Конкретные рекомендации по инженерии контекста
Агентная инженерия контекста опирается на принципы традиционной инженерии контекста. Поэтому здесь приведены несколько ключевых советов для улучшения контекста.
- Обучение на нескольких примерах
- Структурированные промпты
- Поэтапное рассуждение
Это три широко применяемых метода в инженерии контекста, которые часто повышают эффективность языковых моделей.
Обучение на нескольких примерах
Обучение на нескольких примерах — это популярный метод, при котором перед задачей агента добавляются образцы похожих операций. Это помогает модели лучше осмыслить задачу, что обычно приводит к росту производительности.
Ниже представлены два примера промптов. Первый — это промпт без примеров, где модельу напрямую задается вопрос. Для простой задачи модель, скорее всего, справится; однако эффект от нескольких примеров проявляется сильнее на сложных заданиях. Во втором промпте приведены образцы математических вычислений, обернутые в XML-теги. Это не только уточняет суть задачи для модели, но и обеспечивает единообразие формата ответа, поскольку модель склонна копировать структуру из предоставленных примеров.
# промпт без примеров = "Сколько будет 123+150?" # промпт с несколькими примерами = """ <example>"Сколько будет 10+20?" -> "30"</example><example>"Сколько будет 120+70?" -> "190"</example> Сколько будет 123+150? """ Структурированные промпты
Использование структурированных промптов также играет vitalную роль в инженерии контекста. В приведенных выше примерах кода видны XML-теги вроде <example> … </example>. Можно также применять форматирование Markdown для усиления структуры промптов. Я часто составляю общий план промпта, а затем передаю его языковой модели для доработки и правильной организации — это отличный способ создания качественных промптов.
Существуют специализированные инструменты, такие как оптимизатор промптов от Anthropic, но можно просто загрузить неструктурированный промпт в ChatGPT и попросить улучшить его. Результаты станут еще лучше, если описать ситуации, в которых текущий промпт дает сбои.
Например, если математический агент успешно справляется с сложением, вычитанием и делением, но испытывает трудности с умножением, укажите это в запросе к оптимизатору промпта.
Поэтапное рассуждение
Поэтапное рассуждение — еще один мощный подход в инженерии контекста. Модели предлагается последовательно обдумывать решение проблемы перед ее прямым разрешением. Для еще большей эффективности можно комбинировать все три метода, описанные в этом разделе, как показано в примере ниже:
# промпт с примерами + структурой + поэтапным рассуждением = """ <example>"Сколько будет 10+20?" -> "Чтобы ответить на запрос пользователя, нужно сложить два числа. Я начинаю с последних цифр: 0+0=0. Затем складываю единицы: 1+2=3. Ответ: 30" </example><example>"Сколько будет 120+70?" -> "Чтобы ответить на запрос пользователя, нужно сложить цифры справа налево. Начинаю с: 0+0=0. Затем: 2+7=9, и наконец: 1+0=1. Ответ: 190" </example> Сколько будет 123+150? """ Такой подход позволяет модели глубже понять примеры, что часто дополнительно повышает ее производительность.
Сокращение контекста
Когда агент выполнил несколько шагов, например, запросил ввод от пользователя или получил данные, контекст модели может начать заполняться. Чтобы избежать достижения лимита контекста и потери токенов за пределами этого лимита, следует сократить контекст.
Обобщение — эффективный способ сокращения контекста; однако оно иногда может исключить важные элементы. Первая часть контекста может быть бесполезной, в то время как вторая содержит ключевые параграфы. Это одна из сложностей агентной инженерии контекста.
Для сокращения контекста обычно задействуется другая языковая модель, которую я назову моделью-сокращалкой. Она принимает контекст и возвращает его укороченную версию. Простейший вариант — это модель, которая просто обобщает контекст. Но можно использовать следующие методы для улучшения сокращения:
- Определить, можно ли полностью исключить целые блоки контекста (конкретные документы, прошлые вызовы инструментов и т.д.)
- Настроенную на промпт модель-сокращалку, адаптированную под текущую задачу: она анализирует доступную информацию и возвращает только релевантные для решения данные
Определение целых блоков для исключения
Первым шагом при сокращении контекста должно быть выявление его частей, которые можно полностью удалить.
Например, если модель ранее извлекла документ для предыдущей задачи и получила результаты, этот документ больше не нужен и подлежит удалению из контекста. Аналогично, если модель получила данные через поиск по ключевым словам и сама обобщила их, исходный вывод поиска следует убрать.
Простое удаление таких блоков значительно сокращает контекст. Однако важно помнить, что исключение потенциально полезных для будущих задач данных может навредить работе агента.
Как отмечает Anthropic в своей статье об инженерии контекста, сначала следует оптимизировать по принципу полноты, гарантируя, что сокращалка никогда не удалит будущие релевантные данные. Достигнув почти идеальной полноты, можно перейти к точности, удаляя все больше неактуальных элементов для текущей задачи.
Настроенная на промпт модель-сокращалка
Также рекомендуется разработать модель-сокращалку, настроенную на промпт. Для этого сначала создайте тестовый набор контекстов и желаемых сокращенных версий с учетом конкретной задачи. Эти примеры лучше всего брать из реальных взаимодействий пользователей с агентом.
Далее оптимизируйте промпт (или даже дообучите) модель-сокращалку для задачи обобщения контекста: сохранения ключевых частей и удаления нерелевантных.
Инструменты
Одно из ключевых отличий агентов от разовых вызовов языковых моделей — их способность использовать инструменты. Агентам обычно предоставляется набор инструментов, расширяющих возможности по решению задач. Примерами таких инструментов служат:
- Поиск по ключевым словам в корпусе документов
- Получение данных о пользователе по его email
- Калькулятор для сложения чисел
Эти инструменты упрощают задачу агента. Он может провести поиск по ключевым словам для получения дополнительных (часто необходимых) данных или использовать калькулятор для точных вычислений, что надежнее, чем предсказание следующего токена.
Вот несколько приемов, которые стоит учитывать для правильного использования инструментов в контексте агента:
- Хорошо описанные инструменты (понятны ли они человеку?)
- Создание специализированных инструментов
- Избежание набухания
- Показ только релевантных инструментов
- Информативная обработка ошибок
Хорошо описанные инструменты для агентов
Первое и, вероятно, самое важное — обеспечить четкие описания инструментов. Определения инструментов должны включать аннотации типов для всех входных параметров и типа возвращаемого значения. Кроме того, нужны подходящее имя функции и описание в docstring. Ниже пример плохого и хорошего определения инструмента:
# плохое определение инструмента def calculator(a, b): return a+b # хорошее определение инструмента def add_numbers(a: float, b: float) -> float: """Функция для сложения двух чисел. Используйте ее всякий раз, когда нужно сложить два числа. Параметры: a: float b: float Возвращает: float """ return a+b Вторая функция гораздо проще для понимания агентом. Тщательное описание инструментов помогает агенту лучше определять, когда их применять, и когда лучше выбрать другой подход.
Критерием хорошего описания инструмента может служить:
Может ли человек, впервые видящий эти инструменты, понять их, просто взглянув на функции и их определения?
Специализированные инструменты
Также старайтесь делать инструменты максимально конкретными. Вагуальные определения затрудняют для модели понимание момента и способа их использования.
Например, вместо общего инструмента для извлечения данных из базы данных предоставьте специализированные инструменты для конкретных запросов.
Плохой инструмент:
- Извлечение информации из базы данных
- Входные данные
- Колонки для извлечения
- Индекс базы данных для поиска
Лучшие инструменты:
- Получение информации обо всех пользователях из базы данных (без входных параметров)
- Формирование отсортированного списка документов по дате для заданного ID клиента
- Сбор агрегированного списка всех пользователей и их действий за последние 24 часа
При необходимости можно добавлять более targeted инструменты. Это облегчает агенту включение релевантных данных в контекст.
Избежание набухания
Набухание контекста следует избегать любой ценой. Для этого с функциями применяются два основных подхода:
- Функции должны возвращать структурированные выводы и, optionally, только подмножество результатов
- Избегать нерелевантных инструментов
Для первого пункта вернемся к примеру поиска по ключевым словам. При поиске, например, в AWS Elastic Search, возвращается много данных, часто неструктурированных.
# плохой возврат функции def keyword_search(search_term: str) -> str: # выполнение поиска по ключевым словам # results: {"id": ..., "content": ..., "createdAt": ..., ...}, {...}, {...}] return str(results) # хороший возврат функции def _organize_keyword_output(results: list[dict], max_results: int) -> str: output_string = "" num_results = len(results) for i, res in enumerate(results[:max_results]): # максимум max_results output_string += f"Документ номер {i}/{num_results}. ID: {res["id"]}, содержание: {res["content"]}, создано: {res["createdAt"]}" return output_string def keyword_search(search_term: str, max_results: int) -> str: # выполнение поиска по ключевым словам # results: {"id": ..., "content": ..., "createdAt": ..., ...}, {...}, {...}] organized_results = _organize_keyword_output(results, max_results) return organized_resultsВ плохом примере мы просто преобразуем сырой список словарей в строку. Лучше использовать отдельную вспомогательную функцию для структурирования результатов в coherent строку.
Также обеспечьте возможность возврата только подмножества результатов, как с параметром max_results. Это особенно полезно для функций вроде поиска по ключевым словам, которые могут выдать сотни результатов и сразу заполнить контекст модели.
Второй пункт касается избежания нерелевантных инструментов. В ситуациях с большим количеством инструментов многие из них актуальны только на определенных этапах. Если инструмент не нужен агенту в данный момент, его следует исключить из контекста.
Информативная обработка ошибок
Информативная обработка ошибок критически важна при предоставлении инструментов агентам. Агента нужно информировать о причинах ошибок. Обычно сырые сообщения об ошибках из Python громоздки и трудно понимаемы.
Ниже хороший пример обработки ошибок в инструментах, где агенту объясняется суть проблемы и как ее исправить. Например, при ошибке лимита скорости мы указываем агенту уснуть перед повтором. Это упрощает задачу, избавляя от необходимости самостоятельного вывода.
def keyword_search(search_term: str) -> str: try: # поиск по ключевым словам results = ... return results except requests.exceptions.RateLimitError as e: return f"Ошибка лимита скорости: {e}. Перед повтором выполните time.sleep(10)." except requests.exceptions.ConnectionError as e: return f"Произошла ошибка соединения: {e}. Сеть может быть недоступна, сообщите пользователю с помощью функции inform_user." except requests.exceptions.HTTPError as e: return f"Произошла HTTP-ошибка: {e}. Функция завершилась с HTTP-ошибкой. Обычно это из-за проблем доступа. Убедитесь в валидности перед использованием." except Exception as e: return f"Произошла неожиданная ошибка: {e}"Такую обработку ошибок стоит реализовать для всех функций, учитывая:
- Сообщения об ошибках должны подробно описывать случившееся
- Если известны исправления (или возможные варианты) для конкретной ошибки, укажите модели, как действовать (например, при лимите скорости — выполнить time.sleep())
Перспективы агентной инженерии контекста
В этой статье рассмотрены три основных аспекта: конкретные рекомендации по инженерии контекста, сокращение контекста агентов и предоставление инструментов агентам. Это фундаментальные знания, необходимые для создания надежных ИИ-агентов. Есть и другие темы для изучения, такие как учет предварительно вычисленных данных или поиск информации во время инференса. О них расскажем в следующей публикации. Агентная инженерия контекста останется высоко актуальной областью, и умение управлять контекстом агента будет основой для будущих разработок ИИ-агентов.