Как создавать AI-агентов: планирование, использование инструментов и выполнение

Как создавать AI-агентов: планирование, использование инструментов и выполнение

Создание AI-агента начинается с простой идеи: дать модели возможность выполнять действия, наблюдать за результатами и повторять цикл. Модель размышляет над целью, решает, что делать дальше, вызывает инструмент, получает результат и продолжает, пока задача не будет выполнена. Этот цикл «чтение-оценка-действие» лежит в основе любого агента, который вы построите — будь то однократный вызов инструмента или многошаговая система, которая пишет код, запускает его и исправляет собственные ошибки.

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

Что делает сущность AI-агентом

AI-агент отличается от обычного вызова LLM одним критическим образом: он выполняет действия и наблюдает за их результатами.

Обычный чат-комплишн (chat completion) генерирует текст. Агент использует этот текст для вызова функции, запроса к API, выполнения команды или чтения файла — и передаёт результат обратно как входные данные для следующего шага. Этот цикл обратной связи и делает агентов полезными для задач, которые невозможно решить за один ответ.

Три возможности определяют агента:

  • Вызов инструментов — вызов определённых функций по имени со структурированными аргументами
  • Накопление контекста — перенос результатов работы инструментов по мере роста диалога
  • Целенаправленный цикл — продолжение до достижения условия остановки, а не после одного ответа

Если система обладает всеми тремя, вы строите агента. Если отсутствует накопление контекста или цикл — это чат-бот с поддержкой инструментов.

Одна и та же архитектура подходит для широкого круга задач: исследовательские агенты, которые ищут и синтезируют информацию; кодирующие агенты, которые пишут и запускают код; агенты для работы с данными, которые выполняют запросы к базам данных и генерируют отчёты. Шаблон реализации практически идентичен в каждом случае.

Шаг 1: Планирование — уровень рассуждений

Уровень планирования — это то, где модель решает, что делать дальше. Вам не нужна отдельная модель-планировщик — та же модель, которая генерирует текст, может размышлять над целью и выдавать структурированное действие (вызов инструмента) на каждом шаге.

Системный промпт (system prompt) выполняет большую часть работы. Хороший промпт для планирования сообщает модели:

  • Какую цель она преследует
  • Какие инструменты доступны и что каждый из них делает
  • В каком формате нужно вызывать инструмент
  • Когда остановиться (условие завершения)

С помощью Novita LLM API — совместимого с OpenAI — это выглядит так:

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.novita.ai/v3/openai",
    api_key=os.environ["NOVITA_API_KEY"],
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "run_code",
            "description": "Execute Python code and return stdout/stderr.",
            "parameters": {
                "type": "object",
                "properties": {
                    "code": {
                        "type": "string",
                        "description": "Python code to execute"
                    }
                },
                "required": ["code"]
            }
        }
    }
]

messages = [
    {
        "role": "system",
        "content": "You are a coding assistant. Use the run_code tool to execute Python and solve tasks. Stop when you have a final answer."
    },
    {
        "role": "user",
        "content": "Find all prime numbers under 50."
    }
]

