- BrainTools - https://www.braintools.ru -

Мы привыкли, что ИИ-агенты — это про скорость. Быстрее написать код, быстрее ответить, быстрее сгенерировать. Но скорость без размышления — это не интеллект [1], а рефлекс [2]. Настоящий прорыв происходит, когда агент начинает думать, прежде чем делать. Проверять себя после каждого шага. Сомневаться. Спрашивать разрешения. И только потом действовать.
Современные языковые модели отлично рассуждают в теории. Но стоит дать им инструменты и доступ к реальному миру, как проявляются системные уязвимости: галлюцинации, превращённые в действия; каскадные ошибки [3], где одна неточность тянет за собой цепочку неверных решений; слепое следование цели с игнорированием побочных эффектов; отсутствие самокритики. Решение — не в том, чтобы сделать модель умнее. Решение — в архитектуре, которая принудительно встраивает размышление в каждое звено цикла «восприятие → решение → действие → проверка».
Эта статья — про архитектуру, которая превращает бездумный конвейер генерации в мыслящего коллегу. И она применима не только к работе с кодом, а к любому домену, где цена ошибки высока: юриспруденция, медицина, финансы, управление инфраструктурой, маркетинг, поддержка клиентов, образование.
reflective_agent/
├── core/
│ ├── __init__.py
│ ├── agent.py # Основной цикл агента
│ ├── context.py # Динамический контекст
│ ├── state.py # Управление состоянием
│ └── types.py # Типы данных
├── tools/
│ ├── __init__.py
│ ├── base.py # Базовый класс инструментов
│ ├── filesystem.py # Файловая система
│ ├── terminal.py # Терминальные команды
│ └── mcp_adapter.py # MCP протокол
├── safety/
│ ├── __init__.py
│ ├── jail.py # Scope Jail
│ ├── detectors.py # Детекция циклов
│ └── snapshots.py # Снапшоты и откат
├── ui/
│ ├── __init__.py
│ └── console.py # Консольный интерфейс
└── main.py # Точка входа
# core/types.py
from dataclasses import dataclass, field
from typing import Any, Optional, List, Dict, Literal
from datetime import datetime
from enum import Enum
class AgentMode(Enum):
CHAT = "chat"
AGENT = "agent"
TERMINAL = "terminal"
class ToolRisk(Enum):
SAFE_READ = "safe_read" # Автоматически
MODIFY = "modify" # Черновик
DESTRUCTIVE = "destructive" # Требует подтверждения
@dataclass
class Message:
role: Literal["user", "assistant", "system", "tool"]
content: Optional[str]
timestamp: float = field(default_factory=datetime.now().timestamp)
tool_calls: Optional[List[Dict]] = None
tool_call_id: Optional[str] = None
name: Optional[str] = None
requires_action: Optional[str] = None
@dataclass
class ToolCall:
id: str
name: str
arguments: Dict[str, Any]
risk: ToolRisk
@dataclass
class Draft:
file_path: str
original_content: str
new_content: str
status: Literal["pending", "accepted", "rejected"] = "pending"
@dataclass
class Snapshot:
id: str
timestamp: float
files: Dict[str, str]
messages: List[Message]
drafts: List[Draft]
# core/agent.py
import asyncio
import json
import hashlib
from typing import List, Optional, Dict, Any, Callable
from datetime import datetime
from openai import AsyncOpenAI
from .types import (
AgentMode, Message, ToolCall, Draft, Snapshot, ToolRisk
)
from .context import DynamicContext
from .state import AgentState
from safety.jail import ScopeJail
from safety.detectors import LoopDetector, IterationLimiter
from safety.snapshots import SnapshotManager
from tools.base import ToolRegistry
class ReflectiveAgent:
"""Рефлексирующий ИИ-агент с семиступенчатым циклом"""
def __init__(
self,
model: str = "gpt-4",
api_key: str = None,
max_iterations: int = 15,
require_confirmation: bool = True
):
self.client = AsyncOpenAI(api_key=api_key)
self.model = model
self.state = AgentState()
self.context = DynamicContext()
self.tools = ToolRegistry()
self.jail = ScopeJail()
self.loop_detector = LoopDetector(max_repeats=3)
self.iteration_limiter = IterationLimiter(max_iterations)
self.snapshot_manager = SnapshotManager()
self.require_confirmation = require_confirmation
# Флаги
self.is_reflecting = False
self._abort_controller = False
async def run(self, user_input: str) -> None:
"""Главный метод запуска агента"""
# Создаем снапшот перед началом
await self.snapshot_manager.create_snapshot(self.state)
# Добавляем сообщение пользователя
user_message = Message(
role="user",
content=user_input,
timestamp=datetime.now().timestamp()
)
self.state.add_message(user_message)
# Запускаем рекурсивный цикл
await self._run_cycle(depth=1)
async def _run_cycle(self, depth: int) -> None:
"""Рекурсивный цикл выполнения"""
# Проверка прерывания
if self._abort_controller:
return
# Проверка лимита итераций
if not self.iteration_limiter.can_continue(depth):
await self._handle_iteration_limit()
return
try:
# 1. ВОСПРИЯТИЕ - Сбор динамического контекста
context = await self.context.build_context(self.state)
# 2. ПЛАНИРОВАНИЕ - Вызов LLM
response = await self._call_llm(context)
# Сохраняем ответ ассистента
assistant_msg = Message(
role="assistant",
content=response.get("content"),
tool_calls=response.get("tool_calls")
)
self.state.add_message(assistant_msg)
# 3. ОБРАБОТКА TOOL CALLS
if response.get("tool_calls"):
await self._process_tool_calls(response["tool_calls"])
# Рекурсивный вызов для следующего шага
await self._run_cycle(depth + 1)
else:
# Нет вызовов инструментов -> завершение
await self._complete_task()
except Exception as e:
await self._handle_error(e, depth)
async def _call_llm(self, context: str) -> Dict[str, Any]:
"""Вызов LLM с системным промптом и инструментами"""
messages = [
{"role": "system", "content": self._build_system_prompt()},
*[self._serialize_message(m) for m in self.state.messages]
]
# Добавляем контекст как системное сообщение
messages.insert(1, {"role": "system", "content": context})
tools = self.tools.get_openai_schema()
response = await self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=tools if tools else None,
tool_choice="auto" if tools else None,
temperature=0.3
)
message = response.choices[0].message
result = {"content": message.content or ""}
if message.tool_calls:
result["tool_calls"] = [
{
"id": tc.id,
"name": tc.function.name,
"arguments": json.loads(tc.function.arguments)
}
for tc in message.tool_calls
]
return result
async def _process_tool_calls(self, tool_calls: List[Dict]) -> None:
"""Обработка вызовов инструментов"""
for tool_call in tool_calls:
if self._abort_controller:
break
# 4. ШЛЮЗ - Проверка безопасности
risk = self.tools.get_risk(tool_call["name"])
if risk == ToolRisk.DESTRUCTIVE and self.require_confirmation:
# Пауза, ждем подтверждения пользователя
await self._request_confirmation(tool_call)
return # Выход из цикла, ждем событие
# 5. ДЕТЕКЦИЯ ЗАЦИКЛИВАНИЙ
signature = self._get_tool_signature(tool_call)
if self.loop_detector.check(signature):
await self._handle_loop_detected(tool_call)
return
# 6. Scope Jail - проверка границ
if not await self.jail.check_permissions(tool_call):
await self._handle_access_denied(tool_call)
continue
# 7. ВЫПОЛНЕНИЕ ИНСТРУМЕНТА
result = await self.tools.execute(
tool_call["name"],
tool_call["arguments"],
is_draft=(risk == ToolRisk.MODIFY)
)
# Сохраняем результат
tool_msg = Message(
role="tool",
content=result,
tool_call_id=tool_call["id"],
name=tool_call["name"]
)
self.state.add_message(tool_msg)
async def _complete_task(self) -> None:
"""Завершение задачи с фазой рефлексии"""
if not self.is_reflecting:
# ФАЗА РЕФЛЕКСИИ
self.is_reflecting = True
# Запрашиваем самопроверку
reflection_msg = Message(
role="system",
content="🔄 **SELF-REFLECTION PHASE**nn"
"Пожалуйста, проверьте свою работу:n"
"1. Все ли шаги выполнены?n"
"2. Нет ли ошибок или пропусков?n"
"3. Если есть проблемы - исправьте ихn"
"4. Если всё верно - вызовите task_completed(verified=true)",
requires_action="reflect"
)
self.state.add_message(reflection_msg)
# Продолжаем цикл для рефлексии
await self._run_cycle(depth=self.state.current_depth + 1)
else:
# ФИНАЛЬНОЕ ЗАВЕРШЕНИЕ
if self.state.drafts:
# Показываем окно ревью
await self._show_review_modal()
print("n✅ Задача завершена!")
print(f"📊 Статистика: {self.state.get_stats()}")
self.is_reflecting = False
def _build_system_prompt(self) -> str:
"""Построение системного промпта"""
return """
ТЫ - Рефлексирующий ИИ-агент. Твоя задача - помогать пользователю,
активно используя доступные инструменты.
ПРАВИЛА:
1. Перед действием объясни, что ты собираешься сделать
2. Используй create_plan для сложных задач
3. Проверяй свою работу перед завершением
4. Не гадай - используй инструменты для получения информации
ДОСТУПНЫЕ ПРИНЦИПЫ:
- Черновики: изменения сохраняются в черновик до подтверждения
- Рефлексия: всегда проверяй свою работу
- Безопасность: не выходи за пределы разрешенных границ
"""
async def _request_confirmation(self, tool_call: Dict) -> None:
"""Запрос подтверждения у пользователя"""
self.state.pending_confirmation = tool_call
print(f"n⚠️ ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ")
print(f"Инструмент: {tool_call['name']}")
print(f"Аргументы: {json.dumps(tool_call['arguments'], indent=2, ensure_ascii=False)}")
# В реальном приложении здесь был бы UI
# Для консоли - ждем ввода
response = input("nПодтвердить? (y/n): ").lower()
if response == 'y':
await self.confirm_action()
else:
await self.reject_action()
async def confirm_action(self) -> None:
"""Подтверждение действия"""
if not self.state.pending_confirmation:
return
tool_call = self.state.pending_confirmation
self.state.pending_confirmation = None
# Выполняем подтвержденное действие
result = await self.tools.execute(
tool_call["name"],
tool_call["arguments"],
is_draft=False # Принудительное применение
)
tool_msg = Message(
role="tool",
content=result,
tool_call_id=tool_call["id"],
name=tool_call["name"]
)
self.state.add_message(tool_msg)
# Продолжаем цикл
await self._run_cycle(self.state.current_depth + 1)
async def reject_action(self) -> None:
"""Отклонение действия"""
if not self.state.pending_confirmation:
return
tool_call = self.state.pending_confirmation
self.state.pending_confirmation = None
tool_msg = Message(
role="tool",
content="Действие отклонено пользователем",
tool_call_id=tool_call["id"],
name=tool_call["name"]
)
self.state.add_message(tool_msg)
await self._run_cycle(self.state.current_depth + 1)
def stop(self) -> None:
"""Остановка агента"""
self._abort_controller = True
async def _handle_error(self, error: Exception, depth: int) -> None:
"""Обработка ошибок с авто-повтором"""
if self.state.retry_count < 2:
self.state.retry_count += 1
delay = 5 # секунд
print(f"⚠️ Ошибка: {error}. Повтор через {delay}с...")
await asyncio.sleep(delay)
# Повтор с той же глубиной
await self._run_cycle(depth)
else:
print(f"❌ Критическая ошибка: {error}")
self.state.add_message(Message(
role="system",
content=f"Ошибка: {error}"
))
def _get_tool_signature(self, tool_call: Dict) -> str:
"""Генерация сигнатуры для детекции циклов"""
content = f"{tool_call['name']}:{json.dumps(tool_call['arguments'], sort_keys=True)}"
return hashlib.md5(content.encode()).hexdigest()
async def _show_review_modal(self) -> None:
"""Показ окна ревью изменений"""
print("n📝 ИЗМЕНЕНИЯ В ЧЕРНОВИКАХ:")
for i, draft in enumerate(self.state.drafts):
print(f"n{i+1}. {draft.file_path}")
print(f" Статус: {draft.status}")
print(f" Размер: {len(draft.new_content)} символов")
response = input("nПрименить все изменения? (y/n): ").lower()
if response == 'y':
await self._commit_drafts()
else:
await self._rollback()
async def _commit_drafts(self) -> None:
"""Применение черновиков"""
for draft in self.state.drafts:
if draft.status == "pending":
await self.tools.apply_draft(draft)
draft.status = "accepted"
print("✅ Изменения применены")
async def _rollback(self) -> None:
"""Откат изменений"""
await self.snapshot_manager.restore_snapshot(self.state)
print("↩️ Изменения отменены")
async def _handle_iteration_limit(self) -> None:
"""Обработка превышения лимита итераций"""
print(f"n⚠️ Достигнут лимит итераций ({self.iteration_limiter.max_iterations})")
response = input("Продолжить? (y/n): ").lower()
if response == 'y':
self.iteration_limiter.reset()
await self._run_cycle(depth=1)
else:
self.stop()
async def _handle_loop_detected(self, tool_call: Dict) -> None:
"""Обработка обнаруженного цикла"""
print(f"n🔄 ОБНАРУЖЕН ЦИКЛ: {tool_call['name']}")
print("Агент повторяет одно и то же действие")
self.stop()
async def _handle_access_denied(self, tool_call: Dict) -> None:
"""Обработка нарушения доступа"""
print(f"n🚫 ДОСТУП ЗАПРЕЩЕН: {tool_call['name']}")
print(f"Попытка выхода за пределы разрешенной зоны")
self.state.add_message(Message(
role="system",
content=f"Ошибка доступа: {tool_call['name']} - выход за границы"
))
def _serialize_message(self, msg: Message) -> Dict:
"""Сериализация сообщения для OpenAI API"""
result = {"role": msg.role}
if msg.content:
result["content"] = msg.content
if msg.tool_calls:
result["tool_calls"] = msg.tool_calls
if msg.tool_call_id:
result["tool_call_id"] = msg.tool_call_id
if msg.name:
result["name"] = msg.name
return result
# tools/base.py
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional
from core.types import ToolRisk
class BaseTool(ABC):
"""Базовый класс для всех инструментов"""
def __init__(self, name: str, description: str, risk: ToolRisk):
self.name = name
self.description = description
self.risk = risk
self.parameters = self.get_parameters()
@abstractmethod
def get_parameters(self) -> Dict[str, Any]:
"""Схема параметров для OpenAI API"""
pass
@abstractmethod
async def execute(self, **kwargs) -> str:
"""Выполнение инструмента"""
pass
class FileSystemTool(BaseTool):
"""Инструменты файловой системы"""
def __init__(self, workspace_root: str):
self.workspace_root = workspace_root
super().__init__(
name="read_file",
description="Read file content",
risk=ToolRisk.SAFE_READ
)
def get_parameters(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path"}
},
"required": ["path"]
}
async def execute(self, path: str, **kwargs) -> str:
# Проверка безопасности пути
full_path = self._safe_path(path)
try:
with open(full_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
return f"Error reading file: {e}"
def _safe_path(self, path: str) -> str:
"""Проверка безопасного пути"""
import os
full = os.path.join(self.workspace_root, path.lstrip('/'))
if not full.startswith(self.workspace_root):
raise PermissionError("Access denied: path outside workspace")
return full
class PatchFileTool(BaseTool):
"""Инструмент патчинга файлов (создает черновик)"""
def __init__(self, draft_manager):
self.draft_manager = draft_manager
super().__init__(
name="patch_file",
description="Patch file content (creates draft)",
risk=ToolRisk.MODIFY
)
def get_parameters(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"path": {"type": "string"},
"search_str": {"type": "string"},
"replace_str": {"type": "string"}
},
"required": ["path", "search_str", "replace_str"]
}
async def execute(self, path: str, search_str: str, replace_str: str, **kwargs) -> str:
# Создаем черновик вместо прямого изменения
draft = await self.draft_manager.create_draft(
path=path,
search_str=search_str,
replace_str=replace_str
)
return f"Draft created: {path}nPreview: {draft.new_content[:200]}..."
class ToolRegistry:
"""Реестр всех доступных инструментов"""
def __init__(self):
self._tools: Dict[str, BaseTool] = {}
def register(self, tool: BaseTool) -> None:
self._tools[tool.name] = tool
async def execute(self, name: str, arguments: Dict[str, Any], is_draft: bool = False) -> str:
if name not in self._tools:
return f"Error: Tool '{name}' not found"
tool = self._tools[name]
# Для модифицирующих операций используем черновик
if tool.risk == ToolRisk.MODIFY and is_draft:
return await tool.execute(**arguments, _is_draft=True)
return await tool.execute(**arguments)
def get_openai_schema(self) -> List[Dict]:
"""Получение схемы для OpenAI API"""
return [
{
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters
}
}
for tool in self._tools.values()
]
def get_risk(self, tool_name: str) -> ToolRisk:
"""Получение уровня риска инструмента"""
if tool_name not in self._tools:
return ToolRisk.SAFE_READ
return self._tools[tool_name].risk
# safety/jail.py
from typing import Dict, Any, List
import re
class ScopeJail:
"""Проверка границ доступа"""
def __init__(self, allowed_paths: List[str] = None, blocked_patterns: List[str] = None):
self.allowed_paths = allowed_paths or ["."]
self.blocked_patterns = blocked_patterns or [
r"../", # Обход директорий
r"/etc/passwd", # Системные файлы
r"rms+-rf", # Опасные команды
r"sudos+", # Повышение привилегий
r"|s*shs", # Инъекции
]
async def check_permissions(self, tool_call: Dict[str, Any]) -> bool:
"""Проверка разрешений для вызова инструмента"""
tool_name = tool_call.get("name")
args = tool_call.get("arguments", {})
# Проверка путей
for path_key in ["path", "oldPath", "newPath", "source", "destination"]:
if path_key in args:
if not self._check_path(args[path_key]):
return False
# Проверка команд
if tool_name == "exec_command":
command = args.get("command", "")
if not self._check_command(command):
return False
return True
def _check_path(self, path: str) -> bool:
"""Проверка безопасности пути"""
# Проверка на паттерны обхода
for pattern in self.blocked_patterns:
if re.search(pattern, path):
return False
return True
def _check_command(self, command: str) -> bool:
"""Проверка безопасности команды"""
dangerous = ["rm -rf", "dd if=", "mkfs", ":(){ :|:& };:"]
for cmd in dangerous:
if cmd in command.lower():
return False
return True
class LoopDetector:
"""Детектор зацикливаний"""
def __init__(self, max_repeats: int = 3, window_size: int = 10):
self.max_repeats = max_repeats
self.window_size = window_size
self._signatures: List[str] = []
def check(self, signature: str) -> bool:
"""Проверка на повторение"""
self._signatures.append(signature)
# Оставляем только последние N
if len(self._signatures) > self.window_size:
self._signatures.pop(0)
# Подсчет повторений
repeats = sum(1 for s in self._signatures if s == signature)
return repeats >= self.max_repeats
def reset(self) -> None:
"""Сброс детектора"""
self._signatures.clear()
class IterationLimiter:
"""Лимитер итераций"""
def __init__(self, max_iterations: int = 15):
self.max_iterations = max_iterations
def can_continue(self, current_depth: int) -> bool:
return current_depth <= self.max_iterations
def reset(self) -> None:
pass
class SnapshotManager:
"""Управление снапшотами и откатами"""
def __init__(self):
self._snapshots: List[Snapshot] = []
async def create_snapshot(self, state: 'AgentState') -> Snapshot:
"""Создание снапшота текущего состояния"""
snapshot = Snapshot(
id=self._generate_id(),
timestamp=datetime.now().timestamp(),
files=state.files.copy(),
messages=state.messages.copy(),
drafts=state.drafts.copy()
)
self._snapshots.append(snapshot)
return snapshot
async def restore_snapshot(self, state: 'AgentState', snapshot_id: str = None) -> bool:
"""Восстановление состояния из снапшота"""
if not self._snapshots:
return False
if snapshot_id:
snapshot = next((s for s in self._snapshots if s.id == snapshot_id), None)
else:
snapshot = self._snapshots[-1] # Последний
if not snapshot:
return False
# Восстановление состояния
state.files = snapshot.files.copy()
state.messages = snapshot.messages.copy()
state.drafts = snapshot.drafts.copy()
return True
def _generate_id(self) -> str:
"""Генерация ID снапшота"""
import uuid
return str(uuid.uuid4())[:8]
# ui/console.py
import asyncio
from core.agent import ReflectiveAgent
class ConsoleUI:
"""Консольный интерфейс для взаимодействия с агентом"""
def __init__(self):
self.agent = None
async def run(self):
"""Запуск консольного интерфейса"""
print("=" * 60)
print("🤖 РЕФЛЕКСИРУЮЩИЙ ИИ-АГЕНТ")
print("=" * 60)
print("nКоманды:")
print(" /stop - остановить агента")
print(" /status - показать статус")
print(" /snapshot - создать снапшот")
print(" /rollback - откат к последнему снапшоту")
print(" /help - показать помощь")
print("n" + "=" * 60)
# Инициализация агента
api_key = input("n🔑 OpenAI API Key: ").strip()
self.agent = ReflectiveAgent(
model="gpt-4",
api_key=api_key,
max_iterations=15,
require_confirmation=True
)
print("n✅ Агент готов к работе!")
print("Введите ваш запрос:n")
while True:
try:
user_input = input("👤 Вы: ").strip()
if not user_input:
continue
if user_input.startswith("/"):
await self._handle_command(user_input)
continue
print("n🤖 Агент думает...n")
# Запуск агента
await self.agent.run(user_input)
print("n" + "-" * 40 + "n")
except KeyboardInterrupt:
print("nn👋 До свидания!")
break
except Exception as e:
print(f"n❌ Ошибка: {e}n")
async def _handle_command(self, command: str):
"""Обработка команд"""
if command == "/stop":
self.agent.stop()
print("⏹️ Агент остановлен")
elif command == "/status":
print(f"n📊 Статус:")
print(f" Сообщений: {len(self.agent.state.messages)}")
print(f" Черновиков: {len(self.agent.state.drafts)}")
print(f" Итераций: {self.agent.state.current_depth}")
elif command == "/snapshot":
snapshot = await self.agent.snapshot_manager.create_snapshot(self.agent.state)
print(f"📸 Снапшот создан: {snapshot.id}")
elif command == "/rollback":
await self.agent.snapshot_manager.restore_snapshot(self.agent.state)
print("↩️ Выполнен откат к последнему снапшоту")
elif command == "/help":
print("nДоступные команды:")
print(" /stop - остановить агента")
print(" /status - показать статус")
print(" /snapshot - создать снапшот")
print(" /rollback - откат к последнему снапшоту")
print(" /help - показать помощь")
else:
print(f"❌ Неизвестная команда: {command}")
async def main():
ui = ConsoleUI()
await ui.run()
if __name__ == "__main__":
asyncio.run(main())
# main.py
import asyncio
from ui.console import ConsoleUI
from core.agent import ReflectiveAgent
from tools.filesystem import FileSystemTool, PatchFileTool
from tools.terminal import TerminalTool
from tools.mcp_adapter import MCPAdapter
async def main():
"""Точка входа в приложение"""
# Запуск консольного интерфейса
ui = ConsoleUI()
await ui.run()
async def demo():
"""Демонстрация работы агента без UI"""
agent = ReflectiveAgent(
model="gpt-4",
api_key="your-api-key", # Замените на ваш ключ
max_iterations=10
)
# Регистрация инструментов
from tools.filesystem import FileSystemTool, PatchFileTool
from tools.terminal import TerminalTool
agent.tools.register(FileSystemTool(workspace_root="./workspace"))
agent.tools.register(PatchFileTool(agent.state.draft_manager))
agent.tools.register(TerminalTool())
# Запросы пользователя
queries = [
"Прочитай файл README.md",
"Создай новый Python файл с функцией hello_world",
"Какой сейчас Python version?",
]
for query in queries:
print(f"n{'='*60}")
print(f"👤 Пользователь: {query}")
print(f"{'='*60}n")
await agent.run(query)
print("n" + "="*60 + "n")
if __name__ == "__main__":
# Для демонстрации
# asyncio.run(demo())
# Для интерактивного режима
asyncio.run(main())
# requirements.txt
openai>=1.0.0
asyncio
pydantic>=2.0.0
python-dotenv>=1.0.0
pip install -r requirements.txt
Рекурсивный цикл – асинхронная рекурсия вместо while
Черновики – все изменения проходят через DraftManager
Рефлексия – принудительная самопроверка перед завершением
Безопасность – многоуровневые проверки (Jail, детектор циклов)
Снапшоты – возможность отката любого изменения
Human-in-the-loop – подтверждение опасных действий
Большинство агентов живут по схеме think → do. Это быстро, но хрупко. Достаточно одной галлюцинации, неоднозначного промпта или непредвиденного состояния среды — и агент молча отправляет не тому клиенту письмо, ломает интеграцию, генерирует некорректный отчёт или уходит в бесконечный цикл запросов, сжигая бюджет.
Универсальная архитектура заменяет эту схему на семиступенчатый цикл:
Восприятие → Планирование → Черновик → Рефлексия → Шлюз → Коммит → Откат
Это не замедляет работу. Это делает её предсказуемой, аудируемой и безопасной для продакшена.
Агент не действует вслепую. Перед каждым шагом он собирает актуальное состояние среды: какие данные доступны, какие инструменты подключены, какие политики и ограничения действуют, что уже было сделано в текущей сессии. Это как пилот, проверяющий приборы перед манёвром.
Вместо того чтобы запихивать в промпт мегабайты данных, архитектура собирает динамический контекст:
|
Компонент |
Что содержит |
Аналогия |
|---|---|---|
|
Дерево доступных объектов |
Файлы, таблицы, документы, API-эндпоинты — в виде структуры, а не содержимого |
Карта местности |
|
Загруженные сущности |
Полное содержимое только того, с чем агент работает прямо сейчас |
Открытые вкладки |
|
Границы области (Scope) |
Чёткий периметр, за который нельзя выходить |
Красная лента на месте происшествия |
|
Подключённые расширения |
Список доступных инструментов с сигнатурами |
Пояс с инструментами |
|
История взаимодействия |
Полная цепочка действий и результатов в рамках сессии |
Оперативная память [4] |
Это даёт агенту не «всезнание», а ситуационную осведомлённость — ровно то, что нужно для принятия решений в конкретный момент. Контекст формируется заново на каждой итерации, чтобы избежать работы с устаревшими допущениями.
Вместо жёсткого while(true) используется асинхронная рекурсия. Почему это важно? Потому что ИИ-агент — не конвейер, а диалог.
Агент может остановиться, запросить уточнение, дождаться ответа человека, внешнего вебхука или результата долгой операции, и продолжить ровно с того же места. История диалога, состояние инструментов и бюджет вычислений сохраняются. Пауза не ломает процесс — она встроена в его ДНК. Человек остаётся в контуре, а не выпадает из него.
Одно из самых мощных архитектурных решений — концепция черновиков. Все модифицирующие операции не применяются к реальным данным сразу. Вместо этого:
Создаётся виртуальный слой изменений в памяти.
Все последующие чтения внутри сессии видят уже изменённые данные (чтобы агент работал с актуальным состоянием).
Реальные объекты остаются нетронутыми.
Пользователь видит окно ревью: дифф всех изменений, возможность принять, отклонить или отредактировать.
Только после явного «Сохранить» черновики атомарно применяются.
Это принцип git staging, перенесённый на любые действия. Будь то правка документа, обновление записи в CRM, отправка сообщения, запуск ETL-пайплайна или изменение конфигурации — система фиксирует «что было» и «что станет», но не трогает продакшен-среду. Черновики превращают агента из «исполнителя» в «советника», оставляя финальное слово за человеком.
Это ключевой архитектурный блок, который отличает «думающего» агента от «генерирующего». Когда агент считает задачу выполненной, система не принимает это на веру.
Включается фаза принудительной рефлексии — внутренний аудит:
Соответствует ли результат исходной цели?
Нет ли логических противоречий или пропущенных шагов?
Соблюдены ли бизнес-правила, форматы, ограничения?
Можно ли запустить сухую проверку или тест?
Агент пересматривает свои действия, перечитывает изменённые объекты, запрашивает недостающий контекст. Если находит проблемы — исправляет их через те же инструменты. Только после явного подтверждения (verified: true) задача считается завершённой.
Это аналог чек-листа пилота перед взлётом: лучше потратить лишний шаг на проверку, чем получить критическую ошибку в продакшене. В юридическом домене это означало бы перепроверку ссылок на статьи закона. В медицинском — перекрёстный анализ назначений с противопоказаниями. В финансовом — сверку расчётов с контрольными суммами. Рефлексия превращает агента из «генератора текста» в ответственного исполнителя.
Все действия делятся на три категории риска:
🟢 Безопасные чтения — выполняются автоматически
🟡 Изменения — создают черновики, не затрагивая оригинал
🔴 Деструктивные операции — требуют явного подтверждения
Удаление данных, финансовые транзакции, публикация контента, изменение ролей, массовые рассылки, перезапуск сервисов — всё, что необратимо или влияет на внешнюю среду, требует паузы. Интерфейс показывает карточку с контекстом, рисками, альтернативами и превью изменений. Агент замирает, ждёт решения и продолжает работу только после «зелёного света».
Это не чат с кнопками, а полноценный паттерн Human-in-the-Loop: человек остаётся архитектором последствий, а скорость агента не превращается в безрассудство.
Защита не может быть фильтром на выходе. Она должна быть частью цикла выполнения. Каждое действие агента проходит через независимые предохранители, встроенные в исполнительный слой:
|
Механизм |
Что делает |
Аналогия |
|---|---|---|
|
Scope Jail |
Блокирует выход за пределы разрешённых ресурсов, ролей, политик |
Забор вокруг стройплощадки |
|
Детектор зацикливаний |
3 одинаковых действия подряд с одной сигнатурой → остановка с диагностикой |
Автоматический выключатель |
|
Лимиты итераций и стоимости |
Жёсткие потолки на шаги и токены с предложением «продолжить?» |
Таймер и счётчик бюджета |
|
Снапшоты и откат |
Точка восстановления перед каждой сессией. Ошибка? Откат в один клик |
Кнопка «Отменить всё» |
|
Abort-контроллер |
Мгновенная остановка по сигналу пользователя или системы |
Аварийный тормоз |
Эти механизмы работают не «поверх» агента, а встроены в уровень вызова инструментов. Модель не может их обойти, потому что проверка происходит на уровне исполнения, а не генерации текста. Агент не может «договориться» с системой обойти защиту.
Архитектура не привязана к конкретному домену. Через стандартизированный интерфейс (аналог Model Context Protocol) подключаются любые сервисы: базы данных, CRM, аналитические платформы, браузеры, почта, IoT-устройства, внешние API.
Это как USB-C для ИИ-агентов:
|
Домен |
Что подключается через MCP |
|---|---|
|
Разработка |
Файловая система, терминал, Git, линтеры |
|
Право |
Базы законов, конструкторы договоров, проверка контрагентов |
|
Медицина |
Медицинские справочники, системы расшифровки анализов |
|
Финансы |
Платёжные шлюзы, CRM, системы бюджетирования |
|
Маркетинг |
Платформы рассылок, аналитика, CMS |
|
Образование |
Базы знаний, системы проверки заданий |
Агент видит инструменты как единый набор «рук», а система безопасности проверяет каждый вызов независимо от источника. Сегодня агент работает с таблицами, завтра — с чат-ботами, послезавтра — с логистическими системами. Ядро не меняется.
|
Домен |
Пример цикла агента |
|---|---|
|
Аналитика данных |
Формирует запрос → запускает в сухом режиме → проверяет структуру и объём выборки → показывает превью → после одобрения экспортирует отчёт и фиксирует метаданные |
|
Маркетинг и коммуникации |
Генерирует черновик рассылки → проверяет тон, персонализацию, соответствие регламенту → ждёт одобрения менеджера → отправляет → логирует результат |
|
Исследования и синтез |
Собирает источники → проверяет пересечения и достоверность → формирует черновик обзора → запускает самопроверку на логические разрывы → предлагает финальную версию с цитатами |
|
Управление процессами |
Читает задачу из трекера → распределяет подзадачи → проверяет доступность исполнителей → создаёт черновик плана → после утверждения создаёт тикеты и ставит напоминания |
|
Поддержка клиентов |
Анализирует тикет → ищет похожие кейсы в базе → формирует ответ-черновик → проверяет на соответствие SLA и тону бренда → предлагает оператору → отправляет после клика |
|
Юриспруденция |
Анализирует документ → выявляет риски по секциям → сверяет выводы с судебной практикой → формирует черновик заключения → юрист проверяет и утверждает |
Во всех сценариях сохраняется один паттерн: агент думает, примеряет, проверяет, спрашивает и только потом действует.
Рассмотрим универсальный пример — анализ документа с рисками:
ПОЛЬЗОВАТЕЛЬ: "Проанализируй договор аренды на предмет рисков"
↓
🔵 ВОСПРИЯТИЕ: Агент подгружает файл, видит его структуру и метаданные
↓
🟡 ПЛАНИРОВАНИЕ: Определяет, что это договор аренды. Планирует анализ по секциям:
предмет, сроки, ответственность, расторжение. Загружает релевантные статьи закона.
↓
┌─→🔴 ДЕЙСТВИЕ: Читает секцию «Ответственность», формирует оценку неустойки
│ ↓
│ 🟣 РЕФЛЕКСИЯ: Находит пункт с завышенной неустойкой.
│ Сверяет с актуальной судебной практикой. Фиксирует риск в черновик.
│ ↓
└───Продолжает цикл для остальных секций
↓
🟣 ФИНАЛЬНАЯ РЕФЛЕКСИЯ: Перепроверяет все выводы, ищет упущенные риски,
проверяет, не противоречат ли выводы по разным секциям друг другу
↓
🔴 ШЛЮЗ: Окно ревью — «Найдено 12 рисков, 3 критические ошибки. Применить правки?»
Пользователь видит дифф по каждому пункту, может отредактировать или отклонить
↓
ПОЛЬЗОВАТЕЛЬ: Подтверждает
↓
✅ КОММИТ: Черновики атомарно применены к документу. Снапшот сохранён.
Здесь нет спешки. Каждый шаг осознан, проверен, подтверждён. Агент работает не как «быстрый генератор», а как внимательный аналитик.
Доверие строится на архитектуре, а не на обещаниях. Черновики, снапшоты и шлюзы делают ошибки обратимыми. Стоимость сбоя стремится к нулю. Разработчик или бизнес-пользователь больше не боится запустить агента на рабочем проекте, потому что до явного подтверждения ни одно действие не покинет песочницу.
ИИ становится коллегой, а не чёрным ящиком. Каждый шаг прозрачен, логируем и объясним. Человек видит не только результат, но и процесс. Вы не гадаете, что сделал ИИ — вы видите каждый шаг и держите руку на пульсе.
Безопасность встроена, а не добавлена. Предохранители работают на уровне исполнения, а не промпта. Модель не может «договориться» с системой обойти защиту, потому что проверка происходит после генерации, на уровне вызова инструментов.
Масштабируемость без страха. Один и тот же цикл разворачивается в коде, данных, текстах, процессах и интеграциях. Меняются инструменты — не меняется логика [5]. Универсальный протокол инструментов позволяет агенту работать с чем угодно, не теряя в безопасности.
Экономика под контролем. Лимиты итераций, детектор циклов и паузы на подтверждение предотвращают runaway-сценарии и неконтролируемые расходы токенов. Бюджет расходуется осмысленно, а не сжигается в бесконечной «мыслительной жвачке».
Текущая архитектура закладывает фундамент. Следующие шаги эволюции:
Коллективная рефлексия: несколько агентов проверяют работу друг друга — как код-ревью, только для любых решений.
Предиктивная рефлексия: агент предсказывает, что может пойти не так, до выполнения действия, а не после.
Обучение [6] на ошибках: паттерны неудач сохраняются в «память агента» и учитываются в будущих задачах.
Этическая рефлексия: проверка решений на соответствие не только целям, но и этическим нормам и регуляторным требованиям.
Мы привыкли измерять прогресс ИИ скоростью. Но настоящий прорыв происходит, когда мы учим машины не торопиться. Когда между стимулом [7] и реакцией [8] появляется пауза — на размышление, проверку, сомнение.
Гонка за «полной автономностью» часто игнорирует главный принцип инженерии: надёжность важнее скорости. Агент, который работает быстро, но ломает процессы, бесполезен. Агент, который работает чуть медленнее, но предсказуемо, проверяет себя и уважает границы, становится частью рабочего потока.
Универсальная архитектура рефлексирующего агента — это не про то, чтобы сделать ИИ «безопаснее» как маркетинговый слоган. Это про другой класс агентов: тех, кто работает не вместо человека, а вместе с ним. Кто не скрывает свои действия, а делает их прозрачными. Кто не боится перепроверить себя и признать ошибку. Кто берёт на себя рутину, синтез и исполнение, но оставляет за человеком право вето, контекст и финальное слово.
Именно такие агенты выйдут из песочниц в реальный мир. В больницы, суды, банки, фабрики. Не потому что они быстрее. А потому что им можно доверять.
Самый умный агент — не тот, который делает всё сам. А тот, который умеет останавливаться, проверять себя и спрашивать: «Я всё правильно понял?»
Если вам интересны технические детали реализации (управление токенами, fuzzy-поиск, интеграция MCP, логика рекурсивного цикла) или хотите увидеть схему развёртывания для конкретного домена — дайте знать, разберём любой компонент под микроскопом.
Автор: TAU15
Источник [9]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/29669
URLs in this post:
[1] интеллект: http://www.braintools.ru/article/7605
[2] рефлекс: http://www.braintools.ru/article/9352
[3] ошибки: http://www.braintools.ru/article/4192
[4] память: http://www.braintools.ru/article/4140
[5] логика: http://www.braintools.ru/article/7640
[6] Обучение: http://www.braintools.ru/article/5125
[7] стимулом: http://www.braintools.ru/article/5596
[8] реакцией: http://www.braintools.ru/article/1549
[9] Источник: https://habr.com/ru/articles/1030196/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1030196
Нажмите здесь для печати.