Думать и делать — разные роли

LLM умеет рассуждать и писать код, но не умеет его выполнять — у неё нет интерпретатора. А без выполнения нельзя узнать, работает ли решение: модель уверенно напишет функцию с багом и не заметит. Нужен кто-то, кто реально запустит код и вернёт результат — тогда автор увидит ошибку и исправит.

AutoGen разводит это на две роли (привет уроку про разделение ответственности):

  • AssistantAgent — «мозг». Думает, рассуждает, пишет код. Использует LLM, кода не выполняет.
  • UserProxyAgent — «руки» и представитель человека. Выполняет код, возвращает результат, при необходимости спрашивает человека. LLM ему обычно не нужен.

Оба — это ConversableAgent из прошлого урока с предустановленными настройками. AssistantAgent приходит с готовым системным промптом «решай задачи, пиши код в блоках»; UserProxyAgent — с включённым выполнением кода и режимом запроса человека. Их связка через initiate_chat и есть классический паттерн AutoGen.

ℹ️ «UserProxy» = заместитель пользователя

Имя сбивает с толку: UserProxyAgent — это не «агент-пользователь», а прокси (заместитель) человека в разговоре. Он действует от лица пользователя: выполняет то, что предложил ассистент, и при настройке human_input_mode может в любой момент передать слово реальному человеку. По сути — мост между LLM-ассистентом и реальным миром (код, файлы, человек).

Цикл «решение → выполнение → правка»

Магия паттерна — в их диалоге. UserProxy ставит задачу → Assistant пишет код → UserProxy его выполняет и возвращает вывод (или ошибку) → Assistant, увидев результат, исправляет или подтверждает. Цикл повторяется, пока задача не решена.

100%
колёсико — масштаб  ·  зажать и тянуть — перемещение
UserProxyAgent «руки» + человек ▸ выполняет код ▸ возвращает вывод ▸ спрашивает человека llm_config = False AssistantAgent «мозг» ▸ рассуждает ▸ пишет код ▸ правит по ошибке llm_config = {...} 1 · задача 2 · код решения ⌨ 3 · результат выполнения / ошибка 4 · исправленный код … (цикл) Assistant пишет — UserProxy выполняет — Assistant правит по реальному результату

Это и есть заземление рассуждений на реальность: ассистент не «думает, что код верный», а узнаёт это из настоящего вывода интерпретатора. По сути — тот же verifier из урока про проверку и критику, только проверка объективная (запуск кода), а не мнение.

AssistantAgent: мозг

AssistantAgent — это ConversableAgent с готовым системным промптом, который велит решать задачи и оформлять код в markdown-блоках. По умолчанию он сам код не выполняет (human_input_mode="NEVER", без code execution) — его дело думать и писать.

Ассистент — думает и пишет код
python
from autogen import AssistantAgent

llm_config = {"model": "gpt-4o-mini", "api_key": "sk-..."}

assistant = AssistantAgent(
    name="assistant",
    llm_config=llm_config,
    # системный промпт уже встроен: "решай задачи, код — в блоках,
    # в конце ответь TERMINATE". Можно переопределить system_message.
)

UserProxyAgent: руки и человек

UserProxyAgent — тоже ConversableAgent, но настроенный на действие: он умеет выполнять код из сообщений ассистента и представлять человека. LLM ему обычно не нужен (llm_config=False) — он не генерирует текст, а исполняет.

Прокси — выполняет код и/или зовёт человека
python
from autogen import UserProxyAgent

user_proxy = UserProxyAgent(
    name="user_proxy",
    human_input_mode="NEVER",            # NEVER — автономно; ALWAYS — спрашивать
    max_consecutive_auto_reply=10,       # предохранитель: автоответов подряд
    is_termination_msg=lambda m: "TERMINATE" in (m.get("content") or ""),
    code_execution_config={              # ГДЕ и КАК выполнять код
        "work_dir": "coding",            # рабочая папка
        "use_docker": False,             # True — изолированно в Docker (безопаснее)
    },
    llm_config=False,                    # ему не нужна модель — он исполнитель
)

Ключевой параметр — code_execution_config: он включает выполнение кода и говорит, где его запускать. Поставь False — и прокси перестанет исполнять код (станет просто мостом к человеку). human_input_mode управляет участием человека: "NEVER" — полностью автономно, "ALWAYS" — спрашивать на каждом ходу, "TERMINATE" — только перед завершением.

Выполнение кода от LLM — это риск

UserProxyAgent запускает код, который придумала модель. Без изоляции это опасно: галлюцинированная команда может удалить файлы или утечь данными. На проде включай use_docker=True (выполнение в контейнере) и не давай прокси доступ к чувствительному окружению. Это прямое продолжение урока про approval workflows: опасные действия — под контролем.

Полный пример: пара решает задачу

Assistant пишет код — UserProxy выполняет
python
from autogen import AssistantAgent, UserProxyAgent

llm_config = {"model": "gpt-4o-mini", "api_key": "sk-..."}

assistant = AssistantAgent(name="assistant", llm_config=llm_config)

user_proxy = UserProxyAgent(
    name="user_proxy",
    human_input_mode="NEVER",
    max_consecutive_auto_reply=10,
    is_termination_msg=lambda m: "TERMINATE" in (m.get("content") or ""),
    code_execution_config={"work_dir": "coding", "use_docker": False},
)

