Зачем вообще фреймворк

В предыдущих уроках мы написали агента с нуля — ~80 строк Python. Это работает. Но при росте сложности начинаются проблемы: как сохранить состояние между сессиями? Как запустить несколько ветвей параллельно? Как сделать паузу и дождаться подтверждения от пользователя? Как отлаживать агента, когда он делает 20 итераций?

Фреймворки решают именно эти инженерные задачи. Они не меняют фундаментальный цикл perceive → think → act → observe — они дают инструменты для управления этим циклом в production: персистентность, параллелизм, наблюдаемость, human-in-the-loop.

ℹ️ Каждый фреймворк — это компромисс

Гибкость vs простота. Контроль vs скорость разработки. Нет «лучшего» фреймворка — есть подходящий для конкретной задачи. Именно поэтому важно понимать идею каждого, а не просто уметь им пользоваться.

Агенты как графы состояний
LangChain Inc · 2024 · Python & JS · Apache 2.0
StateGraph Checkpointing Human-in-the-loop Streaming Multi-agent

LangGraph строит агентов как направленные графы состояний. Каждый узел (node) — это функция Python, которая принимает состояние и возвращает обновлённое состояние. Рёбра (edges) определяют маршрут: обычные или условные (conditional), по которым граф ветвится в зависимости от значения состояния.

Идея пришла из теории конечных автоматов. Агент — это не «умный чат-бот», а детерминированная машина, которую можно нарисовать на бумаге: вот узлы, вот переходы, вот условия. Это делает LangGraph самым прозрачным и предсказуемым из всех фреймворков.

Ключевые абстракции

StateGraph
Граф, построенный вокруг типизированного состояния. Определяет все узлы, рёбра и точку входа.
State (TypedDict)
Схема состояния агента. Все узлы читают из него и записывают в него. Аннотации управляют слиянием (operator.add).
Node
Python-функция (state) → dict. Возвращает только изменённые поля. Может быть синхронной или async.
Edge / Conditional Edge
Обычное ребро — всегда переходит в следующий узел. Условное — вызывает роутер-функцию, которая возвращает имя следующего узла.
Checkpointer
Сохраняет снимок состояния после каждого шага. MemorySaver — в памяти, SqliteSaver / PostgresSaver — в БД. Основа для resume и HITL.
interrupt_before/after
Пауза перед или после узла — для подтверждения от пользователя (Human-in-the-Loop). После паузы граф можно возобновить с изменённым состоянием.

Как работает LangGraph

100%
колёсико — масштаб · зажать и тянуть — перемещение
STATE: TypedDict messages: Annotated[list, operator.add] next: str = "agent" ← читается и обновляется каждым узлом → START entry NODE: agent вызов LLM возвращает state patch 💾 checkpointer сохраняет здесь state router conditional should_continue(state) "tools" "end" NODE: tools выполнение инструментов tool_result → state ← следующая итерация END финальный ответ stop_reason = end_turn interrupt_before=["agent"] граф ставится на паузу → ждёт подтверждения Checkpointer MemorySaver / SqliteSaver / PostgresSaver

Минимальный пример

from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from typing import TypedDict, Annotated
import operator, anthropic

client = anthropic.Anthropic()

# ── Состояние агента ─────────────────────────────────────────
class AgentState(TypedDict):
    messages: Annotated[list, operator.add]   # += слияние списков
    iterations: int

# ── Узлы графа ───────────────────────────────────────────────
def node_agent(state: AgentState) -> dict:
    """Вызов LLM — ядро агента."""
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=1024,
        messages=state["messages"],
    )
    return {
        "messages": [{"role": "assistant", "content": response.content}],
        "iterations": state["iterations"] + 1,
    }

def node_tools(state: AgentState) -> dict:
    """Выполнение всех запрошенных инструментов."""
    last_msg = state["messages"][-1]["content"]
    results = []
    for block in last_msg:
        if hasattr(block, "type") and block.type == "tool_use":
            result = dispatch_tool(block.name, block.input)
            results.append({"type": "tool_result", "tool_use_id": block.id, "content": result})
    return {"messages": [{"role": "user", "content": results}]}

