Как устроены AI агенты: разбираемся на примере ReAct и Reflection

от автора

Привет, Хабр!

В последнее время AI агенты стали главным трендом. Многие используют готовые шаблоны, такие как create_react_agent из langchain, но не понимают, как они работают под капотом. При этом агенты становятся все сложнее, и придет время, когда нужно будет писать свою реализацию.

В этой статье мы разберем:

  • Устройство ReAct агента

  • Устройство Reflection агента

  • Примеры системных prompt запросов

  • Кейсы использования и особенности

Что такое AI агент

Прежде чем переходить к коду, нужно понять, что можно считать агентом. Существует множество определений, например:

  • AI агент — система, способная принимать автономные решения, взаимодействовать с окружающей средой и другими агентами для выполнения конкретных задач

  • AI агент — программное обеспечение, которое автономно собирает данные и выполняет задачи с использованием этих данных.

Все они сходятся в одном: агентная система должна уметь действовать автономно и сама выбирать следующий шаг. Конечно, это все еще расплывчатое определение. У меня бывали кейсы, когда агент требовал подтверждения своих действий у человека на многих шагах, но при этом последовательность его действий была недетерминированной.

Составные части AI агентов:

  1. System Prompt

    • Задает роль

    • Описывает поведение

    • Определяет доступные инструменты

  2. Tools (Инструменты)

    • Функции, доступные агенту для решения задач. Например, поиск в интернете или базе

  3. Система хранения контекста

    • Краткосрочная память

    • Долгосрочная память

Если вы используете 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 агент.

Логика работы агента:

  1. Проанализировать запрос пользователя

  2. Выбрать подходящий инструмент или FINISH

  3. Получить результат вызова инструмента

  4. Снова проанализировать запрос (шаг 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]}'" 

Так как работу таких систем тяжело воспринимать, не имея полной картины, я начну с общего описания.

Каркас агента

  1. run()

    • Принимает текущую задачу

    • Возвращает результат работы агента

  2. build_prompt()

    • Формирует системный промпт с историей действий, текущей задачей, доступными инструментами

  3. 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, так как младшая версия не справилась с форматом вывода.

Логика работы агента:

  1. Проанализировать запрос пользователя

  2. Выбрать подходящий инструмент

  3. Если инструмент != FINISH -> вызвать инструмент

  4. Если инструмент вернул результат без ошибок, добавить вызов в историю и перейти к 1

  5. Если инструмент вернул сообщение об ошибке: проанализировать ошибку -> создать обновленный prompt -> вернуться к шагу 1

Найти применение такому виду агентов может быть сложно, но благодаря шагу с анализом ошибки он сможет адаптироваться, например, под изменившуюся схему входных аргументов для инструментов. Например, если вы используете сторонние инструменты, которые находятся в активной стадии разработки.

Да, возможно, вы никогда не будете его использовать, но мне по каким то причинам нравится его идея, поэтому я решил добавить его сюда.

Хотя… Каждый агент по своему прекрасен)

Каркас агента:

  1. run() — главный метод через который вызывается агент

  2. exectute_exction() — выполняет вызов инструмента

  3. needs_reflection() — метод, в которым мы будем определять, нужно ли изменять запрос

  4. perform_reflection() — метод, который будет генерировать своеобразную рекомендацию по исправлению ошибки

  5. build_prompt() — формирует prompt с историей, задачей, набором инструментов

  6. build_reflection_prompt() — формирует prompt с рекомендацией по исправлению ошибки

  7. 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 сообществе.

Если вам понравилась статья, оставьте комментарий и поделитесь своим опытом. Где искать код с подробным описанием, вы знаете)

Спасибо за прочтение!


ссылка на оригинал статьи https://habr.com/ru/articles/933372/