Вступление
Одними из наиболее интересных подходов в создании AI-систем в 2024 году являются агенты (agents, agentic AI systems) и мультиагентные системы (multi-agent systems).
Оба они показывают большую эффективность при решении многих задач и позволяют решать более сложные задачи с помощью LLM
Эти подходы не только увеличивают эффективность, а в принципе позволяют по другому взглянуть на то как можно подходить к дизайну AI систем в бизнесе и помогают решать задачи с бОльшей степенью неопределенности. По сути, вместо того, чтобы давать четкие инструкции по решению задачи, мы объясняем AI системе каким образом нужно думать, чтобы решить её.
Если хотите разбираться глубже в том, как использовать LLM и AI-агентов в продуктивности, работе, бизнесе и повседневной жизни — подписывайтесь на наш Telegram-канал “Вкалывают роботы”.
Поговорим сегодня про Агентов.
Ключевая черта, отличающая агентов от других AI-систем — это автономность
То есть мы можем говорить что имеем дело с Агентом, если наша система обладает автономностью в решении задачи. Степень автономности может быть разная: от выбора действий в рамках заданных шагов и инструментов до самостоятельного определения плана и последовательности выполнения.
Решение реальной бизнес-задачи
Большинство гайдов по агентам ограничиваются теорией и супер-абстрактными примерами, игнорируя реальные проблемы, которые возникают в процессе разработки.
Сегодня мы решим задачу, приближенную к реальности и актуальную для многих бизнесов — сделаем агента-ассистента для сбора данных.
Представим, что у нас есть юридическая компания, помогающая клиентам защищать права потребителей. Когда клиент обращается с проблемой, прежде чем передать задачу юристу, наш ассистент должен собрать полную информацию о клиенте, компании и покупке.
Если бы мы просто автоматизировали этот процесс, то сделали бы стандартный опросник и набор скриптов для сбора данных из интернета. Но мы создадим агента, который будет автономно решать эту задачу, адаптируясь в процессе.
Требования к агенту
Наш агент должен уметь:
Задавать уточняющие вопросы клиенту.
Искать информацию в поисковике.
Для этого используются инструменты (tools), которые становятся своего рода «руками» LLM, позволяя взаимодействовать с внешним миром.
Приблизительно это работает так:
Мы объясняем LLM, что есть в её распоряжении и просим выполнить какую-то задачу.
USER: У тебя есть следующие Инструменты: search(query) — для поиска в интернете, human(question) — чтобы задать вопрос пользователю
Какой адрес большого театра?
LLM просит нас выполнить функцию с заданными параметрами (сама она этого сделать не может)
LLM: run search('большой театр адрес')
Мы на своей стороне выполняем функцию search и отдаем LLM результаты
USER: run search('большой театр адрес') results = {результаты поиска, например первая страница выдачи гугла}
LLM используя эти данные отвечает
LLM: Адрес большого театра: ул. Большая Дмитровка, 4/2, Москва
Т.е. мы можем имплементировать какое-то количество функций, объяснить LLM как и для чего их использовать, и она будет по своему усмотрению просить нас их выполнять.
Реализация агента
Для создания агента будем использоваться langgraph — это очень крутая библиотека, которая упрощает работу с агентами и инструментами. В качестве LLM будем использовать chatgpt-4o.
Основа приложения на langgraph — это, собственно граф: несколько нод, объединенных связями.
В нашем случае это будет 2 ноды:
Нода взаимодействия с LLM
Нода вызова инструментов
Также нам потребуется 2 инструмента:
Поиск в интернете – будем использовать Tavily
Взаимодействие с пользователем – human tool из langchain
Настройка модели и инструментов
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END, MessagesState
from langgraph.prebuilt import ToolNode
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.tools import TavilySearchResults
from langchain.agents import load_tools
# Tavily для поиска в интернете
search_tool = TavilySearchResults(max_results=10,
include_answer=True,
include_raw_content=False)
# Human tool для взаимодействия с пользователем
human_tool = load_tools(["human"])[0]
tools = [search_tool, human_tool]
# Настраиваем модель и биндим к ней инструменты
llm = ChatOpenAI(model="gpt-4o", max_tokens=None).bind_tools(tools)Теперь у нас определены инструменты и модель, которая знает про эти инструменты
Далее создадим ноду для взаимодействия с LLM и граф:
# функция, которая определяет нужно ли вызывать инструменты
# или результат уже получен
def should_continue(state: MessagesState):
messages = state["messages"]
last_message = messages[-1]
if last_message.tool_calls:
return "tools"
return END
# функция для ноды взаимодейтсвия с LLM
def gather_data(state: MessagesState):
messages = state["messages"]
messages.append(SystemMessage(content='''
Ты – ассистент юриста.
Клиент обратился с проблемой. Твоя задача - собрать данные о клиенте,
компании и покупке
Тебе нужно собрать следующие данные:
* о компании
** юридическое наименование (ООО, ИП и другие правовые формы)
** Юридический адрес
** ИНН, ОГРН(ИП)
** контактный email
* Данные клиента:
** ФИО
** Паспортные данные (номер, кем и когда выдан)
* Данные о покупке:
** дата
** номер (если есть)
** сумма
Ответь JSON в формате
{{"case_data": все данные о клиенте и компании текстовом виде}}.
Никакий других данных в ответе быть не должно,
только JSON, который можно распарсить
'''))
response = llm.invoke(messages)
# информация для отладки
print(json.dumps(response.tool_calls, indent=2,ensure_ascii=False))
print(json.dumps(response.content, indent=2,ensure_ascii=False))
return {"messages": [response]}
# встроенная в langgraph нода вызова инструментов
tool_node = ToolNode(tools)
workflow = StateGraph(MessagesState)
# задаём ноды
workflow.add_node("gather_data_node", gather_data)
workflow.add_node("tools", tool_node)
# задаём переходы между нодами
# входная нода - gather_data_node
workflow.set_entry_point("gather_data_node")
# после gather_data_node вызываем should_continue,
# чтобы определить что делать дальше
workflow.add_conditional_edges("gather_data_node",
should_continue,
["tools", END])
# после вызова инструментов всегда возвращаемся к ноде LLM,
# чтобы отдать ей результат вызова инструментов
workflow.add_edge("tools", "gather_data_node")
graph = workflow.compile()
# изначальное описание ситуации от клиента
prompt = """
Вот описание ситуации от клиента:
Я купил абонемент в спортзале Wordclass на лени��ском 109 на год.
Через месяц я захотел его вернуть и на ресепшене мне отказали.
"""
input_messages = [HumanMessage(prompt)]
output = graph.invoke({"messages": input_messages})В итоге у нас получился вот такой граф:

