Типичная ошибка: когда LLM становится богом архитектуры
Откройте любой туториал по созданию AI-агентов на 2026 год. Что вы увидите? Классическую структуру: пользовательский запрос → промпт-инженерия → вызов LLM → парсинг ответа → выполнение действия. LLM в центре всего. Она решает, что делать. Она думает. Она управляет.
И это работает. Пока не начинает ломаться.
Проблема в том, что LLM по своей природе стохастична. Каждый вызов — это лотерея. Даже с temperature=0 вы получаете вариативность. Агент с LLM в центре становится нестабильным, непредсказуемым, неконтролируемым.
Представьте себе самолет, где автопилот на каждом шагу спрашивает у нейросети: "А куда мне теперь лететь?" Звучит как кошмар. Но именно так мы строим большинство AI-агентов.
Парадокс: чем умнее модель, тем глупее архитектура
Современные модели — GPT-5 (если верить утечкам на 2026 год), Claude 4, Gemini Ultra 2 — стали невероятно мощными. Они понимают контекст, рассуждают, решают сложные задачи. И мы, разработчики, в ответ... отдаем им все больше контроля.
"Модель умная, пусть сама решает" — говорим мы. И получаем систему, где:
- Невозможно гарантировать детерминизм
- Сложно дебажить (почему агент принял именно это решение?)
- Нет четкой бизнес-логики (все смешано в промптах)
- Агент страдает от проблемы "Молчаливого ученого"
Помните ту статью про эпистемическую асимметрию? Там мы говорили о том, что агенты знают больше, чем выражают. Эта проблема усугубляется, когда LLM управляет всем процессом. Модель видит только то, что ей показали в контексте. Она не помнит прошлые решения. Не строит долгосрочные стратегии.
Субъектный подход: агент как stateful сущность
А что если перевернуть архитектуру с ног на голову? Сделать так, чтобы агент был stateful сущностью с собственной памятью, логикой и целями. А LLM стала всего лишь одним из инструментов в его арсенале.
Три ключевых принципа:
- Stateful ядро: агент хранит историю, контекст, цели
- Логический слой: детерминированная бизнес-логика вне LLM
- Инверсия управления: агент решает, когда и как использовать LLM
1 Stateful ядро: память важнее интеллекта
Типичная ошибка — передавать всю историю диалога в контекст LLM. Это дорого, неэффективно и ограничено размером контекстного окна. Вместо этого агент должен сам управлять своей памятью.
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
from datetime import datetime
@dataclass
class AgentMemory:
"""Ядро памяти агента"""
conversation_history: List[Dict[str, Any]] = field(default_factory=list)
knowledge_base: Dict[str, Any] = field(default_factory=dict)
goals: List[str] = field(default_factory=list)
current_context: Dict[str, Any] = field(default_factory=dict)
def add_interaction(self, user_input: str, agent_response: str,
metadata: Optional[Dict] = None):
"""Сохраняем взаимодействие со сжатием"""
entry = {
'timestamp': datetime.now().isoformat(),
'user': user_input,
'agent': agent_response,
'metadata': metadata or {}
}
# Сжимаем историю, оставляя только важное
if len(self.conversation_history) > 50:
self._compress_history()
self.conversation_history.append(entry)
def _compress_history(self):
"""Интеллектуальное сжатие: оставляем только ключевые моменты"""
# Здесь могла бы быть логика сжатия через LLM,
# но вызываемая по решению агента, а не автоматически
pass
def get_relevant_context(self, query: str, limit: int = 10) -> List[Dict]:
"""Получаем релевантный контекст для запроса"""
# Простая реализация - в продакшене здесь был бы векторный поиск
return self.conversation_history[-limit:]
@dataclass
class AgentState:
"""Полное состояние агента"""
memory: AgentMemory
current_task: Optional[str] = None
task_progress: float = 0.0
available_tools: List[str] = field(default_factory=list)
constraints: Dict[str, Any] = field(default_factory=dict)
def update_progress(self, progress: float):
self.task_progress = max(0.0, min(1.0, progress))
def is_task_complete(self) -> bool:
return self.task_progress >= 1.0
Обратите внимание: память — это не просто список сообщений. Это структурированные данные с методами для работы. Агент сам решает, что сохранять, как сжимать, что забывать.
2 Логический слой: где живет бизнес-логика
Это самая важная часть. Логический слой — это детерминированный код, который принимает решения на основе состояния агента. Не LLM. Код.
Предупреждение: многие пытаются запихнуть бизнес-логику в промпты. "Если пользователь спрашивает про X, то делай Y". Это работает до тех пор, пока промпт не забудется, не перезатрется или LLM не решит интерпретировать его по-своему.
class LogicLayer:
"""Детерминированный слой принятия решений"""
def __init__(self, state: AgentState):
self.state = state
def decide_next_action(self, user_input: str) -> Dict[str, Any]:
"""Основная логика принятия решений"""
# 1. Анализ состояния
if self.state.is_task_complete():
return {
'action': 'task_complete',
'next_step': 'ask_for_next_task'
}
# 2. Определение типа запроса
query_type = self._classify_query(user_input)
# 3. Принятие решения на основе типа и состояния
if query_type == 'information_request':
return self._handle_information_request(user_input)
elif query_type == 'task_execution':
return self._handle_task_execution(user_input)
elif query_type == 'clarification':
return self._handle_clarification(user_input)
else:
return {
'action': 'use_llm',
'reason': 'unclassified_query',
'prompt_template': 'general_assistance'
}
def _classify_query(self, query: str) -> str:
"""Классификация запроса без LLM"""
# Простые эвристики - в реальной системе здесь мог бы быть
# классификатор на ML или правила
query_lower = query.lower()
information_keywords = ['что', 'как', 'почему', 'расскажи', 'объясни']
task_keywords = ['сделай', 'найди', 'создай', 'отправь', 'запиши']
if any(keyword in query_lower for keyword in information_keywords):
return 'information_request'
elif any(keyword in query_lower for keyword in task_keywords):
return 'task_execution'
elif '?' in query:
return 'clarification'
else:
return 'unknown'
def _handle_information_request(self, query: str) -> Dict:
"""Обработка запроса информации"""
# Проверяем, есть ли ответ в knowledge base
if self._has_cached_answer(query):
return {
'action': 'return_cached',
'data': self._get_cached_answer(query)
}
# Если нет - используем LLM
return {
'action': 'use_llm',
'reason': 'information_gap',
'prompt_template': 'information_extraction',
'context': self.state.memory.get_relevant_context(query)
}
def _has_cached_answer(self, query: str) -> bool:
"""Проверка кэшированных ответов"""
# Упрощенная реализация
return False
Логический слой принимает решения на основе четких правил. LLM вызывается только тогда, когда это действительно нужно. И вызывается с конкретной задачей: "ответь на вопрос про X", а не "что делать дальше?".
3 Deliberate Query: осознанный вызов LLM
Вот где происходит магия. Вместо того чтобы каждый раз спрашивать у LLM "что делать", агент сам формирует точный запрос к модели.
class LLMService:
"""Сервис для работы с LLM"""
def __init__(self, model_name: str = "gpt-4"):
self.model_name = model_name
self.prompt_templates = self._load_templates()
def execute_query(self, query_type: str, context: Dict,
options: Optional[Dict] = None) -> str:
"""Выполняет запрос к LLM"""
# 1. Выбираем шаблон промпта
template = self.prompt_templates.get(query_type,
self.prompt_templates['default'])
# 2. Формируем промпт
prompt = self._render_template(template, context, options)
# 3. Вызываем LLM (упрощенно)
response = self._call_llm_api(prompt)
# 4. Валидируем и парсим ответ
validated_response = self._validate_response(response, query_type)
return validated_response
def _load_templates(self) -> Dict[str, str]:
"""Загружаем промпт-шаблоны"""
return {
'information_extraction': """
Ты — помощник по извлечению информации.
Контекст диалога: {context}
Вопрос пользователя: {question}
Ответь максимально точно и кратко. Если не знаешь — скажи "не знаю".
""",
'task_planning': """
Ты — планировщик задач.
Текущее состояние: {state}
Задача: {task}
Разбей задачу на шаги. Верни JSON: {{"steps": [{{"description": "...", "tools": [...]}}]}}
""",
'default': """Ответь на вопрос: {question}"""
}
def _render_template(self, template: str, context: Dict,
options: Optional[Dict]) -> str:
"""Рендерим промпт с контекстом"""
# Упрощенная реализация
return template.format(**context)
def _call_llm_api(self, prompt: str) -> str:
"""Вызов LLM API"""
# Здесь реальный вызов к OpenAI, Anthropic и т.д.
return "Mock response"
def _validate_response(self, response: str, query_type: str) -> str:
"""Валидация ответа LLM"""
# Проверяем формат, длину, содержание
if not response or len(response) > 10000:
return "Ошибка: неверный формат ответа"
return response
Ключевое отличие: LLMService не знает о состоянии агента. Он получает конкретную задачу и контекст. Он не решает, что делать дальше. Он просто выполняет.
Собираем все вместе: архитектура субъектного агента
class SubjectiveAgent:
"""Агент с субъектным подходом"""
def __init__(self, name: str, capabilities: List[str]):
self.name = name
self.state = AgentState(
memory=AgentMemory(),
available_tools=capabilities
)
self.logic = LogicLayer(self.state)
self.llm_service = LLMService()
def process_input(self, user_input: str) -> str:
"""Основной цикл обработки"""
# 1. Логический слой решает, что делать
decision = self.logic.decide_next_action(user_input)
# 2. Выполняем решение
if decision['action'] == 'use_llm':
# Осознанный вызов LLM
response = self.llm_service.execute_query(
query_type=decision.get('prompt_template', 'default'),
context={
'question': user_input,
'context': decision.get('context', []),
'state': self._get_state_summary()
}
)
# 3. Сохраняем в память
self.state.memory.add_interaction(
user_input=user_input,
agent_response=response,
metadata={'decision': decision}
)
return response
elif decision['action'] == 'return_cached':
# Возвращаем кэшированный ответ
return decision['data']
elif decision['action'] == 'execute_tool':
# Выполняем инструмент
return self._execute_tool(decision['tool'], decision['params'])
else:
# Запасной вариант
return "Не могу обработать запрос"
def _get_state_summary(self) -> Dict:
"""Сводка состояния для LLM"""
return {
'current_task': self.state.current_task,
'progress': self.state.task_progress,
'goals': self.state.memory.goals
}
def _execute_tool(self, tool_name: str, params: Dict) -> str:
"""Выполнение инструмента"""
# Реализация инструментов
return f"Выполнено: {tool_name} с параметрами {params}"
Эмерджентная рефлексия: когда агент сам себя улучшает
Субъектный подход открывает интересную возможность: агент может рефлексировать над своими действиями и улучшать собственную логику.
class ReflectiveAgent(SubjectiveAgent):
"""Агент с рефлексией"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.reflection_history = []
def process_input(self, user_input: str) -> str:
response = super().process_input(user_input)
# Периодическая рефлексия
if len(self.state.memory.conversation_history) % 10 == 0:
self._trigger_reflection()
return response
def _trigger_reflection(self):
"""Запуск процесса рефлексии"""
# Анализируем последние взаимодействия
recent_interactions = self.state.memory.conversation_history[-5:]
# Используем LLM для анализа (но по решению агента!)
reflection_prompt = self._build_reflection_prompt(recent_interactions)
analysis = self.llm_service.execute_query(
query_type='reflection',
context={'interactions': recent_interactions}
)
# Агент решает, применять ли insights
if self._should_apply_insights(analysis):
self._update_logic_based_on_insights(analysis)
self.reflection_history.append({
'timestamp': datetime.now().isoformat(),
'analysis': analysis
})
def _build_reflection_prompt(self, interactions: List) -> str:
"""Строим промпт для рефлексии"""
return f"""
Проанализируй последние взаимодействия агента:
{interactions}
Какие паттерны ты видишь?
Что можно улучшить в логике принятия решений?
Предложи конкретные изменения.
"""
def _should_apply_insights(self, analysis: str) -> bool:
"""Решает, применять ли insights"""
# Простая эвристика - в реальности здесь могла бы быть
# более сложная логика
return 'улучшить' in analysis.lower() and 'логику' in analysis.lower()
def _update_logic_based_on_insights(self, analysis: str):
"""Обновляет логический слой"""
# Здесь могла бы быть логика обновления правил
# или даже генерации нового кода
print(f"[Reflection] Applying insights: {analysis[:100]}...")
Агент теперь не просто выполняет команды. Он учится. Анализирует свои ошибки. Улучшает собственную логику. Но делает это осознанно, через свой логический слой, а не хаотично.
Где этот подход ломается (и как это чинить)
| Проблема | Причина | Решение |
|---|---|---|
| Сложность логического слоя | Ручное кодирование всех правил | Используйте DSL для правил или генерацию кода через LLM (но с валидацией!) |
| Жесткость решений | Детерминированные правила негибкие | Добавьте вероятностные правила или fallback к LLM |
| Сложность дебага | Много слоев, состояние распределено | Детальное логирование всех решений и состояния |
| Производительность | Много вызовов LLM для простых решений | Кэширование, батчинг, прекомпиляция правил |
Когда НЕ использовать субъектный подход
Да, есть случаи, когда классическая архитектура с LLM в центре работает лучше:
- Быстрые прототипы: нужно сделать за день-два, а не за неделю
- Исследовательские задачи: когда вы не знаете, какие правила понадобятся
- Творческие агенты: писатели, художники, где нужна максимальная креативность
- Очень простые задачи: если агент делает одну вещь, не усложняйте
Но для production-систем, где нужна стабильность, предсказуемость и контроль — субъектный подход становится must-have.
Что дальше? Эволюция архитектур
На 2026 год мы видим тренд: агенты становятся сложнее, но архитектуры — проще. Не в смысле "примитивнее", а в смысле "более понятные".
Субъектный подход — это шаг к тому, что я называю "архитектурой System 2" (да, отсылка к статьи про System 2). Агент с медленным, осознанным мышлением. С рефлексией. С памятью.
Что будет через год? Два? Возможно, мы увидим:
- Стандартизацию интерфейсов между логическим слоем и LLM-сервисами
- Языки описания логики (DSL) специально для агентов
- Автоматическую оптимизацию логического слоя на основе метрик
- Гибридные подходы, где часть логики генерируется LLM, но валидируется и компилируется
Ключевой инсайт: LLM — это инструмент, а не архитектура. Относитесь к ней как к мощному, но капризному сотруднику. Давайте ей четкие задачи. Контролируйте результаты. И никогда не отдавайте ей бразды правления.
Попробуйте переделать одного из своих агентов по этой схеме. Начните с простого: вынесите классификацию запросов из промптов в код. Добавьте stateful память. Посмотрите, как изменится поведение.
Вы удивитесь, насколько стабильнее и предсказуемее станет ваш агент. И насколько проще будет его развивать.