# ── Роутер (conditional edge) ────────────────────────────────
def router(state: AgentState) -> str:
    last = state["messages"][-1]["content"]
    if state["iterations"] >= 10:
        return "end"
    for block in (last if isinstance(last, list) else []):
        if hasattr(block, "type") and block.type == "tool_use":
            return "tools"
    return "end"

# ── Сборка графа ─────────────────────────────────────────────
builder = StateGraph(AgentState)
builder.add_node("agent", node_agent)
builder.add_node("tools", node_tools)

builder.set_entry_point("agent")
builder.add_conditional_edges("agent", router, {"tools": "tools", "end": END})
builder.add_edge("tools", "agent")   # после инструментов — снова к агенту

graph = builder.compile(
    checkpointer=MemorySaver(),
    interrupt_before=["agent"],       # Human-in-the-loop: пауза перед каждым шагом
)

# ── Запуск ───────────────────────────────────────────────────
config = {"configurable": {"thread_id": "session-1"}}
initial_state = {"messages": [{"role": "user", "content": "Найди последние новости"}], "iterations": 0}

for chunk in graph.stream(initial_state, config=config):
    print(chunk)   # стриминг каждого шага
Сильные стороны
Максимальный контроль над логикой — каждый узел и переход явно задан в коде
Лучший checkpointing в индустрии: resume после сбоя, time-travel дебаггинг
Human-in-the-loop из коробки: interrupt_before/after
Потоковая отдача (streaming) на уровне узлов и токенов
Subgraphs: вложенные графы для сложной логики
Отличная интеграция с LangSmith для трейсинга
Слабые стороны
Крутая кривая обучения — нужно понять TypedDict, Annotated, operator.add
Много boilerplate для простых задач
Привязка к экосистеме LangChain (хотя и не обязательная)
Граф надо проектировать заранее — плохо для быстрых прототипов
Выбирай LangGraph когда:

Нужен production-агент с явной бизнес-логикой, resume после сбоев, human-in-the-loop, сложными ветвлениями. Стандарт для enterprise. Хорошо когда граф можно нарисовать на доске до написания кода.

Команда специализированных агентов
CrewAI Inc · 2024 · Python · MIT
Role-based agents Task delegation Sequential / Hierarchical Memory Flows

CrewAI строит агентов как команду специалистов с ролями, целями и инструментами. Метафора — отдел компании: есть исследователь, аналитик, автор — каждый делает свою часть работы и передаёт результат дальше. Агент понимает свою роль через system prompt, который CrewAI генерирует автоматически из role, goal и backstory.

Ключевая идея: задача (Task) назначается агенту, а не агент сам выбирает что делать. Это делает поведение системы более предсказуемым. Процесс исполнения — последовательный (Process.sequential) или иерархический (Process.hierarchical), где менеджер-LLM распределяет задачи.

Ключевые абстракции

Agent
Специалист с role, goal, backstory и tools. System prompt генерируется автоматически из этих полей.
Task
Задание с description и expected_output. Назначается конкретному агенту или менеджеру для делегирования.
Crew
Контейнер для агентов и задач. Оркестрирует исполнение — определяет порядок и передачу результатов между задачами.
Process
sequential — задачи идут по цепочке. hierarchical — менеджер-LLM решает кому и что делегировать.
context
Результат одной задачи автоматически передаётся как контекст следующей. Цепочка знаний через весь пайплайн.
Memory / Flows
Short-term, long-term, entity memory. Flows (CrewAI 2.0) — событийный оркестратор с условными ветвлениями без LangGraph.

Как работает CrewAI

