Архитектура AI-агента

Из чего состоит агент, как работает цикл принятия решений и почему оркестрация — это главный навык.

← AI-агенты · Паттерны → · 🇬🇧 English


Содержание

  1. Компоненты агента
  2. Цикл агента (Agent Loop)
  3. State и память
  4. Контекстное окно
  5. Оркестрация
  6. Что дальше

1. Компоненты агента

Любой AI-агент, независимо от сложности, состоит из четырёх обязательных компонентов:

┌──────────────────────────────────────────────────────────┐
│                       AI-АГЕНТ                            │
│                                                          │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────┐  │
│  │     LLM      │  │  Инструменты  │  │  Оркестратор   │  │
│  │  (мозг)      │  │  (руки)      │  │  (управление)   │  │
│  └──────┬───────┘  └──────┬───────┘  └───────┬─────────┘  │
│         │                 │                   │           │
│         ▼                 ▼                   ▼           │
│  Принимает          Вызывает функции,    Управляет        │
│  решения            API, читает файлы,   циклом,          │
│                     ищет в интернете     state, память    │
│                                                          │
│  ┌──────────────────────────────────────────────────┐    │
│  │                 Память (state)                    │    │
│  │  История шагов · Контекст · Результаты           │    │
│  └──────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────┘

1.1 LLM — модель принятия решений

Модель — это «мозг» агента. Она не генерирует ответ пользователю напрямую — она решает, что делать дальше.

У агента модель выполняет три роли:

Роль Что делает Пример решения
Reasoner Анализирует ситуацию и планирует «Пользователь спросил про погоду. Нужно вызвать API погоды»
Decider Выбирает следующий шаг «У меня есть результат из API — можно ответить»
Generator Формулирует финальный ответ «Сейчас в Токио +22°C, ясно»

Для локальных агентов используются те же модели, что и для чата, но есть нюансы:

Как настроить model для агента: ollama-for-agents.md

1.2 Инструменты (Tools)

Инструменты — это функции, которые модель может вызывать. Они расширяют возможности модели за пределы её знаний.

Типы инструментов:

Тип Пример Для чего агенту
Поиск Web search, векторный поиск Найти актуальную информацию
Чтение Чтение файлов, PDF, сайтов Получить данные для анализа
Запись Создание файлов, отправка писем Действовать в системе
Вычисления Калькулятор, код Python Точные расчёты
API Вызов внешних сервисов Интеграция с другими системами
Коммуникация Telegram, Slack, email Общение с людьми

Каждый инструмент описывается JSON Schema — модель «читает» описание и решает, когда и с какими аргументами его вызвать.

tool = {
    "type": "function",
    "function": {
        "name": "search_web",
        "description": "Поиск информации в интернете",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Поисковый запрос"}
            },
            "required": ["query"]
        }
    }
}

1.3 Оркестратор (цикл управления)

Оркестратор — это код, который управляет последовательностью: отправить запрос модели → получить решение → выполнить инструмент → вернуть результат модели → повторить.

Это и есть моя работа как Sisyphus. Оркестратор решает:

1.4 Память (State)

Агент должен помнить, что он уже сделал, чтобы не ходить по кругу. Подробно — в memory.ru.md, но кратко:

Тип памяти Хранит Пример
Short-term Текущий диалог с моделью Все сообщения в цикле
Long-term Информацию между сессиями Векторная БД с проектами
Working Текущее состояние задачи Какой шаг выполняется сейчас

2. Цикл агента (Agent Loop)

Это самое важное. Понимание цикла — это понимание агентов.

Базовый цикл

  ┌──────────┐
  │  НАЧАЛО  │
  └────┬─────┘
       │
       ▼
  ┌──────────┐     ┌─────────────┐
  │ LLM      │────▶│ Нужен       │
  │ решает   │     │ инструмент? │
  └──────────┘     └──────┬──────┘
       ▲                  │         ┌──────────┐
       │           ┌──────┤  ДА ───▶│ Вызов    │
       │           │      │         │ инструм. │
       │           │      │         └────┬─────┘
       │           │      │              │
       │           │      │              ▼
       │           │      │         ┌──────────┐
       │           │      │         │ Результат│
       │           │      │         └────┬─────┘
       │           │      │              │
       │           │      └──────────────┘
       │           │
       │      ┌────┴────┐
       │      │  НЕТ    │
       │      ▼         │
       │  ┌────────┐    │
       └──┤ ОТВЕТ  │◄───┘
          │ польз. │
          └────────┘

Пошагово:

  1. Пользователь отправляет запрос → система добавляет его в историю
  2. LLM получает всю историю (системный промпт + все предыдущие шаги)
  3. LLM решает: ответить текстом ИЛИ вызвать инструмент
  4. Если инструмент:
    • Система выполняет функцию с аргументами от LLM
    • Результат добавляется в историю как observation
    • Возврат к шагу 2
  5. Если ответ: текст возвращается пользователю

Реализация цикла на Python (без фреймворков)

import requests
import json

