1. Наша первая агентная система
Постановка задачи
Давайте начнем с проблемы, которую мы решаем. Каждый день ваша команда поддержки клиентов обрабатывает десятки или сотни писем с просьбами вернуть деньги за разбитую кружку, отменить неотправленный заказ или изменить адрес доставки. Для каждого сообщения человек-агент должен прочитать свободный текст, найти заказ в вашей backend-системе, вызвать соответствующий API, а затем напечатать письмо с подтверждением. Этот повторяющийся двухминутный процесс созрел для автоматизации — но только если мы правильно выберем область применения.
Когда мы понимаем, что люди печатают клавиши и нажимают кнопки, часто следуя правилам и инструкциям, мы видим, что многие из этих же паттернов могут быть выполнены хорошо спроектированными системами, которые полагаются на foundation модели.
Простая агентная система для отмены заказов
Мы хотим, чтобы наш агент принимал сырое сообщение клиента плюс запись заказа, решал, какой инструмент вызвать (issue_refund, cancel_order или update_address_for_order), вызывал этот инструмент с правильными параметрами, а затем отправлял краткое сообщение с подтверждением. Этот двухшаговый рабочий процесс достаточно узок, чтобы построить его быстро, достаточно ценен, чтобы освободить человеческое время, и достаточно богат, чтобы демонстрировать интеллектуальное поведение.
Мы можем построить работающего агента для этого случая использования всего в нескольких строках кода:
from langchain.tools import tool
from langchain_openai.chat_models import ChatOpenAI
from langchain.schema import SystemMessage, HumanMessage, AIMessage
from langchain_core.messages.tool import ToolMessage
from langgraph.graph import StateGraph
# -- 1) Определяем наш единственный бизнес-инструмент
@tool
def cancel_order(order_id: str) -> str:
"""Отменить заказ, который еще не был отправлен."""
# (Здесь вы бы вызвали ваш реальный backend API)
return f"Заказ {order_id} был отменен."
# -- 2) "Мозг" агента: вызываем LLM, запускаем инструмент, затем снова вызываем LLM
def call_model(state):
msgs = state["messages"]
order = state.get("order", {"order_id": "UNKNOWN"})
# Системный промпт говорит модели точно, что делать
prompt = (
f'''Вы агент поддержки электронной коммерции.
ID ЗАКАЗА: {order['order_id']}
Если клиент просит отменить, вызовите cancel_order(order_id)
и затем отправьте простое подтверждение.
В противном случае просто отвечайте нормально.'''
)
full = [SystemMessage(prompt)] + msgs
# 1-й проход LLM: решает, вызывать ли наш инструмент
first = ChatOpenAI(model="gpt-5", temperature=0)(full)
out = [first]
if getattr(first, "tool_calls", None):
# запускаем инструмент cancel_order
tc = first.tool_calls[0]
result = cancel_order(**tc["args"])
out.append(ToolMessage(content=result, tool_call_id=tc["id"]))
# 2-й проход LLM: генерируем финальный текст подтверждения
second = ChatOpenAI(model="gpt-5", temperature=0)(full + out)
out.append(second)
return {"messages": out}
# -- 3) Собираем все вместе в StateGraph
def construct_graph():
g = StateGraph({"order": None, "messages": []})
g.add_node("assistant", call_model)
g.set_entry_point("assistant")
return g.compile()
graph = construct_graph()
if __name__ == "__main__":
example_order = {"order_id": "A12345"}
convo = [HumanMessage(content="Пожалуйста, отмените мой заказ A12345.")]
result = graph.invoke({"order": example_order, "messages": convo})
for msg in result["messages"]:
print(f"{msg.type}: {msg.content}")
Отлично — теперь у вас есть работающий агент для "отмены заказа". Прежде чем мы расширим нашего агента, давайте подумаем, почему мы начали с такого простого среза.
Важность правильного выбора области применения
Определение области применения всегда является балансированием. Если вы слишком сужаете свою задачу — скажем, только отмены — вы упускаете другие запросы с большим объемом, такие как возвраты или изменения адресов, ограничивая реальное влияние. Но если вы расширяете ее слишком далеко — "автоматизировать каждый запрос поддержки" — вы утонете в граничных случаях, таких как споры по счетам, рекомендации продуктов и техническая поддержка. И если вы оставляете ее расплывчатой — "улучшить удовлетворенность клиентов" — вы никогда не узнаете, когда вы преуспели.
Вместо этого, фокусируясь на четком, ограниченном рабочем процессе — отмена заказов — мы обеспечиваем конкретные входы (сообщение клиента + запись заказа), структурированные выходы (вызовы инструментов + подтверждения) и тесную обратную связь.
Пример работы агента
Например, представьте письмо, которое говорит: "Пожалуйста, отмените мой заказ #B73973, потому что я нашел более дешевый вариант в другом месте." Человек-агент посмотрел бы заказ, проверил, что он еще не отправлен, нажал "Отменить" и ответил с подтверждением. Перевод этого в код означает вызов cancel_order(order_id="B73973") и отправку простого сообщения с подтверждением обратно клиенту.
Оценка производительности агента
Теперь, когда у нас есть работающий агент для "отмены заказа", следующий вопрос: действительно ли он работает? В продакшене мы не просто хотим, чтобы наш агент работал — мы хотим знать, насколько хорошо он работает, что он делает правильно и где он терпит неудачу. Для нашего агента отмены заказа нас интересуют такие вопросы, как:
- Вызвал ли он правильный инструмент (
cancel_order)? - Передал ли он правильные параметры (правильный ID заказа)?
- Отправил ли он четкое, правильное сообщение с подтверждением клиенту?
В нашем репозитории с открытым исходным кодом вы найдете полный скрипт оценки для автоматизации этого процесса:
- Набор данных для оценки
- Скрипт пакетной оценки
Вот минимальная, упрощенная версия этой логики для того, как вы можете протестировать своего агента напрямую:
# Минимальная проверка оценки
example_order = {"order_id": "B73973"}
convo = [HumanMessage(content='''Пожалуйста, отмените заказ #B73973.
Я нашел более дешевый вариант в другом месте.''')]
result = graph.invoke({"order": example_order, "messages": convo})
assert any("cancel_order" in str(m.content) for m in result["messages"],
"Инструмент отмены заказа не был вызван")
assert any("cancelled" in m.content.lower() for m in result["messages"],
"Сообщение с подтверждением отсутствует")
print("✅ Агент прошел минимальную оценку.")
Этот фрагмент гарантирует, что инструмент был вызван и подтверждение было отправлено. Конечно, реальная оценка идет глубже: вы можете измерить точность инструмента, точность параметров и общие показатели успешности задач на сотнях примеров, чтобы поймать граничные случаи перед развертыванием. Мы углубимся в стратегии и фреймворки оценки в главе 9, но пока помните: непротестированный агент — это недоверенный агент.
Поскольку оба шага автоматизированы с использованием декораторов @tool, написание тестов против реальных тикетов становится тривиальным — и вы мгновенно получаете измеримые метрики, такие как полнота инструмента, точность параметров и качество подтверждения.