100%
колёсико — масштаб · зажать и тянуть — перемещение
CREW process = Process.sequential crew.kickoff(inputs={topic: "AI Agents"}) Researcher role: "Senior Researcher" goal: "Find relevant data" tools: [search_tool] backstory: "10 years exp…" ↓ system prompt генерируется автоматически из этих полей Task 1 «Изучи тему {topic}» expected_output: "список ключевых фактов" context: [] context передаётся Analyst role: "Data Analyst" goal: "Analyse findings" tools: [calculate_tool] получает результат Task 1 как дополнительный контекст Task 2 «Проанализируй данные» expected_output: "выводы и инсайты" context: [task1] context накапливается Writer role: "Content Writer" goal: "Write clear report" tools: [ ] видит результаты task1 и task2 как контекст Task 3 «Напиши итоговый отчёт» expected_output: "markdown-отчёт" context: [task1, task2] crew.kickoff() → CrewOutput.raw финальный результат последней задачи

Минимальный пример

from crewai import Agent, Task, Crew, Process
from crewai.tools import tool

# ── Инструмент ───────────────────────────────────────────────
@tool("Search Web")
def search_web(query: str) -> str:
    """Поиск актуальной информации в интернете."""
    return f"Результаты поиска по '{query}': ..."

# ── Агенты (роли с автоматическим system prompt) ─────────────
researcher = Agent(
    role="Senior AI Researcher",
    goal="Найти актуальные и достоверные данные по теме {topic}",
    backstory=(
        "Опытный исследователь с 10-летним стажем. "
        "Специализируется на быстром поиске и верификации источников."
    ),
    tools=[search_web],
    verbose=True,
)

writer = Agent(
    role="Technical Writer",
    goal="Написать понятный технический отчёт на основе исследования",
    backstory="Технический автор, умеющий превращать сложные данные в читаемые тексты.",
    tools=[],
)

# ── Задачи (привязаны к агентам) ─────────────────────────────
research_task = Task(
    description="Изучи тему '{topic}': найди последние события, статистику, экспертные мнения.",
    expected_output="Структурированный список из 5-7 ключевых фактов с источниками.",
    agent=researcher,
)

write_task = Task(
    description="На основе результатов исследования напиши отчёт на русском языке.",
    expected_output="Markdown-отчёт с заголовками, тезисами и выводом.",
    agent=writer,
    context=[research_task],   # автоматически получает результат research_task
)

# ── Команда ──────────────────────────────────────────────────
crew = Crew(
    agents=[researcher, writer],
    tasks=[research_task, write_task],
    process=Process.sequential,
    verbose=True,
)

result = crew.kickoff(inputs={"topic": "AI Agents в 2025"})
print(result.raw)
Сильные стороны
Самый низкий порог входа среди всех фреймворков
Ролевая метафора интуитивно понятна — легко объяснить бизнесу
Автоматический system prompt из role/goal/backstory
Встроенная память (short-term, long-term, entity)
Отличен для multi-agent content pipelines и автоматизации
Слабые стороны
Меньше контроля чем LangGraph — сложно задать кастомную логику
Иерархический процесс непредсказуем — менеджер-LLM может неправильно делегировать
Слабое checkpointing по сравнению с LangGraph
Более высокая стоимость: каждый агент = отдельный LLM-вызов
Выбирай CrewAI когда:

Нужна быстрая разработка мульти-агентной системы с чёткими ролями. Идеален для content automation, research pipelines, бизнес-аналитики. Если хочется прототип за час — CrewAI лучший выбор.

Агенты как участники разговора
Microsoft Research · 2023 · Python · CC BY 4.0 / MIT
ConversableAgent GroupChat Code execution Human proxy AutoGen Studio

AutoGen строит агентов как участников чата. Каждый агент — это ConversableAgent, который умеет получать сообщения, отвечать на них (через LLM или функцию) и инициировать разговор. Несколько агентов объединяются в GroupChat, где GroupChatManager решает кому слово.

Уникальная особенность AutoGen — выполнение кода. UserProxyAgent с code_execution_config запускает Python-код, который сгенерировал AssistantAgent, анализирует вывод и передаёт результат обратно. Это делает AutoGen идеальным для data science и coding-задач.

Ключевые абстракции