response = client.chat.completions.create(
    model="meta-llama/llama-3.1-8b-instruct",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

Модель возвращает вызов инструмента — JSON-объект с именем функции и аргументами — вместо обычного текстового ответа. Ваш код извлекает это, выполняет функцию и возвращается в цикл, добавляя результат в messages.

Шаг 2: Использование инструментов — подключение модели к миру

Инструменты — это то, что агенты действительно делают. Без них модель может только генерировать текст. С ними она может читать файлы, вызывать API, запускать код, выполнять поиск в базах данных или инициировать любую определённую вами функцию.

Определение инструментов

Каждый инструмент — это JSON-схема, описывающая функцию. Модель читает эту схему, чтобы правильно отформатировать свой вызов. Несколько практических правил:

  • Делайте описания короткими и точными. Модель читает их на каждом шаге. Многословные описания тратят токены.
  • Имена параметров должны соответствовать тому, что функция действительно принимает. Несоответствия между схемой и реализацией приводят к скрытым ошибкам.
  • Возвращайте структурированный вывод. Если ваш инструмент возвращает сырой HTML или неформатированный текст, модель тратит токены на его разбор. Возвращайте чистые строки или JSON с чёткими метками.

Цикл вызова инструментов

После того как модель сделала вызов инструмента, ваш код:

  1. Извлекает имя инструмента и аргументы из ответа
  2. Находит соответствующую функцию Python
  3. Вызывает её с переданными аргументами
  4. Добавляет результат в messages с role: "tool"
  5. Делает ещё один запрос комплишн

Цикл продолжается, пока модель не вернёт обычное сообщение без вызова инструмента (условие завершения).

import json

def run_agent_loop(client, model, messages, tools, tool_fns, max_steps=10):
    for _ in range(max_steps):
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=tools,
            tool_choice="auto",
        )
        choice = response.choices[0]

        # Done — no tool call
        if choice.finish_reason == "stop":
            return choice.message.content

        # Execute the tool call
        tool_call = choice.message.tool_calls[0]
        fn_name = tool_call.function.name
        fn_args = json.loads(tool_call.function.arguments)

        result = tool_fns[fn_name](**fn_args)

        # Append both the model's tool call and the result
        messages.append(choice.message)
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": str(result),
        })

    return "Max steps reached without a final answer."

Это полный цикл агента. Ограничение max_steps предотвращает бесконечные циклы — если модель застревает, вызывая один и тот же инструмент многократно, цикл завершается, а не выполняется бесконечно. Всё остальное — многопоточная оркестрация, память, потоковая передача — строится поверх этого шаблона.

Распространённые шаблоны инструментов

Доступ к файлам: Чтение и запись файлов в рабочем каталоге агента. Возвращайте содержимое файлов в виде строк.

Веб-поиск: Возвращайте результаты поиска как список объектов {title, url, snippet}. Держите сниппеты короткими.

Выполнение кода: Выполняйте код в изолированной среде и возвращайте stdout/stderr (рассматривается в следующем разделе).

Запрос структурированных данных: Выполняйте запрос к базе данных или API и возвращайте записи в формате JSON. Модель занимается обобщением.

Каждый инструмент должен делать одно дело и возвращать детерминированный вывод при одинаковых входных данных. Агенты, которые пытаются упаковать слишком много логики в один инструмент, становятся сложнее в отладке и оценке.

Шаг 3: Выполнение кода — безопасный запуск кода

Если ваш агент генерирует и запускает код, вам нужна изолированная среда. Запуск кода, сгенерированного моделью, напрямую на вашей машине — это риск безопасности: модель может выдать разрушительные команды оболочки, бесконечные циклы или код, который читает конфиденциальные файлы, к которым не должен иметь доступа.

Стандартный подход — песочница (sandbox) — кратковременная, изолированная среда Linux, в которой агент может свободно выполнять код, не влияя на хост-систему. Novita Agent Sandbox создан именно для этого: облачная среда выполнения, которая запускается менее чем за 200 мс, выполняет любой код, созданный агентом, и уничтожает среду после завершения сессии.

Вот как инструмент run_code из раздела о планировании был бы реализован с использованием песочницы:

from novita_sandbox.code_interpreter import Sandbox

sandbox = Sandbox.create()

def run_code(code: str) -> str:
    result = sandbox.run_code(code)
    output = ""
    if result.logs.stdout:
        output += "\n".join(result.logs.stdout)
    if result.logs.stderr:
        output += "\nSTDERR: " + "\n".join(result.logs.stderr)
    return output or "(no output)"

Когда агент вызывает run_code, эта функция выполняет код в изолированной среде и возвращает stdout/stderr в виде строки. Агент видит результат и решает, что делать дальше — исправить ошибку, продолжить работу с выходными данными или объявить задачу выполненной.

Такая архитектура чётко разделяет уровень рассуждений и уровень выполнения. LLM планирует и вызывает инструменты; песочница выполняет код, не раскрывая ничего за пределами контейнера.

Что обрабатывает песочница

  • Команды оболочки (bash, git, python, node)
  • Операции с файловой системой внутри песочницы
  • Сетевые запросы из контейнера
  • Долго выполняющиеся фоновые задачи с асинхронным получением результатов
  • Пауза и возобновление для многосессионных рабочих процессов

