Субъектный подход в агентах: убираем LLM из центра архитектуры | Stateful агенты | AiManual
AiManual Logo Ai / Manual.
21 Янв 2026 Гайд

LLM как подчиненный: субъектный подход в архитектуре агентов

Продвинутый архитектурный паттерн: stateful ядро, логический слой и инверсия управления LLM. Решаем проблему стохастики моделей через субъектный подход.

Типичная ошибка: когда LLM становится богом архитектуры

Откройте любой туториал по созданию AI-агентов на 2026 год. Что вы увидите? Классическую структуру: пользовательский запрос → промпт-инженерия → вызов LLM → парсинг ответа → выполнение действия. LLM в центре всего. Она решает, что делать. Она думает. Она управляет.

И это работает. Пока не начинает ломаться.

Проблема в том, что LLM по своей природе стохастична. Каждый вызов — это лотерея. Даже с temperature=0 вы получаете вариативность. Агент с LLM в центре становится нестабильным, непредсказуемым, неконтролируемым.

Представьте себе самолет, где автопилот на каждом шагу спрашивает у нейросети: "А куда мне теперь лететь?" Звучит как кошмар. Но именно так мы строим большинство AI-агентов.

Парадокс: чем умнее модель, тем глупее архитектура

Современные модели — GPT-5 (если верить утечкам на 2026 год), Claude 4, Gemini Ultra 2 — стали невероятно мощными. Они понимают контекст, рассуждают, решают сложные задачи. И мы, разработчики, в ответ... отдаем им все больше контроля.

"Модель умная, пусть сама решает" — говорим мы. И получаем систему, где:

  • Невозможно гарантировать детерминизм
  • Сложно дебажить (почему агент принял именно это решение?)
  • Нет четкой бизнес-логики (все смешано в промптах)
  • Агент страдает от проблемы "Молчаливого ученого"

Помните ту статью про эпистемическую асимметрию? Там мы говорили о том, что агенты знают больше, чем выражают. Эта проблема усугубляется, когда LLM управляет всем процессом. Модель видит только то, что ей показали в контексте. Она не помнит прошлые решения. Не строит долгосрочные стратегии.

Субъектный подход: агент как stateful сущность

А что если перевернуть архитектуру с ног на голову? Сделать так, чтобы агент был stateful сущностью с собственной памятью, логикой и целями. А LLM стала всего лишь одним из инструментов в его арсенале.

💡
Субъектный подход — это когда агент имеет собственное состояние (state), внутреннюю логику принятия решений и использует LLM как сервис для решения конкретных подзадач, а не как центральный процессор.

Три ключевых принципа:

  1. Stateful ядро: агент хранит историю, контекст, цели
  2. Логический слой: детерминированная бизнес-логика вне LLM
  3. Инверсия управления: агент решает, когда и как использовать 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). Агент с медленным, осознанным мышлением. С рефлексией. С памятью.

Что будет через год? Два? Возможно, мы увидим:

  1. Стандартизацию интерфейсов между логическим слоем и LLM-сервисами
  2. Языки описания логики (DSL) специально для агентов
  3. Автоматическую оптимизацию логического слоя на основе метрик
  4. Гибридные подходы, где часть логики генерируется LLM, но валидируется и компилируется

Ключевой инсайт: LLM — это инструмент, а не архитектура. Относитесь к ней как к мощному, но капризному сотруднику. Давайте ей четкие задачи. Контролируйте результаты. И никогда не отдавайте ей бразды правления.

Попробуйте переделать одного из своих агентов по этой схеме. Начните с простого: вынесите классификацию запросов из промптов в код. Добавьте stateful память. Посмотрите, как изменится поведение.

Вы удивитесь, насколько стабильнее и предсказуемее станет ваш агент. И насколько проще будет его развивать.