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

В последнее время 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 сообществе.

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

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

Комментарии (0)