Вступление
Одними из наиболее интересных подходов в создании 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-канал “Вкалывают роботы”.