ConversableAgent
Базовый класс всех агентов. Умеет получать и отправлять сообщения, запускать функции, вызывать LLM.
AssistantAgent
LLM-агент с системным промптом. Генерирует ответы и код. По умолчанию не выполняет код сам.
UserProxyAgent
Прокси для пользователя или исполнитель кода. human_input_mode: ALWAYS / TERMINATE / NEVER.
GroupChat
Список участников + история сообщений. Определяет кто следующий говорит (round-robin или LLM-роутер).
GroupChatManager
Оркестратор GroupChat. Использует LLM для выбора следующего спикера. Инициирует разговор.
code_execution_config
Настройка sandbox для выполнения кода: директория, Docker, timeout, разрешённые языки.

Как работает AutoGen

100%
колёсико — масштаб · зажать и тянуть — перемещение
GROUP CHAT GroupChatManager решает кому слово (через LLM) speaker_selection_method AssistantAgent "Coder" генерирует Python-код для решения задачи llm_config: gpt-4o system_message: "Write code" code_execution: False AssistantAgent "Reviewer" проверяет корректность и качество кода suggest APPROVED или правки code_execution: False UserProxyAgent "Executor" human_input_mode: NEVER выполняет код автоматически code_execution_config: work_dir: "coding" use_docker: True 🐳 Docker Sandbox Python → stdout → результат в чат UserProxy "Human" human_input_mode: ALWAYS / TERMINATE реальный пользователь initiate_chat() История сообщений GroupChat все агенты видят весь диалог

Минимальный пример

import autogen

# ── Конфигурация LLM ─────────────────────────────────────────
llm_config = {
    "config_list": [{"model": "gpt-4o", "api_key": "sk-..."}],
    "temperature": 0,
}

# ── Агент: генерирует и исправляет код ───────────────────────
assistant = autogen.AssistantAgent(
    name="Coder",
    system_message=(
        "Ты опытный Python-разработчик. "
        "Пиши чистый, рабочий код. "
        "Если код не прошёл тесты — исправь ошибку."
    ),
    llm_config=llm_config,
)

# ── Прокси: выполняет код, может спрашивать пользователя ─────
user_proxy = autogen.UserProxyAgent(
    name="Executor",
    human_input_mode="TERMINATE",       # спросить человека только при завершении
    max_consecutive_auto_reply=10,
    is_termination_msg=lambda x: "TERMINATE" in x.get("content", ""),
    code_execution_config={
        "work_dir": "coding",
        "use_docker": False,             # True в prod — изоляция через Docker
    },
)

# ── Запуск диалога ───────────────────────────────────────────
user_proxy.initiate_chat(
    assistant,
    message=(
        "Напиши Python-скрипт для анализа данных из файла sales.csv: "
        "загрузи данные, посчитай топ-10 продуктов по выручке, "
        "построй график и сохрани в report.png."
    ),
)
# ── GroupChat с несколькими агентами ────────────────────────
coder    = autogen.AssistantAgent("Coder",    llm_config=llm_config, system_message="Пишешь код.")
reviewer = autogen.AssistantAgent("Reviewer", llm_config=llm_config, system_message="Проверяешь код. Говоришь APPROVED или объясняешь что исправить.")
executor = autogen.UserProxyAgent("Executor", human_input_mode="NEVER", code_execution_config={"work_dir": "coding"})

groupchat = autogen.GroupChat(
    agents=[executor, coder, reviewer],
    messages=[],
    max_round=20,
    speaker_selection_method="auto",   # LLM выбирает следующего спикера
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)

executor.initiate_chat(manager, message="Реши задачу сортировки массива за O(n log n)")
Сильные стороны
Лучший в классе для задач с генерацией и выполнением кода
Встроенный Code Executor с Docker-изоляцией
Гибкие режимы человеческого участия (ALWAYS / TERMINATE / NEVER)
Поддержка local моделей (Ollama, vLLM) из коробки
AutoGen Studio — GUI для прототипирования без кода
Слабые стороны
Разговорная парадигма неудобна для строгих pipeline-задач
Сложно контролировать порядок в GroupChat — менеджер-LLM непредсказуем
AutoGen 0.4 сломал совместимость с 0.2 — экосистема ещё не устоялась
Много токенов тратится на «разговор» между агентами
Выбирай AutoGen когда:

Задача — итеративная разработка кода, data analysis, или нужна система где агенты реально «обсуждают» проблему. Идеален для coding assistants и research automation с выполнением кода.

