Привет, Хабр!
В последнее время 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 сообществе.
Если вам понравилась статья, оставьте комментарий и поделитесь своим опытом. Где искать код с подробным описанием, вы знаете)
Спасибо за прочтение!
ссылка на оригинал статьи https://habr.com/ru/articles/933372/
Добавить комментарий