Агенты супер багованы. В своих проектах в компании мы заметили, что Langchain стал уж слишком баговым. В мультиагентных системах агенты зачастую циклятся, так как не понимают, когда они выполнили финальное действие, не вызывают друг друга когда надо, или же просто возвращают данные в битом формате JSON. Короче говоря, создать агентную систему стало не так то просто, и мы даже стали задумываться об упрощении систем, избавляясь от кучи агентов. И вот неделю назад OpenAI обновили SDK для создания агентов, а еще выкатили доступ к новым тулзам по API. Ну и я пошел тестить.

Я Арсений, Data Scientist в Raft и сегодня я расскажу вам про OpenAI либу для создания агентов от OpenAI.
Мультиагенты
Мультиагентые системы супер важны, поскольку это позволяет не размазывать контекст у моделей, на основе которых они сделаны, делая ее работу более стабильной. Нельзя зафигачить супер большой системный промпт в одного агента и ждать что он отработает во всех кейсах идеально. Важно делать задачи для агентов максимально прямолинейными и атомарными, для лучшей производительности, а общий результат должен зависеть от взаимодействия агентов.
Привычный пайплайн создания агентов через Langchain уже устарел, разработчики библиотеки сами предлагают мигрировать на LangGraph. Фиксить ошибки агентов написанных на LangChain с помощью промптинга можно, но это отнимает много времени на дописывание инструкций, которые даже не относятся выполнению самой задачи, например, объяснять агенту, после какой конкретно функции он получил окончательный ответ, и как ответ должен выглядеть, чтобы его можно было вернуть пользователю.
Помимо этого, логирование и дебаг агентов — не супер удобная штука. Можно вручную настроить логирование в Grafana и записывать туда вывод агентов, но, исходя из моего опыта, это не очень удобно для чтения и требует дополнительного настраивания Grafana.
OpenAI Agents SDK
Последние две недели OpenAI выкатывает изменения в своем API, чтобы разработчики по всему миру внедряли это в свои приложения. Теперь в API доступны поиск по файлам, веб-поиск, использование компьютера, а так же новые аудиомодели. Помимо этого они обновили собственную библиотеку для агентов, где можно использовать все эти функции внутри агента, а так же создавать свои кастомные. Очевидным преимуществом является совместимость со своими же моделями, поддержка JSON формата вывода, что позволяет не парится над форматом вывода и легко связывать агентов между собой, встроенное логирование и другие фичи, по типу указания конкретных тулзов, после которых можно завершать действие и выдавать ответ.
Документация у них не супер полная, но чтобы стартануть пойдет. Как раз используя ее давайте сделаем какую-то простую систему, где заюзаем две важные тулзы -- поиск по файлу и веб-поиск. Итак наша цель:
Агент ресёрчер (типа deep search в GPT).
Что он делает: Анализирует то, что ввёл пользователь.
Задаёт уточняющие вопросы при необходимости.
Анализирует файлы и интернет на предмет данных по материалу.
Выдаёт полный отчёт со ссылками на материалы.
В качестве файла я выбрал данные одного крупнейшего Агро производителя удобрений и семян. В файле всего 340 страниц и его вес примерно 50 МБ. Задавать вопросы будем соответственно про удобрения.
Итак, пойдем по порядку, и начнем с первого пункта
Шаг 1. Анализ вопроса пользователя
Это будет простой агент без тулзов, который будет общаться с пользователем и уточнять вопросы.
from agents import Agent, Runner
query_analyzer_agent = Agent(
name="QueryAnalyzer",
instructions="""
Ты агент, который анализирует запросы пользователей. Тебе задают вопросы ожидая,
в дальнейшем ответа с использованием файлов и интернета.
Твоя задача - понять запрос, определить его тематику и структуру.
Выдели ключевые аспекты, названия, аттрибуты, определи, нужен ли уточняющий вопрос.
Не злоупотребляй уточняющими вопросами. Если ты смог достать ключевую информацию,
не спрашивай у пользователя ещё раз.
"""
)
runner = Runner()
analysis_result = await runner.run(
query_analyzer_agent,
f"Проанализируй следующий запрос пользователя и определи, требуются ли уточнения: '{query}'"
"Если уточнения требуются, сформулируй конкретный вопрос. "
"Если уточнения не требуются, просто напиши 'Уточнения не требуются'."
)
Runner запускает рабочий процесс агента. Агент будет выполняться в цикле до тех пор, пока не будет сгенерирован окончательный результат.
Цикл выполняется следующим образом:
Агент вызывается с определенным заданием. В примере выше в задание подставляется пользовательский запрос:
"Проанализируй следующий запрос пользователя и определи, требуются ли уточнения: '{query}'
Если уточнения требуются, сформулируй конкретный вопрос.
Если уточнения не требуются, просто напиши 'Уточнения не требуются'.Если есть конечный результат (т.е. агент выдает что-то типа
agent.output_type
), цикл завершается.Если есть handoff (передача данных в другого агента), снова запускается цикл, с новым агентом.
В противном случае вызываются инструменты (если таковые имеются) и повторно запускается цикл.
При создании агента, можно также выбирать модель, и то через какое API будет реализовываться обращение к модели.
Рекомендуется использовать новую версию Responses API, например так
model=OpenAIResponsesModel(model="gpt-4o", openai_client=AsyncOpenAI())
Но и через OpenAIChatCompletionsModel, который вызывает OpenAI APIs Chat Completions API
model= OpenAIChatCompletionsModel(model="gpt-4o", openai_client=AsyncOpenAI())
Используется по дефолту новая версия Responses API и gpt-4o-2024-08-06.
Шаг 2. FileSearch
Будем использовать встроенную в SDK FileSearchTool()
(подробнее тут). Для того чтобы искать по файлам нужно сначала создать хранилище файлов и загрузить их туда. Это можно сделать либо через сайт, либо используя API.
На сайте:
Создаем векторное хранилище
Добавляем нужные файлы
Для использования нужно сохранить
vector_id
, чтобы затем передать его вWebSearchTool()
API:
from openai import OpenAI
client = OpenAI()
# Создаем файл
local_file_path = "path"
with open(local_file_path, "rb") as file_content:
file_response = client.files.create(
file=file_content,
purpose="file-search"
)
# Создаем хранилище
vector_store = client.vector_stores.create(
name="knowledge_base"
)
# Добавляем файл в хранилище
client.vector_stores.files.create(
vector_store_id=vector_store.id,
file_id=file_id
)
Теперь можем создать агента поиска по файлу. Для этого используем vector_store.id
и FileSearchTool()
Можно задать количество файлов, которое использовать как подходящие для поиска ответа. В моем хранилище только один файл, поэтому я использую только его один.
from agents import Agent, FileSearchTool, Runner
file_search_tool = FileSearchTool(
vector_store_ids=[vector_store.id],
max_num_results=1
)
file_agent = Agent(
name="FileSearcher",
instructions="""
Ты агент, специализирующийся на поиске информации в загруженных файлах.
Используй имеющиеся у тебя инструменты для поиска информации в загруженных файлах.
Предоставляй полную и точную информацию из файлов, цитируя источник.
Это самый важный этап анализа, так как информация из файла имеет приоритет.
Если информация в файле не найдена, четко укажи это.
""",
tools=[file_search_tool]
)
runner = Runner()
file_result = await runner.run(
file_agent,
f"""Найди информацию по запросу: '{query}' в загруженном файле.
Очень важно найти максимально релевантную информацию."""
)
Цены и ограничения:
Цена использования $2.50 за 1000 запросов и хранилище стоит $0.10/GB/day, но первый Гб бесплатно.
Проекты не должны превышать 100GB для всех файлов.
Максимум хранилища -- 10k файлов.
Размер одного файла не более 512MB (примерно 5M токенов)
Шаг 3. WebSearch
Теперь когда у нас есть информация из файла, мы можем дополнить ее поиском в интернете. Важно, мы считаем информацию из файла более приоритетной, поскольку файл имеет более узкую спецификацию.
Итак сначала создаем тулзу для поиска WebSearchTool()
. Указываем размер контекста поиска, что напрямую влияет на итоговую цену, а так же опционально можем настроить локацию поиска
"user_location": {"type": "approximate", "country": "GB", "city": "London", "region": "London"}
Затем создаем самого агента и передаем ему инструкции и тулзу. После чего уже его можем запускать.
from agents import Agent, WebSearchTool, Runner
web_search_tool = WebSearchTool(
search_context_size="medium"
)
web_agent = Agent(
name="WebSearcher",
instructions="""
Ты агент, специализирующийся на поиске информации в интернете.
Используй инструменты поиска в интернете для нахождения актуальной информации,
которая дополняет или расширяет информацию, найденную в файле.
Предоставляй полную и точную информацию из результатов поиска, указывая источники.
""",
tools=[web_search_tool]
)
web_result = await runner.run(
web_agent,
f"""Найди актуальную информацию по запросу: '{query}' в интернете.
В файле была найдена следующая информация: '{file_result.final_output}'
Дополни или расширь эту информацию своими находками."""
)
Цены и ограничения:
Плата за токены: Стоимость зависит от количества токенов, затраченных на обработку запроса и генерацию ответа. Начинается от 25$ за 1000 обращений для GPT-4o-mini-search. Точные тарифы указаны на странице цен.
Зависимость от размера контекста: Выбор search_context_size влияет на стоимость — больший контекст увеличивает расходы. Подробности доступны в разделе цен на сайте OpenAI.
Шаг 4. Итоговый Ответ
И в конце пайплайна создадим агента, который будет форматировать все данные в удобный для юзера формат. Это обычный агент с простым промптом.
report_agent = Agent(
name="ReportGenerator",
instructions="Ты агент, специализирующийся на формировании итоговых отчетов."
"Твоя задача - обобщать результаты поиска из файлов и интернета."
"Структурируй информацию логически, выделяй ключевые моменты."
"Избегай крупных Заголовков."
"Приоритезируй информацию из файла, затем дополняй её данными из веб-поиска."
"Обязательно указывай источники информации (ссылки на файлы и веб-страницы)."
)
file_info = file_result.final_output
report_prompt = f"""
Сформируй полный и структурированный отчет на основе следующих данных:
ЗАПРОС ПОЛЬЗОВАТЕЛЯ: {query}
ИНФОРМАЦИЯ ИЗ ФАЙЛА (ПРИОРИТЕТНАЯ):
{file_info}
ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ ИЗ ИНТЕРНЕТА:
{web_result.final_output}
Обязательно укажи все источники информации. Структурируй отчет логически.
Информация из файла должна быть представлена в первую очередь, а затем дополнена данными из интернета.
"""
report_result = await runner.run(report_agent, report_prompt)
Логирование
Отлично, рабочий пайплайн готов!
Запустить его можно streamlit run app.py
Однако создать агентов мало, важно уметь дебажить и логировать их работу. В OpenAI все схвачено, они берут это на себя.
Итак давайте запустим с тестовым запросом 'Технические характеристики удобрения Вайбранс'
и перед просмотром ответа пройдем все шаги подряд через логи.
На влкадке Logs можно посмотреть все обращения агентов по API. Реализовано куча фильтров:
Тип API: ChatCompletion или Response.
Название Модели.
Дата обращения.
Метаданные.
Вызываемые в запросах тулзы.
Логи нашего запроса
Видим пошагово все вызовы агентов. Можно ткнуть на каждый вызов и посмотреть детали.