Минималистичный агент с handoffs
OpenAI · 2025 · Python · MIT (ранее Swarm)
Agent Handoffs Runner Guardrails Tracing

OpenAI Agents SDK — самый минималистичный из четырёх. Его философия: агент + инструменты + handoff. Агент работает с инструментами пока не решит задачу или не передаст управление другому агенту через handoff. Это официальный SDK от OpenAI, built on top of Responses API.

Особенность — Handoffs: механизм передачи управления между агентами. Агент-роутер принимает запрос и решает кому его делегировать. Специализированный агент выполняет задачу. Такая архитектура чище чем GroupChat, потому что каждый агент знает свою роль и не знает о существовании других.

Ключевые абстракции

Agent
name, instructions, tools, handoffs. Всё что нужно для агента — в одном объекте. Без лишнего boilerplate.
@function_tool
Декоратор превращает Python-функцию в инструмент. JSON Schema генерируется автоматически из docstring и type hints.
Handoff
Передача управления другому агенту. Тот «принимает» разговор и продолжает его со своими инструментами и инструкциями.
Runner
Исполнитель: Runner.run() запускает агента. Внутри — реактивный цикл с инструментами. Поддерживает streaming и async.
Guardrails
Входные и выходные guard-функции: валидация вводов пользователя, фильтрация ответов. Запускаются параллельно с агентом.
Tracing
Встроенный трейсинг в OpenAI Dashboard. Каждый шаг агента виден в UI без дополнительной настройки.

Как работает OpenAI Agents SDK

100%
колёсико — масштаб · зажать и тянуть — перемещение
Runner Runner.run(triage_agent, message) Triage Agent instructions: "Определи тип запроса и передай нужному агенту" tools: [check_intent] handoffs: [billing_agent, support_agent, sales_agent] реактивный цикл: tools → handoff → ответ сам не отвечает — только маршрутизирует handoff handoff handoff Billing Agent tools: [get_invoice, process_refund] знает о счетах и платежах не знает о других агентах Support Agent tools: [search_kb, create_ticket] работает с базой знаний и тикет-системой Sales Agent tools: [get_pricing, create_proposal] специалист по продажам и ценообразованию result = await Runner.run(triage_agent, input) → result.final_output

Минимальный пример

import asyncio
from agents import Agent, Runner, function_tool

# ── Инструменты ──────────────────────────────────────────────
@function_tool
def get_order_status(order_id: str) -> str:
    """Получить статус заказа по его ID."""
    orders = {"ORD-123": "доставлен", "ORD-456": "в пути"}
    return orders.get(order_id, "заказ не найден")

@function_tool
def process_refund(order_id: str, reason: str) -> str:
    """Оформить возврат средств по заказу."""
    return f"Возврат по заказу {order_id} оформлен. Причина: {reason}. Срок: 3-5 дней."

@function_tool
def search_faq(query: str) -> str:
    """Поиск по базе знаний и FAQ."""
    return f"Статья по теме '{query}': [ссылка на документ]"

# ── Специализированные агенты ────────────────────────────────
billing_agent = Agent(
    name="Billing Specialist",
    instructions=(
        "Ты специалист по расчётам и возвратам. "
        "Помогай с вопросами о заказах, счетах и возвратах средств."
    ),
    tools=[get_order_status, process_refund],
)

support_agent = Agent(
    name="Support Specialist",
    instructions=(
        "Ты агент поддержки. "
        "Отвечай на общие вопросы, ищи информацию в базе знаний."
    ),
    tools=[search_faq],
)

# ── Агент-роутер ─────────────────────────────────────────────
triage_agent = Agent(
    name="Triage Agent",
    instructions=(
        "Ты диспетчер. Определи тип запроса и передай нужному агенту:\n"
        "- Вопросы о заказах, платежах, возвратах → billing_agent\n"
        "- Все остальные вопросы → support_agent\n"
        "Не отвечай сам — только маршрутизируй."
    ),
    handoffs=[billing_agent, support_agent],
)

