Цикл в коде
Этот блок переводит телефонный разговор из прошлого блока в код. Пройдём цикл по строкам, потом посмотрим, что бывает, когда модель просит две вещи сразу и когда инструмент падает.
Мы всё ещё делаем агента поддержки Northwind, прямо на Python SDK от Anthropic.
Телефонный разговор как функция
Вы набираете номер, зачитываете весь разговор и слушаете. Если коллега о чём-то просит, вы делаете это и перезваниваете. Если он дал окончательный ответ, вы закончили.
def run_agent(client, session, text):
messages = [{"role": "user", "content": text}]
for _ in range(MAX_STEPS):
reply = client.messages.create(model=MODEL, max_tokens=1024,
system=SYSTEM, tools=TOOLS,
messages=messages)
messages.append({"role": "assistant", "content": reply.content})
if reply.stop_reason == "end_turn":
return text_of(reply)
if reply.stop_reason != "tool_use":
return HANDOFF_MESSAGE
results = [execute(block, session) for block in reply.content
if block.type == "tool_use"]
messages.append({"role": "user", "content": results})
return HANDOFF_MESSAGEВот что делает каждая часть.
messages — это ваш блокнот. В начале там сообщение покупателя, и дальше он только растёт.
client.messages.create — это звонок. Вы отправляете инструкции, список инструментов и весь блокнот.
Первый append записывает слова коллеги. Ответ попадает в блокнот целиком и раньше, чем вы что-то по нему сделаете.
end_turn значит, что коллега закончил, и вы возвращаете текст. У этого агента нет серверных инструментов и stop sequences, поэтому любая другая причина, например max_tokens или refusal, значит, что полного ответа нет, и покупатель уходит к человеку.
results — это выполнение просьб. Запускается каждая, а второй append кладёт все ответы в одно сообщение.
range(MAX_STEPS) — это предел разговора. Про него следующий блок.
Две просьбы сразу
Коллега может попросить о двух вещах за один раз. Поэтому он нумерует просьбы, а вы, отвечая, называете номер. Иначе ответы перепутаются.
Покупатель спрашивает про заказы 4417 и 5120. В одном ответе модели два поиска, поэтому код всегда идёт по списку, даже если обычно в нём один элемент.
В API Anthropic действуют три правила:
- На каждый
tool_useнуженtool_result, и каждыйtool_resultссылается на свой вызов черезtool_use_id. - Результаты идут в следующем же сообщении. Между ответом модели и результатами ничего быть не может.
- Решили пропустить вызов, всё равно ответьте на него с флагом
is_error.
Порядок выполнения выбираете вы. Независимые чтения можно запускать вместе, возвраты лучше по одному.
В других API та же идея под другими именами:
| Anthropic Messages | OpenAI Responses | OpenAI Chat Completions |
|---|---|---|---|
Модель просит | блок | элемент | |
Номер просьбы | | | |
Вы отвечаете | | элемент | сообщение с ролью |
Когда инструмент падает
Коллега просит найти заказ 4471, а такого заказа нет. Вы так и говорите, и он спрашивает у покупателя верный номер. Бросать трубку вы бы не стали.
Неизвестный заказ — это информация, поэтому она возвращается модели как результат инструмента:
def execute(block, session):
result = {"type": "tool_result", "tool_use_id": block.id}
try:
result["content"] = dispatch(block.name, block.input, session)
except Exception as error:
result["content"] = f"Failed: {error}"
result["is_error"] = True
return resultПишите текст достаточно конкретно, чтобы модель могла выбрать, что делать дальше.
Что не решает модель
Коллега не сообщает вам, кто покупатель. Вы и так знаете это из сессии вошедшего пользователя.
Личность покупателя не аргумент инструмента. customer_id нет в схеме инструмента, dispatch берёт его из session.
if name == "lookup_order":
return orders.lookup(order_id=args["order_id"],
customer_id=session.customer_id)Модели умеют выдумывать аргументы: справочник OpenAI предупреждает о параметрах, которых нет в схеме. Strict mode приводит аргументы в соответствие со схемой. Но верны ли значения, он не говорит, поэтому ваш код их проверяет, а dispatch передаёт дальше только ожидаемые поля.
Вывод
- Записывайте ответ в историю целиком, затем отвечайте на каждый вызов по его id, всё одним сообщением.
- Возвращайте сбои как результаты, чтобы модель могла на них отреагировать.
- Личность покупателя берите из сессии.