def agent_loop(model: str, user_input: str, tools: list, max_steps: int = 10):
    """
    Простейший цикл агента.
    model — имя модели в Ollama
    tools — список инструментов в формате JSON Schema
    """
    messages = [
        {"role": "system", "content": "Ты — полезный ассистент. Используй инструменты когда нужно."},
        {"role": "user", "content": user_input}
    ]
    
    for step in range(max_steps):
        # Шаг 1: спросить модель
        response = requests.post("http://localhost:11434/api/chat", json={
            "model": model,
            "messages": messages,
            "tools": tools,
            "stream": False
        })
        data = response.json()
        msg = data["message"]
        messages.append(msg)
        
        # Шаг 2: модель хочет вызвать инструмент?
        if msg.get("tool_calls"):
            for tc in msg["tool_calls"]:
                func_name = tc["function"]["name"]
                args = tc["function"]["arguments"]
                print(f"  ▶ Шаг {step+1}: вызываю {func_name}({args})")
                
                # Шаг 3: выполняем функцию (в реальности — lookup по имени)
                result = execute_tool(func_name, args)
                
                # Шаг 4: результат → в историю
                messages.append({
                    "role": "tool",
                    "name": func_name,
                    "content": json.dumps(result)
                })
        else:
            # Модель решила ответить
            return msg["content"]
    
    return "Достигнут лимит шагов"

Этот код — буквально ядро любого агента. LangGraph, CrewAI, Agno — все они внутри делают то же самое, но с дополнительными плюшками (графы, память, параллелизм).

Почему без фреймворка?

Написать цикл вручную хотя бы раз критически важно, потому что:


3. State и память

Состояние (state) агента — это вся информация, которая накоплена за время его работы.

Что входит в state

agent_state = {
    "messages": [],           # Вся переписка с LLM
    "step": 0,                # Текущий шаг цикла
    "max_steps": 10,          # Лимит шагов
    "tools_used": [],         # История вызовов инструментов
    "errors": [],             # Ошибки
    "intermediate_results": {},  # Промежуточные результаты
    "plan": [],               # План (если есть)
    "user_intent": "",        # Исходный запрос пользователя
}

Как фреймворки управляют state

LangGraph — StateGraph с типизированным состоянием:

class AgentState(TypedDict):
    messages: Annotated[list, add_messages]  # автоматическое добавление
    next_agent: str

CrewAI — каждый агент имеет внутреннее состояние (роль, цель, память).

Agno — session_state для хранения данных между запусками.

Проблемы с состоянием

Проблема Почему возникает Решение
Контекст переполняется Каждый шаг добавляет токены Лимит шагов, компрессия
Потеря контекста Модель «забывает» начало диалога Суммаризировать историю
Противоречивые решения Модель видит слишком много данных Чистить контекст, держать фокус
Утечка данных State хранит чувствительную информацию Scoping, очистка после завершения

4. Контекстное окно

Контекстное окно — главное ограничение агента. Каждый шаг цикла добавляет токены, и окно быстро заполняется.

Сколько токенов съедает цикл

Действие Токенов (примерно)
Системный промпт 200–500
Запрос пользователя 50–200
Решение модели (reasoning) 200–1000
Вызов инструмента (JSON) 100–300
+ Результат инструмента 200–5000
Один шаг цикла ~500–5000 токенов
10 шагов ~5000–50000 токенов

На M1 16 GB с num_ctx: 4096 вы упрётесь в лимит уже на 3–5 шагах.

Решения

  1. Увеличить num_ctx — если влезает в RAM (см. memory-and-context.ru.md)
  2. Ограничить шаги — max_steps=5 для большинства задач хватает
  3. Компрессия контекста — суммаризировать историю, когда она становится слишком длинной
  4. KV cache квантизация — OLLAMA_KV_CACHE_TYPE=q4_0 даёт 4× больше места
# Компрессия контекста: когда сообщений слишком много — суммаризируем
def compress_if_needed(messages, max_messages=20):
    if len(messages) > max_messages:
        # Берём системный промпт + последние N сообщений
        system = messages[0]
        recent = messages[-(max_messages-1):]
        return [system] + recent
    return messages

5. Оркестрация

Оркестрация — это то, чем я занимаюсь как Sisyphus. Один агент — это просто цикл. Несколько агентов, которые работают вместе — это оркестрация.

Уровни оркестрации

┌─────────────────────────────────────────────────────┐
│                  ОРКЕСТРАТОР                          │
│                                                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────┐   │
│  │ Агент 1  │  │ Агент 2  │  │    Агент 3       │   │
│  │ PM       │  │ Аналитик │  │  Программист     │   │
│  └────┬─────┘  └────┬─────┘  └───────┬──────────┘   │
│       │              │                │              │
│       └──────────────┴────────────────┘              │
│                      │                               │
│               Решают задачи,                          │
│               обмениваются результатами               │
└─────────────────────────────────────────────────────┘

Что решает оркестратор:

Как это реализовать:

# Простейший оркестратор — последовательный вызов агентов
def orchestrate(agents: list, task: str):
    result = None
    for agent in agents:
        context = f"Предыдущий результат: {result}" if result else ""
        result = agent.run(f"{task}\n{context}")
    return result

Реальная оркестрация сложнее — она описана в multi-agent.ru.md и в туториале 02-agent-team.ru.md.


6. Что дальше

| Если хотите | Переходите | |————-|———–| | Изучить конкретные паттерны агентов (с кодом) | patterns.md | | Понимать, как агенты хранят информацию | memory.ru.md | | Увидеть, как это работает на фреймворках | frameworks.ru.md | | Написать первого агента руками | tutorials/01-first-agent.ru.md | | Вернуться к навигации | README.md | —


В разделе: architecture · evaluation · frameworks · memory · multi-agent · ollama-for-agents · orchestrators · patterns · prompting · ready-made · safety · skills
Связанные разделы: Нулевой уровень · Локальные модели · Use Cases · Ресурсы
Навигация: ← AI-агенты · ↑ На главную · 🇬🇧 English