# ── Запуск ───────────────────────────────────────────────────
async def main():
    result = await Runner.run(triage_agent, "Хочу вернуть деньги за заказ ORD-123")
    print(result.final_output)

asyncio.run(main())
Сильные стороны
Наименьший boilerplate — прощай, 200 строк конфигурации
Handoffs — элегантный механизм мульти-агентной маршрутизации
JSON Schema инструментов из type hints и docstring автоматически
Встроенный трейсинг в OpenAI Dashboard без настройки
Guardrails для валидации ввода/вывода параллельно с агентом
Слабые стороны
Первоклассная поддержка только OpenAI-моделей (хотя есть адаптеры)
Нет checkpointing — состояние не сохраняется между запусками
Молодой SDK (2025) — экосистема ещё формируется
Меньше контроля над циклом чем в LangGraph
Выбирай OpenAI Agents SDK когда:

Используешь OpenAI-модели и нужна быстрая разработка с минимумом кода. Идеален для customer-facing агентов с чёткой маршрутизацией: triage → специалист. Отличная точка старта для команд, которые только начинают с агентами.

Сравнение всех четырёх фреймворков

Критерий LangGraph CrewAI AutoGen OpenAI SDK
Основная метафора Граф состояний Команда ролей Чат агентов Агент + handoff
Порог входа ●●● высокий низкий ●● средний низкий
Контроль над логикой ●●● максимальный ограниченный ●● средний ●● средний
Checkpointing / Resume ●●● best-in-class базовое ограниченное нет
Human-in-the-loop ●●● нативный ●● есть ●●● нативный ●● через guardrails
Выполнение кода ●● через инструменты ●● через инструменты ●●● встроенный sandbox ●● через инструменты
Мульти-агентность ●●● subgraphs ●●● нативная ●●● GroupChat ●●● handoffs
Observability ●●● LangSmith ●● встроенный ●● встроенный ●●● OpenAI Dashboard
Поддержка моделей любые LLM любые LLM любые LLM + local OpenAI-first
Лучший use case Production с
сложной логикой
Content &
research automation
Coding &
data analysis
Customer-facing
с маршрутизацией

Шпаргалка

Быстрый выбор фреймворка

Нужен полный контроль, checkpointing, HITL, сложные ветвления?
  → LangGraph  (enterprise, production, long-running workflows)

Хочешь быстро собрать команду специалистов для автоматизации?
  → CrewAI  (research, content, data pipelines)

Задача — генерация и выполнение кода, итеративная разработка?
  → AutoGen  (coding assistant, data analysis, scientific computing)

Строишь на OpenAI-моделях, нужна маршрутизация к специалистам?
  → OpenAI Agents SDK  (customer support, triage, simple multi-agent)

Хочешь понять как всё работает под капотом / нужен полный контроль?
  → Чистый Python  (обучение, специфические требования, legacy-интеграции)

Ключевые различия

Парадигма:     LangGraph=граф    CrewAI=команда  AutoGen=чат     OpenAI=routing
Контроль:      максимальный      средний          средний         базовый
Скорость MVP:  медленно          быстро           быстро          быстро
Checkpointing: best-in-class     базовое          ограниченное    нет
Код:           очень явный       декларативный    разговорный     минималистичный

Практическое задание

  1. Установи все четыре. Создай виртуальное окружение и установи langgraph crewai autogen openai-agents. Запусти минимальный пример каждого (взяв code из этого урока) и сравни время до первого работающего результата.
  2. Один агент — четыре реализации. Возьми задачу «агент читает новостной RSS, выбирает 3 интересные новости, пишет краткое резюме» и реализуй её в каждом из четырёх фреймворков. Что пришлось написать в каждом? Сколько строк? Что оказалось неудобным?
  3. LangGraph + Checkpointing. Добавь в LangGraph-пример SqliteSaver вместо MemorySaver. Запусти агента, убей процесс на середине, перезапусти с тем же thread_id — агент должен продолжить с места остановки.
← Предыдущий урок
Агент или пайплайн?
Критерии выбора архитектуры
→ Следующий раздел
Как LLM вызывает функции
Tool Calling в деталях