Что не разрешается: доступ к хост-системе, другим песочницам или учётным данным, которые вы явно не передали. Эта изоляция делает безопасным позволять модели писать и запускать произвольный код.

Шаг 4: Оценка — когда агент прав

Оценка — это самый пропускаемый этап при разработке агентов. Агент, который выдаёт результат, — это не то же самое, что агент, который выдаёт правильный результат.

Три уровня оценки

1. Детерминированные проверки

Для задач с известными правильными ответами — разбор даты, вычисление значения, извлечение поля — пишите юнит-тесты на выход агента. Они дёшевы в выполнении и быстро выявляют регрессии.

2. Проверки на основе выполнения

Для кодирующих агентов наиболее надёжный сигнал: выполняется ли код и даёт ли правильный результат? Выполните выход агента в песочнице и сравните stdout с ожидаемыми значениями. Это сильнее, чем сравнение текста кода, так как проверяет поведение, а не синтаксис.

def evaluate_code_task(agent_code: str, expected_stdout: str) -> bool:
    result = run_code(agent_code)
    return result.strip() == expected_stdout.strip()

3. LLM как судья

Для открытых задач — написание резюме, ответ на исследовательский вопрос — используйте второй вызов LLM для оценки результата по рубрике. Держите рубрику конкретной: «Включает ли ответ рабочий пример кода?» лучше, чем «Хороший ли ответ?». Расплывчатые рубрики дают несогласованные оценки.

Отслеживание режимов сбоев

Агенты терпят неудачу по повторяющимся шаблонам:

Сбой Причина Исправление
Цикл вызова инструмента Модель продолжает вызывать один и тот же инструмент Установить max_steps; логировать промежуточные шаги
Несоответствие аргументов Схема не соответствует реализации Проверять аргументы инструмента перед вызовом
Переполнение контекста Сессия превышает лимит контекста модели Суммировать или обрезать старые результаты инструментов
Вымышленное имя инструмента Модель выдумывает несуществующий инструмент Использовать строгий список инструментов с tool_choice: "auto"

Оценка — это не отдельная фаза в конце, а часть разработки. Каждый раз, когда вы расширяете возможности агента, напишите хотя бы один тест, который выявит регрессию в новом поведении.

Выбор модели для рассуждений агента

Большинство учебных пособий по умолчанию используют модели с закрытым исходным кодом для рассуждений агента. Это хорошо подходит для прототипирования, но в производстве — где агент может делать 20 вызовов LLM на задачу — стоимость модели и лимиты скорости становятся реальными ограничениями.

Модели с открытым исходным кодом, размещённые через Novita LLM API, всё чаще соответствуют производительности закрытых моделей в задачах, которые действительно нужны агентам: вызов функций, структурированный вывод и многошаговые рассуждения. Модели, такие как Llama 3.3 70B и DeepSeek-V3, показывают результаты на уровне GPT-4 в кодировании и следовании инструкциям, работая при этом за долю стоимости. Для высоконагруженных агентных рабочих нагрузок эта разница быстро накапливается.

API Novita полностью совместим с OpenAI, поэтому переход с модели с закрытым исходным кодом на модель с открытым — это изменение одной строки:

# Before
model = "gpt-4o"

# After — same API contract, lower cost
model = "meta-llama/llama-3.3-70b-instruct"

Для циклов агентов, чувствительных к задержкам, меньшие модели, такие как meta-llama/llama-3.1-8b-instruct, отвечают быстрее и стоят меньше за шаг, что важно, когда каждая пользовательская задача включает много последовательных вызовов. Практический шаблон: используйте более быструю, меньшую модель для шагов маршрутизации и классификации, а более крупную — для сложных шагов рассуждений, требующих большей мощности.

Библиотека моделей Novita также включает сильные варианты для конкретных подзадач агентов — модели зрения для результатов инструментов на основе изображений, модели эмбеддингов для поиска и модели, специализированные на коде, для кодирующих агентов — все доступны из одной конечной точки API.

Собираем всё вместе — минимальный рабочий агент

Вот полный агент, который принимает математическую задачу, генерирует код Python для её решения, запускает его в песочнице и возвращает проверенный результат:

import os
import json
from openai import OpenAI
from novita_sandbox.code_interpreter import Sandbox

client = OpenAI(
    base_url="https://api.novita.ai/v3/openai",
    api_key=os.environ["NOVITA_API_KEY"],
)
sandbox = Sandbox.create()

def run_code(code: str) -> str:
    result = sandbox.run_code(code)
    stdout = "\n".join(result.logs.stdout)
    stderr = "\n".join(result.logs.stderr)
    return stdout or stderr or "(no output)"

tool_fns = {"run_code": run_code}

tools = [
    {
        "type": "function",
        "function": {
            "name": "run_code",
            "description": "Execute Python code. Returns stdout or stderr.",
            "parameters": {
                "type": "object",
                "properties": {
                    "code": {"type": "string"}
                },
                "required": ["code"]
            }
        }
    }
]

messages = [
    {
        "role": "system",
        "content": "Solve the task by writing and running Python code. Use run_code to execute it. Stop when you have a verified answer."
    },
    {
        "role": "user",
        "content": "What is the sum of all even Fibonacci numbers under 1,000,000?"
    }
]

for _ in range(10):
    response = client.chat.completions.create(
        model="meta-llama/llama-3.1-8b-instruct",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )
    choice = response.choices[0]

    if choice.finish_reason == "stop":
        print(choice.message.content)
        break

    tool_call = choice.message.tool_calls[0]
    fn_args = json.loads(tool_call.function.arguments)
    result = tool_fns[tool_call.function.name](**fn_args)

    messages.append(choice.message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": result,
    })

sandbox.kill()

Это покрывает полный цикл агента примерно в 50 строках: планирование, вызов инструментов, выполнение, накопление контекста, ограничение на количество шагов и чистое завершение песочницы. Агент пишет код, запускает его, видит вывод и либо продолжает, либо выдаёт окончательный ответ.

Чтобы расширить: добавьте больше инструментов в tool_fns и tools, замените модель на более крупную для сложных задач или замените run_code песочницы на любую другую функцию — веб-поиск, запрос к базе данных, вызов API. Цикл остаётся неизменным.

Часто задаваемые вопросы

В чём разница между AI-агентом и чат-ботом?

Чат-бот генерирует текстовые ответы. Агент выполняет действия — вызывает инструменты, запускает код, запрашивает API — и использует результаты для продолжения рассуждений в направлении цели. Наличие вызовов инструментов и цикла обратной связи — вот что делает сущность агентом.

Нужен ли мне фреймворк вроде LangChain для создания AI-агента?

Нет. Основной цикл агента достаточно прост, чтобы реализовать его напрямую через любой совместимый с OpenAI API. Фреймворки добавляют ценность для сложной оркестрации (многоагентные системы, постоянная память, потоковый вывод), но они добавляют уровни абстракции, которые могут затруднить отладку. Начните с чистого API, затем добавьте фреймворк, если вам действительно нужно то, что он предоставляет.

Как предотвратить выполнение агентом опасного кода?

Используйте изолированную среду выполнения (песочницу). Код агента выполняется внутри изолированного контейнера, который не имеет доступа к вашей хост-системе или другим процессам. Песочница уничтожается после завершения сессии, поэтому любые изменения, внесённые агентом, ограничены этим контейнером.

Могут ли модели с открытым исходным кодом надёжно обрабатывать вызов функций?

Да — с оговорками по размеру модели. Llama 3.3 70B, DeepSeek-V3 и Qwen 2.5 72B хорошо справляются со структурированным вызовом инструментов. Меньшие модели (7B–8B) работают для простых, чётко определённых схем инструментов, но могут испытывать трудности с неоднозначными инструкциями или сложными многошаговыми цепочками рассуждений. Для производственных агентов тестируйте на вашем реальном распределении задач, прежде чем остановиться на модели.

Сколько шагов должен делать агент на задачу?

Установите жёсткий лимит — 10–20 шагов типично для большинства задач. Логируйте промежуточные шаги, чтобы вы могли видеть, что именно сделал агент, и отлаживать сбои. Задачи, которые постоянно требуют больше шагов, обычно выигрывают от разбиения на подзадачи или под-агентов, а не от увеличения лимита цикла.

Рекомендуемые статьи