Как это работает
У графов в langgraph есть стейт, доступный при выполнении каждой ноды, куда можно записывать данные. Стейт можно задать произвольно, в нашем случае используется MessagesState. Этот стейт хранит историю всех сообщений между LLM и пользователем, аналогично тому, как это реализовано в веб-версии ChatGPT. При вызове LLM доступна вся история взаимодействий.
Изначально вызывается gather_data_node. После её выполнения LLM либо возвращает конечный результат, либо «просит» выполнить один из доступных инструментов. Решение о дальнейшем действии принимает функция should_continue: она либо направляет граф в ноду инструментов, либо завершает его выполнение.
После выполнения инструментов граф всегда возвращается к ноде взаимодействия с LLM (gather_data_node), чтобы передать результаты выполнения инструментов для дальнейшей обработки.
Изначальное описание проблемы клиента будет таким:
Я купил абонемент в спортзале Wordclass на Ленинском 109 на год. Через месяц я захотел его вернуть, и на ресепшене мне отказали.
Запускаем
Теперь начинается самое интересное, давайте запускать.
Запуск 1
Первый запуск выдал такое:

По умолчанию LLM просто “ленится” и просит человека предоставить абсолютно все данные 🙂
Давайте объясним ему, как правильно пользоваться инструментами. Немного поправим промпт:
Ты – ассистент юриста. Клиент обратился с проблемой. Твоя задача - собрать данные о клиенте, компании и покупке.
Тебе нужно собрать следующие данные:
* о компании
** юридическое наименование (ООО, ИП и другие правовые формы)
** Юридический адрес
** ИНН, ОГРН(ИП)
** контактный email
* Данные клиента:
** ФИО
** Паспортные данные (номер, кем и когда выдан)
* Данные о покупке:
** дата
** номер (если есть)
** сумма
Для поиска данных о компании используй инструмент TavilySearchResults.
С помощью HumanInputRun ты можешь уточнить у клиента его данные и данные о покупке
Ответь JSON в формате {{"case_data": все данные о клиенте и компании текстовом виде}}.
Никакий других данных в ответе быть не должно, только JSON, который можно распарсить
Запуск 2
[
{
"name": "tavily_search_results_json",
"args": {
"query": "Worldclass Ленинский 109 юридическая информация"
},
"id": "call_RIOMHjxV5qoQjTXVrdV8orNB",
"type": "tool_call"
},
{
"name": "human",
"args": {
"query": "Пожалуйста, предоставьте ваши ФИО, паспортные данные, а также информацию о покупке (дата, номер, сумма), если у вас есть эти данные."
},
"id": "call_DhUwVdO14d7dUFH1fyPp0YvG",
"type": "tool_call"
}
]В первой итерации наш агента решил сделать поисковый запрос «Worldclass Ленинский 109 юридическая информация» и попросил предоставить нас данные
LLM: Пожалуйста, предоставьте ваши ФИО, паспортные данные, а также информацию о покупке (дата, номер, сумма), если у вас есть эти данные.
USER: Иванов Иван Ивановч 4444 444444 (тут я намеренно сделал опечатку и дал ему не все данные)
[
{
"name": "tavily_search_results_json",
"args": {
"query": "Worldclass юридическая информация"
},
"id": "call_LujnH75FUxTEpOim9QZCoXEI",
"type": "tool_call"
}
]
""
[
{
"name": "human",
"args": {
"query": "Я нашел информацию, что клубы World Class управляются ООО «Спорт Форум». Пожалуйста, уточните, кем и когда был выдан ваш паспорт, а также данные о покупке: дата покупки, номер договора (если есть) и сумма."
},
"id": "call_lbKtbYuzCUnz4JMGDxaQsmNA",
"type": "tool_call"
}
]
Видимо он нашёл не всю информацию и решил во второй итерации сделать еще один поиск с другим запросом и дозапросить недостающие данные у нас
LLM: Я нашел информацию, что клубы World Class управляются ООО «Спорт Форум». Пожалуйста, уточните, кем и когда был выдан ваш паспорт, а также данные о покупке: дата покупки, номер договора (если есть) и сумма.
USER: абонемент куплен 01.10.24 за 100 000 руб.
После этого наш агент решил, что собрал всю информацию и вернул конечный результат:
{
"case_data": "Компания:
- Юридическое наименование: ООО «Спорт Форум»
- Юридический адрес: 117420, Москва, улица Наметкина д6 к1
- ИНН, ОГРН: информация не найдена
- Контактный email: информация не найдена
Клиент:
- ФИО: Иванов Иван Иванович
- Паспортные данные: номер 4444 444444, кем и когда выдан: информация не предоставлена
Покупка:
- Дата: 01.10.2024
- Номер: не указан
- Сумма: 100 000 руб."
}Видим что он нашёл только часть данных по компании.
Мне показалось это связано с тем, что поисковый запрос, который он использует, слишком общий. Кроме того, он удовлетворяется неполной информацией и закачивает работу раньше чем стоило бы. Давайте это исправим:
Ты – ассистент юриста.
Клиент обратился с проблемой. Твоя задача - собрать данные о клиенте, компании и покупке
Тебе нужно собрать следующие данные:
* о компании
** юридическое наименование (ООО, ИП и другие правовые формы)
** Юридический адрес
** ИНН, ОГРН(ИП)
** контактный email
* Данные клиента:
** ФИО
** Паспортные данные (номер, кем и когда выдан)
* Данные о покупке:
** дата
** номер (если есть)
** сумма
Для поиска данных о компании используй инструмент TavilySearchResults. При использовании поиска ищи каждый параметр отдельным поисковым запросом.
С помощью HumanInputRun ты можешь уточнить у клиента его данные и данные о покупке.
Продолжай задавать вопросы и пользоваться поиском до тех пор, пока не соберешь все данные
Запуск 3
[
{
"name": "tavily_search_results_json",
"args": {
"query": "Wordclass Ленинский 109 юридическое наименование"
},
"id": "call_j2QfMPhLUrr3u3k6CTCAnzFo",
"type": "tool_call"
},
{
"name": "tavily_search_results_json",
"args": {
"query": "Wordclass Ленинский 109 юридический адрес"
},
"id": "call_5GgJB1bUg3jziGvM5JthfJGq",
"type": "tool_call"
},
{
"name": "tavily_search_results_json",
"args": {
"query": "Wordclass Ленинский 109 ИНН"
},
"id": "call_6IMxybQovBXJXsZGrKjk9Dhs",
"type": "tool_call"
},
{
"name": "tavily_search_results_json",
"args": {
"query": "Wordclass Ленинский 109 ОГРН"
},
"id": "call_erXpeIrcxi185cOnLhEITxwi",
"type": "tool_call"
},
{
"name": "tavily_search_results_json",
"args": {
"query": "Wordclass Ленинский 109 контактный email"
},
"id": "call_k10Q1YreIWa8ElPllnpOC7kk",
"type": "tool_call"
}
]В первую итерацию он сделал несколько запросов в поиск - один запрос на каждый параметр, с человеком пока решил не общаться.
Смотрим дальше:
{
"name": "human",
"args": {
"query": "Пожалуйста, предоставьте ваши данные:\n\n1. ФИО\n2. Паспортные данные (номер, кем и когда выдан)\n3. Данные о покупке абонемента:\n - Дата покупки\n - Номер (если есть)\n - Сумма покупки"
},
"id": "call_kw7nB2z3MZh3ZMCBZXljvHWF",
"type": "tool_call"
}
]
На второй цикл уже нет поисковых запросов, но есть вопросы к пользователю
LLM: Пожалуйста, предоставьте ваши данные:
1. ФИО
2. Паспортные данные (номер, кем и когда выдан)
3. Данные о покупке абонемента:
- Дата покупки
- Номер (если есть)
- Сумма покупки
USER: Иванов Иван Ивановч 4444 444444
Намеренно отвечаем ему только частично, смотрим что происходит
[
{
"name": "human",
"args": {
"query": "Пожалуйста, укажите дату покупки абонемента, его номер (если есть) и сумму покупки."
},
"id": "call_XKuza4NR2K2b0K3ODa8wm4de",
"type": "tool_call"
}
]
Он понял, что данных не хватает �� задал еще вопрос
LLM: Пожалуйста, укажите дату покупки абонемента, его номер (если есть) и сумму покупки.
USER: абонемент куплен 01.10.24 за 100 000 руб.
После этого, наш агент решил, что собрал все данные и выдал конечный результат:
{
"case_data":{
"company":{
"юридическое наименование":"ООО Спорт Форум",
"юридический адрес":"Москва, Ленинский проспект, 109",
"ИНН":"7710309880",
"ОГРН":"1027700488493",
"контактный email":"info@worldclass.ru"
},
"client":{
"ФИО":"Иванов Иван Иванович",
"паспортные данные":{
"номер":"4444 444444"
}
},
"purchase":{
"дата":"01.10.24",
"сумма":"100000 руб"
}
}
}
Теперь результат уже достаточно хороший, чтобы отдавать его юристу, который не будет тратить время на поиск информации.
Финульную версию скрипта можно взять тут.
В процессе создания подобных AI-систем основное время уходит на проектирование графа, стейта и промпт-инжиниринг. Это некоторая смесь программирования и творчества, которое требует накопления опыта и множества проб и ошибок, чтобы развить интуицию и научиться эффективно проектировать такие системы.
Что дальше?
Конечно, это пока только proof-of-concept.
Но он уже демонстрирует, что LLM может автономно решать несложные задачи. Этот пример можно доработать: добавить обработку ошибок, интегрировать дополнительные инструменты, такие как проверка компании в ЕГРЮЛ, парсинг сайтов для получения информации и поиск релевантных статей закона. В итоге получится полноценный инструмент, способный значительно повысить эффективность бизнеса.
Кроме этого, я в этой статье я не затронул важный аспект – реалзиация долгосрочной памяти и RAG, про это на хабре есть хорошая статья.
Следующим шагом может стать создание ещё одного агента — юриста, который сможет автоматически составлять жалобы. Таким образом, мы получим мультиагентную систему, о которой я расскажу в следующей статье.
Перспективы
Текущий уровень развития LLM уже позволяет в некоторых задачах перейти от традиционной автоматизации к созданию автономных агентов, которые работают гораздо эффективнее. Эта эффективность будет только увеличиваться.
Буквально несколько дней назад OpenAI анонсировала новую модель o3 которая, уверен, ещё лучше проявит себя в подходах с использованием агентов. Изменения происходят очень быстро, поэтому важно уже сейчас пересмотреть своё отношение к LLM и воспринимать их не только как инструменты для выполнения чётких инструкций.
---
Если хотите разбираться глубже в том, как использовать LLM и AI-агентов в продуктивности, работе, бизнесе и повседневной жизни — подписывайтесь на наш Telegram-канал “Вкалывают роботы”.