Например, запрос на FileSearch
. Видим количество потраченных токенов, а так же ключевые слова по которым шел поиск в файле: 'технические характеристики удобрения Вайбранс', 'удобрение Вайбранс спецификации', 'Вайбранс характеристики'
. Это важно для понимания работы и отладки.
Tracing
Помимо общего логирования можно настроить трассировку на конкретные процессы. Это позволяет отслеживать взаимодействие агентов и корректность работы всего пайплайна. Сделать это можно просто следующим образом:
from agents import trace
# указываем название Workflow -- Поисковый запрос
with trace("Поисковой запрос"):
# весь код вызова агентов ниже
Теперь каждый поисковой запрос можно будет так же отследить на вкладке Traces с указанием Workflow.

Проваливаясь внутрь можно посмотреть более детально работу каждого из агентов, время работы, потраченные токены. Из трассировки видно, что больше всего времени занимает WebSearch
, порядка 15 секунд, поскольку мы поставили "medium"
размер контекста поиска.
Поиск по файлу и другие агенты отрабатывают очень быстро и четко, несколько миллисекунд.
Итоговый результат
На выходе мы получили полный ресерч с данными из файла и интернета за меньше чем 20 секунд.
Результат модели:
Информация о технических характеристиках удобрения "Вайбранс" организована по следующим категориям:
Технические характеристики из файла:
Активные вещества: седаксан 50 г/л, мефеноксам 37,5 г/л, флудиоксонил 25 г/л.
Формулировка: концентрат суспензии.
Фасовка: канистра 5 л / упаковка 4 × 5 л.
Срок годности: 3 года со дня изготовления.
Класс опасности: 3-й класс.
Дополнительная информация из интернета:
Вайбранс Топ Используется для картофеля.
Действующие вещества: тиаметоксам 262,5 г/л, седаксан 25 г/л, флудиоксонил 25 г/л.
Форма: концентрат суспензии (КС).
Упаковка: канистра 5 л / упаковка 4×5 л.
Класс опасности: 3-й класс.
Норма расхода: 0,4–0,7 л/т клубней.
Спектр действия: колорадский жук, ризоктониоз.
Источник: syngenta.ru
Вайбранс Трио Для озимой и яровой пшеницы, ячменя.
Действующие вещества: седаксан 25 г/л, флудиоксонил 25 г/л, тебуконазол 10 г/л.
Форма: концентрат суспензии (КС).
Спектр действия: корневая гниль, пыльная головня.
Норма расхода: 1,5–2,0 л/т семян.
Источник: pr-agro.ru
"Вайбранс" — это линейка протравителей, предназначенных для защиты семян различных культур от широкого спектра болезней и вредителей. Каждый из препаратов имеет свои уникальные характеристики и целевое применение.
Вывод
По итогу получился очень кайфовый инструмент, с возможностью внедрять свои тулзы, строить агентов и их оркестрировать. Крутым плюсом я считаю возможность настройки через графический интерфейс и встроенные системы логирования.
Конечно, точность ответа зависит от промпта (чем понятнее и детальнее написали, тем лучше) и от того, как данные расположены в файле. Например, были запросы с специфическими названиями, или запросы, информация для которых находилась на разных страницах. Тогда по файлу не всегда удавалось найти подходящую информацию по запросу, хотя информация в файле была. Но тут уже на помощь приходил веб-поиск, котрый позволял агенту выкрутиться.
В общем надеюсь применить OpenAI Agents SDK уже в реальном проекте и рассказать вам о результатах! До встречи!
P.S.: итоговый код я обернул в Streamlit и получилось небольшое веб-приложение. Код тут.