Привет, Хабр!
В последнее время AI агенты стали главным трендом. Многие используют готовые шаблоны, такие как create_react_agent из langchain, но не понимают, как они работают под капотом. При этом агенты становятся все сложнее, и придет время, когда нужно будет писать свою реализацию.
В этой статье мы разберем:
Устройство ReAct агента
Устройство Reflection агента
Примеры системных prompt запросов
Кейсы использования и особенности
Что такое AI агент
Прежде чем переходить к коду, нужно понять, что можно считать агентом. Существует множество определений, например:
AI агент - система, способная принимать автономные решения, взаимодействовать с окружающей средой и другими агентами для выполнения конкретных задач
AI агент - программное обеспечение, которое автономно собирает данные и выполняет задачи с использованием этих данных.
Все они сходятся в одном: агентная система должна уметь действовать автономно и сама выбирать следующий шаг. Конечно, это все еще расплывчатое определение. У меня бывали кейсы, когда агент требовал подтверждения своих действий у человека на многих шагах, но при этом последовательность его действий была недетерминированной.
Составные части AI агентов:
-
System Prompt
Задает роль
Описывает поведение
Определяет доступные инструменты
-
Tools (Инструменты)
Функции, доступные агенту для решения задач. Например, поиск в интернете или базе
-
Система хранения контекста
Краткосрочная память
Долгосрочная память
Если вы используете LLM для выполнения линейной комбинации действий или для обычной вопросно-ответной формы, то ваша система, скорее всего, является LLM - конвейером, а не агентом.
Подробнее о каждой части - в моих предыдущих статьях:
Создаем свой RAG: от загрузки данных до генерации ответов с LangGraph. Часть 2 / Хабр
Используем языковые модели в AI-агентах. Часть 2. Retrievers, TextSplitters
и моем канале. В нем вы найдете:
Полный код каждого агента
Файл с дополнительными архитектурами (Supervisor, plan-and-execute), которые не поместились в эту статью
Ссылка: мой канал
Виды агентных архитектур
Агентов можно разделить на две группы:
Адаптивные
Не адаптивные
Агенты из первой группы умеют адаптироваться / изменять свое поведение в зависимости от окружающей среды.
Агенты из второй группы не умеют менять свой план действий. Они больше подвержены зацикливанию, но проще в реализации.
Сразу скажу, что ваша реализация агентов может отличаться. Сфера AI еще слишком молодая, чтобы устанавливать строгие правила, поэтому каждый может подстроиться под себя.
Используемые библиотеки
Я буду использовать langchain, langchain_gigachat. Если хотите, можете добавить langgraph
Установка:
!pip install langchain langchain-gigachat
Инициализация модели:
from langchain_gigachat import GigaChat
llm = GigaChat(
verify_ssl_certs=False,
credentials="токен",
model="GigaChat-2",
temperature=0.1
)
Токен можно получить бесплатно на странице GigaChat
Необходимые зависимости:
from langchain.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Optional, Any, Type
import json
ReAct agent
Ярким представителем второй категории является ReAct агент.
Логика работы агента:
Проанализировать запрос пользователя
Выбрать подходящий инструмент или FINISH
Получить результат вызова инструмента
Снова проанализировать запрос (шаг 1)
Основное преимущество - простота реализации и масштабирования агента.
Кейсы использования:
Простые задачи, которые требует одноуровневого вызова инструментов. Например: напиши краткое содержание последних 10 статей на Хабр на тему ИИ.
Агенту нужно будет:
Обратиться к API хабра и получить последние новости
Провести суммаризацию
Сгенерировать ответ
Создадим два инструмента
Я напишу два инструмента: добавление в импровизированную базу и чтение. Обратите внимание на то, как я задаю схему входных аргументов, их описание и примеры.
#добавление записи в базу (словарь)
class RegisterUserInput(BaseModel):
username: str = Field(..., description="Логин", examples=["ViacheslavVoo"])
email: str = Field(..., description="Email", examples=["Viacheslav@test.com"])
phone: Optional[str] = Field(None, description="Номер телефона в формате +7XXX...", examples=["+7999999999"])
class RegisterUserTool(BaseTool):
name: str = "register_user"
description: str = "Регистрация пользователя в системе"
args_schema: Type[BaseModel] = RegisterUserInput
def _run(self, **kwargs):
users[kwargs["username"]] = kwargs["email"]
return f"User {kwargs['username']} успешно зарегистрирован"
# поиск в базе (словаре)
class SearchDBInput(BaseModel):
username: str = Field(..., description="Логин/имя пользователя", examples=["ViacheslavVoo"])
limit: Optional[int] = Field(5, description="Лимит результатов")
class SearchDBTool(BaseTool):
name: str = "search_db"
description: str = "Ищет данные пользователей в системе"
args_schema: Type[BaseModel] = SearchDBInput
def _run(self, username: str, limit: int = 5):
return f"Найден пользователь {users[username]}'"
Так как работу таких систем тяжело воспринимать, не имея полной картины, я начну с общего описания.
Каркас агента
-
run()
Принимает текущую задачу
Возвращает результат работы агента
-
build_prompt()
Формирует системный промпт с историей действий, текущей задачей, доступными инструментами
-
parse_response()
Извлекает из ответа название инструмента и необходимые аргументы
В процессе написания статьи я решил добавил еще один шаг - генерация итогового ответа на основе запроса и истории действий, поэтому последний шаг - final_answer()
Каркас агента на Python:
class ReactAgent:
def __init__(self, tools: list[BaseTool], llm, max_iterations: int = 5):
self.tools = {tool.name: tool for tool in tools}
self.llm = llm
self.max_iterations = max_iterations
self.history = []
@property
def tool_desc(self):
pass
def run(self, task: str) -> str:
pass
def _build_prompt(self, task: str) -> str:
pass
def _parse_response(self, response: str) -> tuple[str, dict]:
pass
def _final_answer(self, task: str) -> str:
pass
При инициализации агента необходимо передать:
Набор инструментов
Модель
Максимальное количество операций
Также при инициализации создается словарь, в котором ключами являются названия функций, и история действий.
Начнем с реализации tool_desc.
Задача: сгенерировать описание доступных инструментов, чтобы использовать его в системном промпте.
@property
def tool_desc(self):
tools_desc = []
for tool in self.tools.values():
args_desc = "\n".join(
f" {field}: {value.description} ({'обязательный' if value.is_required() else 'опциональный'})"
for field, value in tool.args_schema.model_fields.items()
)
tools_desc.append(f"{tool.name} - {tool.description}\n{args_desc}")
tools_desc.append(f"FINISH - выбери для завершения работы, если задача выполнена")
return tools_desc
Следующим станет метод build_prompt.
Задача: Сгенерировать prompt, который будет содержать:
Роль агента
Инструкции
Доступные инструменты
-
Историю действий
Важное замечание: мы явно указываем, что при выборе FINISH необходимо передать пустую строку для ключа action_input. Это сделано для единого формата вывода
def _build_prompt(self, task: str) -> str:
history = "\n".join(self.history[-3:]) if self.history else "Нет истории"
return f"""
Ты - умный ассистент, который помогает решаить задачу пользователя. Ты должен выбрать один из доступных инструментов
или написать FINISH если задача выполнена
**Задача**: {task}
**Доступные инструменты**:
{chr(10).join(self.tool_desc)}
**Инструкции**:
-проанализируй задачу
-проанлизируй историю
-выбери подходящий инструмент или FINISH, если задача была выполнена в истории.
-обязательно проанализируй вызовы, которые ты делал раньше
-если в истории ты уже вызывал инструмент с такими аргументами и инструмент был выполнен успешно, то не вызывай его снова. Переходи к следующему инструменту или к FINISH.
-если ты выбераешь FINISH то action_input: ""
**Твоя история вызова инструментов и результаты вызовов**:
{history}
**Ответ в формате JSON**:
{{
"action": "имя_инструмента",
"action_input": {{
"arg1": "значение",
"arg2": "значение"
}},
"though": "обоснование выбора инструмента"
}}
Верни только ответ сторого в формате JSON со всеми заполненными полями. Не добавляй других слов и комментариев
"""
parse_response
После получения ответа от LLM необходимо выделить название инструмента и аргументы.
def _parse_response(self, response: str) -> tuple[str, dict]:
data = json.loads(response.strip())
return data["action"], data["action_input"]
final_answer
Осталось получить итоговый ответ. Этот шаг больше нужен пользователя или других агентов, если вы будете использовать их вместе.
def _final_answer(self, task: str) -> str:
prompt = """
Ты - умный ассистент, который должен сформулировать итоговый ответ для пользователя на основе истории действий.
Запрос пользователя: {user_input}
Итсория действий: {history}
Верни только итоговый ответ - вывод о выполнении задачи.
"""
chain = ChatPromptTemplate.from_template(prompt) | self.llm | StrOutputParser()
return chain.invoke({"user_input": task, "history": self.history})
run
В нем мы будем:
Генерировать prompt
Получать ответ от модели
Извлекать action/action_input
Вызывать инструмент
Получать ответ
Добавлять ответ в историю действий
def run(self, task: str) -> str:
for _ in range(self.max_iterations):
prompt = self._build_prompt(task)
llm_response = self.llm.invoke(prompt)
try:
# 2. Парсинг ответа
action, action_input = self._parse_response(llm_response.content)
if action == "FINISH":
return action_input
except Exception as e:
self.history.append(f"Ошибка парсинга: {str(e)}")
continue
# 3. Валидация и выполнение
if action not in self.tools:
self.history.append(f"Неизвестный инструмент: {action}")
continue
tool = self.tools[action]
try:
observation = tool.run(action_input)
self.history.append(f"Ты вызвал инструмент:{action}({action_input}) Результат выполнения: {observation}")
except Exception as e:
self.history.append(f"Ошибка выполнения {action}: {str(e)}")
return "Достигнут лимит итераций"
При неправильном ответе модели или неудачном вызове инструмента советующее сообщение добавляется в историю.
Главный недостаток ReAct агента: при возникновении ошибки он не может изменить свой план действий или подстроиться под сообщение ошибки, поэтому возникает вероятность зацикливания.
Вызов агента:
from src.llm import llm #ваша языковая модель.
if __name__ == "__main__":
agent = ReactAgent(tools, llm)
# Задача 1: Регистрация
result = agent.run("Зарегистрируй пользователя Ivan с email ivan@test.com")
print(result)
# Составная Задача 2: Регистрация и поиск
result = agent.run("Зарегистрируй пользователя Viacheslav с email Viacheslav@test.com и найди пользователя Ivan")
print(result)
Пример Prompt запроса, который будет передаваться модели:
Ты - умный ассистент, который помогает решаить задачу пользователя. Ты должен выбрать один из доступных инструментов
или написать FINISH если задача выполнена
**Задача**: Зарегистрируй пользователя Ivan с email ivan@test.com
**Доступные инструменты**:
register_user - Регистрация пользователя в системе
username: Логин (обязательный)
email: Email (обязательный)
phone: Номер телефона в формате +7XXX... (опциональный)
search_db - Ищет данные пользователей в базе
username: Логин (обязательный)
limit: Лимит результатов (опциональный)
FINISH - выбери для завершения работы, если задача выполнена
**Инструкции**:
-проанализируй задачу
-проанлизируй историю
-выбери подходящий инструмент или FINISH, если задача была выполнена в истории.
-обязательно проанализируй вызовы, которые ты делал раньше
-если в истории ты уже вызывал инструмент с такими аргументами и инструмент был выполнен успешно, то не вызывай его снова. Переходи к следующему инструменту или к FINISH.
-если ты выбераешь FINISH то action_input: ""
**Твоя история вызова инструментов и результаты вызовов**:
Нет истории
**Ответ в формате JSON**:
{
"action": "имя_инструмента",
"action_input": {
"arg1": "значение",
"arg2": "значение"
},
"though": "обоснование выбора инструмента"
}
Верни только ответ сторого в формате JSON со всеми заполненными полями. Не добавляй других слов и комментариев
После вызовов инструмента запрос дополняется историей и выводит результат:
**Твоя история вызова инструментов и результаты вызовов**:
Ты вызвал инструмент:register_user({'username': 'Viacheslav', 'email': 'Viacheslav@test.com'}) Результат выполнения: User Viacheslav успешно зарегистрирован
Ты вызвал инструмент:search_db({'username': 'Ivan'}) Результат выполнения: Найден пользователь ivan@test.com'
Пример ответа:
Пользователь Ivan с email ivan@test.com успешно зарегистрирован
Подведем итог по React Agent:
Основные преимущества:
Автономность
Гибкость
Прозрачность (можем видеть историю вызываемых инструментов)
Основные недостатки:
Риск зацикливания
Зависимость от качества LLM. Например, GigaChat-Lite может не справиться с вызовом инструментов
Недетерминированность. Порядок вызова инструментов может отличаться для одинаковых запросах
Reflection Agent
Представитель первого типа агентов. Он во многом похож на ReAct, но с одним отличием: в случае ошибки он может адаптироваться под нее. Сразу скажу, что я имею ввиду под Reflection агентом, агента, который адаптируется к ошибкам, а не проводит глубокое рассуждение для ответа на вопрос.
Если вы хотите увидеть вторую версию, пожалуйста, напишите об этом в комментариях и я выложу отдельную статью.
Замечание: ReAct сможет корректно работать с помощью модели GigaChat-2. Для Reflection agent я использовал GigaChat-2-Max, так как младшая версия не справилась с форматом вывода.
Логика работы агента:
Проанализировать запрос пользователя
Выбрать подходящий инструмент
Если инструмент != FINISH -> вызвать инструмент
Если инструмент вернул результат без ошибок, добавить вызов в историю и перейти к 1
Если инструмент вернул сообщение об ошибке: проанализировать ошибку -> создать обновленный prompt -> вернуться к шагу 1
Найти применение такому виду агентов может быть сложно, но благодаря шагу с анализом ошибки он сможет адаптироваться, например, под изменившуюся схему входных аргументов для инструментов. Например, если вы используете сторонние инструменты, которые находятся в активной стадии разработки.
Да, возможно, вы никогда не будете его использовать, но мне по каким то причинам нравится его идея, поэтому я решил добавить его сюда.
Хотя... Каждый агент по своему прекрасен)
Каркас агента:
run() - главный метод через который вызывается агент
exectute_exction() - выполняет вызов инструмента
needs_reflection() - метод, в которым мы будем определять, нужно ли изменять запрос
perform_reflection() - метод, который будет генерировать своеобразную рекомендацию по исправлению ошибки
build_prompt() - формирует prompt с историей, задачей, набором инструментов
build_reflection_prompt() - формирует prompt с рекомендацией по исправлению ошибки
store_memory() - добавляет вызовы в историю
perform_reflection() и build_reflection_prompt() можно было бы совместить в один метод, но я решил разделить логику на два этапа.
Каркас на Python:
class ReflectionAgent:
"""Агент с возможностью саморефлексии и анализа своих действий"""
def __init__(self,
llm,
tools: list,
max_iterations: int = 5,
timeout: int = 10):
self.llm = llm
self.tools = {tool.name: tool for tool in tools}
self.max_iterations = max_iterations
self.timeout = timeout
self.history = []
self.iteration = 0
@property
def tool_desc(self):
#РЕАЛИЗАЦИЯ КАК У ReAct agent
def run(self, task: str) -> str:
pass
def _execute_action(self, action: str, action_input: str) -> str:
pass
def _perform_reflection(self) -> str:
pass
def _build_prompt(self, task: str) -> str:
pass
def _parse_response(self, response: str) -> tuple:
pass
def _reset_state(self):
pass
def _store_memory(self, thought: str, action: str, action_input: str, observation: str):
pass
def _handle_finish(self, result: str) -> str:
pass
def _handle_max_iterations_reached(self) -> str:
pass
Начнем с execute_action:
Логика довольно простая:
Если инструмента нет в списке доступных или его вызов завершился с ошибкой, будет возвращен соответствующий результат с ошибкой
-
Иначе результат выполнения
Важная особенность реализации в текущем контекста: в ошибке должно содержаться слово "Ошибка / error или т.п"
def _execute_action(self, action: str, action_input: str) -> str:
# Проверка существования инструмента
if action not in self.tools:
return f"Ошибка: Инструмент '{action}' не найден. Доступные инструменты: {self.tool_desc}"
try:
tool = self.tools[action]
result = tool.run(action_input)
return result
except Exception as e:
return f"Error executing '{action}': {str(e)}"
Блок, связанный с рефлексией
needs_reflection. Его логика чуть сложнее:
Если истории действий нет -> анализ ошибки не нужен, так как действий еще не было
Если в наблюдении (observation) последнего действия есть ошибка -> return True
Если несколько раз подряд был вызван один и тот же инструмент -> return True
В остальных случаях: return False
Вы можете изменять логику работы для своего случая
def _needs_reflection(self) -> bool:
"""Определяет, нужно ли проводить рефлексию"""
if not self.history:
return False
last_action = self.history[-1]
# Рефлексия при явных ошибках
if any(keyword in last_action['observation'].lower()
for keyword in ["error", "fail", "unknown", "ошибка"]):
return True
# Или если несколько одинаковых действий подряд
if len(self.history) > 2:
last_actions = [m['action'] for m in self.history[-3:]]
if len(set(last_actions)) == 1: # Все одинаковые
return True
return False
perform_reflection
Затем создадим подсказку по исправлению ошибки. На этом шаге вы можете написать свой уникальный prompt, в котором будут приведены разрешения наиболее частых конфликтов. Я напишу общий запрос, который будет просить модель сгенерировать небольшую подсказку.
def _perform_reflection(self) -> str:
history = "\n".join(
f"Iteration {i}: {m['action']}({m['action_input']}) => {m['observation'][:100]}..."
for i, m in enumerate(self.history)
)
reflection_prompt = f"""
Ты - умный ассистент, который помогает AI агенту исправлять ошибки в вызовах инструментов (tools)
История вызовов:
{history}
Твоя задача: проанализируй историю вызов, передаваемые аргументы и ошибки. Определи из за чего возникла ошибка и как ее исправить.
Отвечай кратко.
"""
return self.llm.invoke(reflection_prompt).content
build_reflection_prompt
После создания подсказки для решения задачи, ее необходимо передать в prompt, с помощью которого модель сможет сгенерировать адаптированный ответ.
Замечание: Подразумевается, что модель уже знает, какой инструмент выбирать (это последний вызов, который завершился с ошибкой). В конце промпта обязательно нужно добавить предложение о формате выходных данных.
Если вы уже запутались, то ничего страшного. Все станет понятно после того, как вы увидите адаптацию к ошибке.
def _build_reflection_prompt(self, task: str, reflection: str) -> str:
"""Строит промпт с учетом рефлексии"""
history = "\n".join(
f"""Iteration {i}:
Thought: {m['thought']}
Action: {m['action']}
ActionInput: {m['action_input']}
Observation: {m['observation'][:200]}...
"""
for i, m in enumerate(self.history)
)
return f"""
Ты - умный ассистент, который исправляет ошибки вызовов инструментов в формате JSON.
Рекомендации по устранению ошибок:
{reflection}
Предыдущие действия:
{history}
Твоя задача: используя историю вызов инструментов и рекомендации по усранению ошибки, реши задачу:
{task}
Сохраняй тот же формат вывода что и в истории вызовов инструментов. Не пиши никаких дополнительных пояснений кроме исправленных ошибок в вызове инструментов.
Отвечай строго в формате JSON начиная с фигурных скобок. Размышляй шаг за шагом.
"""
build_prompt
В нем мы формируем наш главный prompt запрос, который будет передаваться на первом шаге и на последующих шагах, которые не требуют обработки ошибок. Обратите внимание на структуру запроса и последовательность абзацев.
Замечание: в промпте мы явно указываем, когда нужно заканчивать работу и выбирать FINISH.
def _build_prompt(self, task: str) -> str:
"""Строит начальный промпт"""
history = "\n".join(
[
f"iteration: {d['iteration']}. thought: {d['thought']}. action: {d['action']}. action_input: {str(d['action_input'])} observation: {d['observation']}"
for d in self.history
]
) if self.history else "Нет истории"
return f"""
Ты - умный ассистент, который помогает решаить задачу пользователя. Ты должен
выбрать из доступынх инструментов или написать FINISH, если задача решена
**Задача** {task}
**Твоя история вызова инструментов и результаты вызовов**:
{history}
**Достпные инструменты**
{self.tool_desc}
**Инструкции**:
-проанализируй задачу
-проанлизируй историю
-выбери подходящий инструмент или FINISH, если задача была выполнена в истории.
-обязательно проанализируй вызовы, которые ты делал раньше
-если в истории ты уже вызывал инструмент с такими аргументами и инструмент был выполнен успешно, то не вызывай его снова. Переходи к следующему инструменту или к FINISH.
-если ты выбераешь FINISH то action_input: ""
**Ответ в формате JSON**:
{{
"action": "имя_инструмента",
"action_input": {{
"arg1": "значение",
"arg2": "значение"
}},
"thought": "обоснование выбора инструмента"
}}
Верни только ответ сторого в формате JSON со всеми заполненными полями. Не добавляй других слов и комментариев
"""
parse_response
Он не отличается от ReAct агента
def _parse_response(self, response: str) -> tuple:
"""Парсит ответ LLM"""
data = json.loads(response.strip())
return data["thought"], data["action"], data["action_input"]
store_memory
Метод, который будет сохранять историю вызовов инструментов.
def _store_memory(self, thought: str, action: str, action_input: str, observation: str):
"""Сохраняет информацию в память агента"""
self.history.append({
'iteration': self.iteration,
'thought': thought,
'action': action,
'action_input': action_input,
'observation': observation
})
Два дополнительных мелких метода для уменьшения кода в run.
handle_finish
def _handle_finish(self, result: str) -> str:
"""Обрабатывает завершение задачи"""
print(f"Выполнение задачи завершено")
return result
handle_max_iterations_reached
def _handle_max_iterations_reached(self) -> str:
"""Обрабатывает достижение максимального числа итераций"""
last_observation = self.history[-1]['observation'] if self.history else "No actions taken"
return f"Max iterations reached. Last state: {last_observation}"
Мы написали реализацию всех необходимых методов. Пришло время главного вызова.
run
Логика работы:
Создать начальный prompt
Получить ответ от модели с выбранным инструментом
Распарсить ответ
Если действие != FINISH, вызвать инструмент
Сохранить вызов инструмента и результат
Проверь, нужна ли работа над ошибками
Перейти к шагу 2
def run(self, task: str) -> str:
prompt = self._build_prompt(task)
while self.iteration < self.max_iterations:
response = self.llm.invoke(prompt)
thought, action, action_input = self._parse_response(response.content)
if action == "FINISH":
return self._handle_finish(action_input)
observation = self._execute_action(action, action_input)
self._store_memory(thought, action, action_input, observation)
if self._needs_reflection():
reflection = self._perform_reflection()
prompt = self._build_reflection_prompt(task, reflection)
else:
prompt = self._build_prompt(task)
self.iteration += 1
return self._handle_max_iterations_reached()
При успешном вызове инструментов, его работа не будет отличаться от работы ReAct агента, поэтому я не буду приводить результат работы. Вместо этого я создам искусственную ошибку , изменив название одного из входных аргументов инструмента: email -> user_email.
INIT PROMPT
Ты - умный ассистент, который помогает решаить задачу пользователя. Ты должен
выбрать из доступынх инструментов или написать FINISH, если задача решена
**Задача** Зарегистрируй пользователя Ivan с email ivan@test.com
**Твоя история вызова инструментов и результаты вызовов**:
Нет истории
**Достпные инструменты**
['register_user - Регистрация пользователя в системе\n username: Логин (обязательный)\n email: Email (обязательный)\n phone: Номер телефона в формате +7XXX... (опциональный)', 'search_db - Ищет данные пользователей в базе\n username: Логин/имя пользователя (обязательный)\n limit: Лимит результатов (опциональный)', 'FINISH - выбери для завершения работы, если задача выполнена']
**Инструкции**:
-проанализируй задачу
-проанлизируй историю
-выбери подходящий инструмент или FINISH, если задача была выполнена в истории.
-обязательно проанализируй вызовы, которые ты делал раньше
-если в истории ты уже вызывал инструмент с такими аргументами и инструмент был выполнен успешно, то не вызывай его снова. Переходи к следующему инструменту или к FINISH.
-если ты выбераешь FINISH то action_input: ""
**Ответ в формате JSON**:
{
"action": "имя_инструмента",
"action_input": {
"arg1": "значение",
"arg2": "значение"
},
"thought": "обоснование выбора инструмента"
}
Верни только ответ сторого в формате JSON со всеми заполненными полями. Не добавляй других слов и комментариев
RESPONSE content='{\n "action": "register_user",\n "action_input": {\n "username": "Ivan",\n "email": "ivan@test.com"\n },\n "thought": "Пользователь хочет зарегистрироваться с именем Иван и указанным емейлом. Используем инструмент регистрации."\n}' additional_kwargs={} response_metadata={'token_usage': {'prompt_tokens': 394, 'completion_tokens': 59, 'total_tokens': 453, 'precached_prompt_tokens': 3}, 'model_name': 'GigaChat-2-Max:2.0.28.2', 'x_headers': {'x-request-id': 'e1509dc9-13c6-4c97-b84d-31e363a80243', 'x-session-id': '5fb1ce37-f276-4ab1-a5cb-4110b43903a5', 'x-client-id': None}, 'finish_reason': 'stop'} id='e1509dc9-13c6-4c97-b84d-31e363a80243' usage_metadata={'output_tokens': 59, 'input_tokens': 394, 'total_tokens': 453, 'input_token_details': {'cache_read': 3}}
{
"action": "register_user",
"action_input": {
"username": "Ivan",
"email": "ivan@test.com"
},
"thought": "Пользователь хочет зарегистрироваться с именем Иван и указанным емейлом. Используем инструмент регистрации."
}
Пытаемся вызвать инструмент и получаем ошибку:
[Action Log] Iteration: 0
Tool: register_user
Input: {'username': 'Ivan', 'email': 'ivan@test.com'}
Result: Ошибка : неожиданное название аргумента email. Достпуные аргументы: user_email, username...
Проводим анализ ошибки:
REFLECTION Ошибка возникла из-за неправильного названия аргумента `email`, нужно использовать доступный аргумент `user_email`. Исправленный вызов:
```python
register_user({'username': 'Ivan', 'user_email': 'ivan@test.com'})
```
Создаем новый prompt с подсказкой по исправлению:
PROMPT AFTER REFLECTION
Ты - умный ассистент, который исправляет ошибки вызовов инструментов в формате JSON.
Рекомендации по устранению ошибок:
Ошибка возникла из-за неправильного названия аргумента `email`, нужно использовать доступный аргумент `user_email`. Исправленный вызов:
```python
register_user({'username': 'Ivan', 'user_email': 'ivan@test.com'})
```
Предыдущие действия:
Iteration 0:
Thought: Пользователь хочет зарегистрироваться с именем Иван и указанным емейлом. Используем инструмент регистрации.
Action: register_user
ActionInput: {'username': 'Ivan', 'email': 'ivan@test.com'}
Observation: Ошибка : неожиданное название аргумента email. Достпуные аргументы: user_email, username...
Твоя задача: используя историю вызов инструментов и рекомендации по усранению ошибки, реши задачу:
Зарегистрируй пользователя Ivan с email ivan@test.com
Сохраняй тот же формат вывода что и в истории вызовов инструментов. Не пиши никаких дополнительных пояснений кроме исправленных ошибок в вызове инструментов.
Отвечай строго в формате JSON начиная с фигурных скобок. Размышляй шаг за шагом.
Получаем новый результат с обновленным названием для аргумента email:
RESPONSE content='{\n "thought": "Пользователь хочет зарегистрироваться с именем Иван и указанным емейлом. Используем инструмент регистрации с правильным названием аргумента.",\n "action": "register_user",\n "action_input": {"username": "Ivan", "user_email": "ivan@test.com"}\n}' additional_kwargs={} response_metadata={'token_usage': {'prompt_tokens': 264, 'completion_tokens': 60, 'total_tokens': 324, 'precached_prompt_tokens': 3}, 'model_name': 'GigaChat-2-Max:2.0.28.2', 'x_headers': {'x-request-id': 'af90213d-6751-4ced-980c-9f9a26de140d', 'x-session-id': 'b104631f-f553-4ff6-8200-2e1d7bd9ed27', 'x-client-id': None}, 'finish_reason': 'stop'} id='af90213d-6751-4ced-980c-9f9a26de140d' usage_metadata={'output_tokens': 60, 'input_tokens': 264, 'total_tokens': 324, 'input_token_details': {'cache_read': 3}}
RESPONSE IN PARSE {
"thought": "Пользователь хочет зарегистрироваться с именем Иван и указанным емейлом. Используем инструмент регистрации с правильным названием аргумента.",
"action": "register_user",
"action_input": {"username": "Ivan", "user_email": "ivan@test.com"}
}
Конечно, это игрушечный пример, но если такой подходит позволит адаптироваться и к сильному изменению входной схеме аргументов.
Заключение
С каждым месяцем появляется все больше архитектур проектирования агентных систем, но представленные выше (особенно ReAct) являются наиболее часто используемыми. И снова повторюсь, вы можете реализовывать свою логику. Возможно, она даже станет общепризнанной в AI сообществе.
Если вам понравилась статья, оставьте комментарий и поделитесь своим опытом. Где искать код с подробным описанием, вы знаете)
Спасибо за прочтение!