# UserProxy инициирует разговор — ставит задачу ассистенту
user_proxy.initiate_chat(
    assistant,
    message="Посчитай 10-е число Фибоначчи. Напиши и запусти код.",
)

# Что произойдёт автоматически:
#  1. user_proxy → assistant: задача
#  2. assistant → user_proxy: ```python ...код фибоначчи...```
#  3. user_proxy ВЫПОЛНЯЕТ код, возвращает вывод (например, 55)
#  4. assistant видит результат, проверяет, отвечает "... TERMINATE"
#  5. разговор завершается по is_termination_msg

Заметь: инициирует разговор обычно user_proxy (он представляет пользователя с задачей), а assistant отвечает. Весь цикл код→выполнение→правка крутится сам, без твоего участия, пока ассистент не скажет TERMINATE или не упрётся в max_consecutive_auto_reply.

Человек в цикле через прокси

Поскольку UserProxy представляет человека, через него легко вернуть человека в контур. Достаточно сменить режим — и перед выполнением кода (или вместо автоответа) система спросит реального пользователя.

human_input_mode — степень участия человека
python
# Полная автономия — ничего не спрашивает (демо, доверенные задачи)
UserProxyAgent(name="up", human_input_mode="NEVER", ...)

# Человек подтверждает каждый ход — максимум контроля
UserProxyAgent(name="up", human_input_mode="ALWAYS", ...)

# Спросить только перед завершением — компромисс
UserProxyAgent(name="up", human_input_mode="TERMINATE", ...)
Это AutoGen-версия human-in-the-loop

human_input_mode="ALWAYS" — прямой аналог interrupt_before из модуля 03: перед действием система отдаёт ход человеку. Для рискованных операций (выполнение кода, обращение к боевым системам) ставь ALWAYS или TERMINATE, для безопасных демо — NEVER. Контур человека встроен в паттерн by design — в этом сила связки с прокси.

Типичные ошибки

Ошибка 1: выполнение кода без изоляции

Код пишет LLM — он может быть опасен. Без use_docker=True или другой песочницы прокси выполнит что угодно прямо в твоём окружении. На проде — изоляция обязательна.

Ошибка 2: llm_config у прокси, когда не нужен

UserProxy — исполнитель, ему обычно не нужна модель. Лишний llm_config заставит его генерировать текст и тратить токены вместо простого выполнения. Ставь llm_config=False.

Ошибка 3: нет предела автоответам

Без max_consecutive_auto_reply и условия завершения пара может «чинить код» бесконечно. Задавай оба: лимит как предохранитель и TERMINATE как осмысленный выход.

Ошибка 4: путать, кто инициирует

Обычно разговор начинает user_proxy (он несёт задачу пользователя), а ассистент отвечает. Перепутаешь — поток получится неестественным. Инициатор = тот, у кого задача.

Шпаргалка

Assistant + UserProxy — всё в одном месте
python
from autogen import AssistantAgent, UserProxyAgent

# РАЗДЕЛЕНИЕ РОЛЕЙ:
#   AssistantAgent  — «мозг»: думает, пишет код (llm_config = {...})
#   UserProxyAgent  — «руки» + человек: выполняет код (llm_config = False)

assistant = AssistantAgent(name="assistant", llm_config=llm_config)

user_proxy = UserProxyAgent(
    name="user_proxy",
    human_input_mode="NEVER",            # NEVER | ALWAYS | TERMINATE
    max_consecutive_auto_reply=10,       # предохранитель
    is_termination_msg=lambda m: "TERMINATE" in (m.get("content") or ""),
    code_execution_config={"work_dir": "coding", "use_docker": True},  # ИЗОЛЯЦИЯ!
    llm_config=False,
)

# Прокси (с задачей) инициирует разговор:
user_proxy.initiate_chat(assistant, message="Реши задачу, напиши и запусти код.")

# ЦИКЛ: assistant пишет код → user_proxy выполняет → assistant правит по выводу

# Правила:
#  • код от LLM → выполнять В ИЗОЛЯЦИИ (use_docker=True)
#  • прокси-исполнителю → llm_config=False
#  • ВСЕГДА предел: max_consecutive_auto_reply + is_termination_msg
#  • инициирует тот, у кого задача (обычно user_proxy)
#  • human_input_mode = AutoGen-версия human-in-the-loop

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

Собери классическую пару AutoGen:

Задание: пара «ассистент + прокси» решает задачу с кодом

  1. Создай AssistantAgent и UserProxyAgent с code_execution_config (для учёбы можно use_docker=False, но осознавай риск).
  2. Запусти через user_proxy.initiate_chat задачу, требующую кода: «построй график функции и сохрани в png» или «посчитай статистику по списку чисел».
  3. Проследи по логу цикл: код → выполнение → (ошибка → правка) → результат → TERMINATE. Найди момент, где ассистент исправился по реальному выводу.
  4. Намеренно усложни задачу так, чтобы первый код упал, и убедись, что ассистент чинит его по сообщению об ошибке от прокси.
  5. Поменяй human_input_mode на "ALWAYS" и поучаствуй: подтверждай или правь действия прокси вручную. Сравни с автономным режимом.
  6. Со звёздочкой: включи use_docker=True и объясни, какие риски это снимает; подумай, какие операции ты бы вообще запретил прокси выполнять.

Что дальше

Пара «ассистент + прокси» — это диалог двоих. Но часто нужно больше участников: писатель, критик, исполнитель — все в одном обсуждении. За координацию многих агентов в AutoGen отвечает GroupChat — следующий урок.