/
Блог Хекслета
/
Код
/

Как создать ИИ-агента на Python: рабочий код с нуля

Как создать ИИ-агента на Python: рабочий код с нуля

17 августа 2026 г.

15 минут
42
Как создать ИИ-агента на Python: рабочий код с нуля

Как создать ИИ-агента: от цикла в двадцать строк до рабочего помощника по коду

Про ИИ-агентов пишут так, будто это отдельная технология со своей теорией. Читаешь пять статей подряд и выносишь оттуда схему с четырьмя прямоугольниками, слово «оркестрация» и ощущение, что без фреймворка на десять зависимостей тут не обойтись.

Потом открываешь исходники любого агента и видишь цикл while. Внутри — вызов модели, проверка «не попросила ли она вызвать функцию», вызов функции, запись результата обратно в историю. Всё.

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

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

Агент, чат-бот и обычная автоматизация: где граница

Три слова, которые в статьях путают чаще всего.

Чат-бот отвечает. Вы спрашиваете, он генерирует текст. Даже если он умеет вызвать одну функцию — например, посмотреть погоду, — решение о вызове принимает человек, задавший вопрос про погоду.

Автоматизация выполняет заранее описанный сценарий. Пришло письмо → распарсить → положить в таблицу → отправить в чат. Порядок шагов задал разработчик. Модель внутри может быть, но она не выбирает маршрут.

Агент сам решает, что делать дальше. Вы даёте цель и набор инструментов, а последовательность шагов агент определяет по ходу дела. Спросили «почему падает тест на скидках» — он сам догадается сначала найти файл, потом прочитать его, потом, может быть, залезть в соседний модуль.

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

Простой признак: если из системы убрать модель и заменить её на if/else, автоматизация продолжит работать, а агент перестанет существовать.

Из чего он состоит

Четыре части, и три из них — обычный код.

Модель решает, что делать дальше. Единственная часть, которую мы не пишем.

Инструменты — функции, которые агент может вызвать. Прочитать файл, сделать запрос к базе, отправить письмо. Модель не выполняет их сама: она возвращает «хочу вызвать read_file с такими аргументами», а выполняем мы.

Память — история переписки. Никакой отдельной базы на старте не нужно: память агента — это массив сообщений, который растёт с каждым шагом. Модель не помнит ничего между вызовами, всю историю мы отправляем заново каждый раз. Из этого следует неприятное: чем дольше работает агент, тем дороже каждый следующий шаг.

Цикл соединяет остальное. Спросить модель → выполнить, что она попросила → вернуть результат → спросить снова.

Схема одного шага:

задача ──> [модель] ──> просит инструмент? ──да──> [выполняем] ──> в историю ─┐
                              │                                              │
                              нет                                            │
                              │                                              │
                              ▼                                              │
                           ответ                              (и снова к модели)

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

Что агенту стоит поручать, а что нет

Здесь у меня есть позиция, и она расходится с тем, что обещают на лендингах.

Агент хорошо работает там, где много однотипной возни и есть способ проверить результат. Разобраться в незнакомом проекте, собрать данные из десяти файлов, найти все места, где используется устаревший метод, прогнать рутинную проверку по списку. Ошибку в такой задаче видно сразу: либо файл нашёлся, либо нет.

Агент плохо работает там, где ошибку не видно. Он с уверенным лицом сошлётся на функцию, которой нет. Придумает флаг командной строки, которого не существует. Причём чем длиннее цепочка шагов, тем выше шанс, что где-то в середине он свернул не туда — а вы увидите только финальный ответ, гладкий и убедительный.

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

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

Писать код или взять no-code

Честный ответ: зависит от того, что вы делаете — прототип или систему, которую придётся поддерживать.

No-code и low-code (Flowise, Botpress, платформы облачных провайдеров) дадут результат за вечер. Вы соберёте схему мышкой, подключите модель, получите работающего агента. Для проверки идеи или для несложного сценария этого хватает.

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

Свой код дороже на старте и дешевле дальше. Вы видите каждый шаг, ставите отладчик там, где нужно, и пишете тесты. Заготовка из этой статьи — примерно тот объём, который придётся написать: около двухсот строк с инструментами и обработкой ошибок.

Про n8n скажу отдельно, потому что его упоминают почти в каждой статье про агентов. Инструмент рабочий, но интерес к нему заметно сходит: в октябре 2025 года его искали в Яндексе 80 331 раз за месяц, в июне 2026 — 34 819. Падение на 57% за восемь месяцев. Это не повод его избегать, но строить на нём долгую стратегию я бы не советовал.

Мой выбор для обучения — свой код, даже если в продакшене вы потом возьмёте фреймворк. Причина простая: разобравшись в цикле, вы сможете читать чужие фреймворки. Начав с фреймворка, вы будете гадать, почему он ведёт себя странно.

На чём собирать агента из России

Тема, которую большинство статей аккуратно обходит: примеры пишут под сервисы, к которым у читателя нет доступа.

Что реально доступно:

Российские модели. GigaChat от Сбера и модели Yandex AI Studio. У обоих есть API, документация на русском и оплата с российской карты. Главное для нас — оба умеют вызывать функции, без этого агента не собрать. В документации GigaChat сказано прямо: пользовательские функции поддерживают все модели. У Яндекса подтверждение ещё нагляднее — в тарифах отдельной строкой идёт «цена за 1000 токенов инструментов», то есть вызовы инструментов там не просто есть, а тарифицируются отдельно.

Названия моделей для запроса: у Сбера — GigaChat-2, GigaChat-2-Pro, GigaChat-2-Max, у Яндекса — YandexGPT Lite, YandexGPT Pro 5.1, Alice AI LLM и открытые модели вроде Qwen и gpt-oss.

Локальные модели. Открытые модели через Ollama или vLLM на своём железе. Плюс: данные не уходят наружу, платить за токены не надо. Минус: нужна видеокарта, а маленькие модели заметно хуже держат длинные цепочки вызовов инструментов. Для агента, который делает десять шагов подряд, разница между большой и маленькой моделью видна невооружённым глазом.

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

Если ещё не выбрали, с какой моделью работать в коде, поможет наш обзор — лучшие ИИ для кодинга.

Хорошая новость: большинство провайдеров говорят на одном языке. Формат chat completions, придуманный OpenAI, поддерживают и российские сервисы, и Ollama, и почти всё остальное. Поэтому в нашем коде модель спрятана за одним классом: поменять провайдера — значит поменять две строки конфигурации.

llm = OpenAICompatLLM(
    model="имя-модели",
    base_url="https://адрес-провайдера/v1",
    api_key=os.getenv("LLM_API_KEY"),
)

Дальше агенту всё равно, кто отвечает.

Собираем агента

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

Шаг 1. Обёртка над моделью

Начнём с того, что агенту нужно от модели. А нужно немного: отправить историю сообщений и список инструментов, получить либо текст, либо просьбу вызвать инструмент.

Опишем ответ модели двумя классами:

@dataclass
class ToolCall:
    """Модель просит вызвать инструмент."""
    id: str
    name: str
    arguments: dict


@dataclass
class Reply:
    """Ответ модели: либо текст, либо вызовы инструментов."""
    text: str | None = None
    tool_calls: list[ToolCall] = field(default_factory=list)

    @property
    def wants_tools(self) -> bool:
        return bool(self.tool_calls)

Дальше — сам запрос. Ничего интереснее обычного HTTP здесь нет, кроме двух мест.

def complete(self, messages: list[dict], tools: list[dict]) -> Reply:
    payload = {"model": self.model, "messages": messages}
    if tools:
        payload["tools"] = tools
        payload["tool_choice"] = "auto"
    ...

tool_choice: auto означает «решай сама, вызывать инструмент или ответить текстом». Именно эта строка отличает агента от программы, которая всегда дёргает функцию.

Второе место — разбор аргументов:

arguments = function.get("arguments") or "{}"
if isinstance(arguments, str):
    try:
        arguments = json.loads(arguments)
    except json.JSONDecodeError:
        arguments = {"__raw__": arguments}

Модель присылает аргументы строкой с JSON внутри. Иногда — сломанным JSON: обрезанным, с лишней запятой, с комментарием. Первая версия моего кода падала на этом, и падала не сразу, а через несколько минут работы агента, когда уже потрачены токены. Поэтому здесь не исключение, а передача проблемы дальше: пусть агент сообщит модели, что аргументы не разобрались, и та попробует снова.

Шаг 2. Инструменты

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

@dataclass
class Tool:
    name: str
    description: str
    parameters: dict
    run: Callable[..., str]

    def schema(self) -> dict:
        """Описание в формате, который понимает модель."""
        return {
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": self.parameters,
            },
        }

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

Самое важное ограничение — песочница:

class Workspace:
    def __init__(self, root: str | Path) -> None:
        self.root = Path(root).resolve()
        if not self.root.is_dir():
            raise ValueError(f"Каталог не найден: {self.root}")

    def resolve(self, relative: str) -> Path:
        target = (self.root / relative).resolve()
        if target != self.root and self.root not in target.parents:
            raise PermissionError(f"Путь вне рабочего каталога: {relative}")
        return target

Восемь строк, и они обязательны. Без них модель однажды попросит прочитать ../../../.ssh/id_rsa — и получит. Не из злого умысла: она просто ищет конфигурацию и делает это широко. resolve() до проверки нужен именно затем, чтобы .. в пути раскрылись до сравнения.

Сами инструменты — три штуки. list_files показывает структуру, read_file читает диапазон строк, search ищет строку по файлам. В каждом есть ограничение на объём вывода:

MAX_CHARS = 4000     # сколько текста максимум отдаём модели за один вызов

MAX_FILES = 60 # чтобы огромный проект не вынес контекст целиком

Это не микрооптимизация. Если list_files вернёт двадцать тысяч путей, они займут весь контекст, и модель забудет исходную задачу. Агент после этого начинает вести себя необъяснимо — а причина в том, что начало разговора вытеснено списком файлов.

Мелочь, которая экономит время: в read_file мы отдаём строки с номерами.

numbered = "\n".join(f"{start + i:>4}| {line}" for i, line in enumerate(chunk))

Без номеров модель ссылается на код приблизительно: «где-то в середине файла». С номерами — «app/billing.py:13». Одна строка кода, а полезность ответов меняется заметно.

Шаг 3. Цикл

Теперь то, ради чего всё затевалось. Вся агентность целиком:

def run(self, task: str) -> Result:
    messages = [
        {"role": "system", "content": self.system_prompt},
        {"role": "user", "content": task},
    ]
    schemas = [tool.schema() for tool in self.tools.values()]
    steps: list[Step] = []

    for _ in range(self.max_steps):
        reply = self.llm.complete(messages, schemas)

        if not reply.wants_tools:
            return Result(answer=reply.text, steps=steps)

        messages.append(self._assistant_message(reply))
        for call in reply.tool_calls:
            step = self._execute(call, len(steps) + 1)
            steps.append(step)
            messages.append({
                "role": "tool",
                "tool_call_id": call.id,
                "content": step.result,
            })

    return Result(answer=None, steps=steps, stopped_by_limit=True)

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

Три детали, каждая из которых важнее, чем кажется.

for _ in range(self.max_steps) вместо while True. Лимит шагов существует не для аккуратности. Модель умеет зацикливаться: просит один и тот же файл, не находит, просит снова. С while True это будет продолжаться, пока не кончатся деньги на счету. Двенадцать шагов по умолчанию хватает на любую разумную задачу.

Ответ модели дописывается в историю до выполнения инструментов. Порядок сообщений должен совпадать с тем, что ждёт API: сначала «ассистент попросил вызвать вот это», потом результаты вызовов. Перепутаете — получите ошибку валидации, причём с формулировкой, по которой причину не угадать.

Результат инструмента возвращается модели, даже если инструмент упал. Об этом ниже.

Шаг 4. Ошибки как часть разговора

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

try:
    output = tool.run(**call.arguments)
except TypeError as error:
    output = f"Неверные аргументы для {call.name}: {error}"
    return Step(number, call.name, call.arguments, output, failed=True)
except (OSError, PermissionError, ValueError) as error:
    output = f"Инструмент {call.name} не смог выполниться: {error}"
    return Step(number, call.name, call.arguments, output, failed=True)

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

То же с несуществующим инструментом:

tool = self.tools.get(call.name)
if tool is None:
    known = ", ".join(self.tools) or "нет доступных инструментов"
    return Step(
        number=number, tool=call.name, arguments=call.arguments,
        result=f"Инструмента {call.name!r} не существует. Доступны: {known}.",
        failed=True,
    )

Модель регулярно придумывает инструменты, которых вы ей не давали — особенно если название очевидное вроде write_file. Список доступных в тексте ошибки помогает ей вернуться в рамки.

Перехватывать Exception целиком я тут не стал. Ошибка в наших инструментах — это наш баг, и он должен всплыть на поверхность, а не растворяться в переписке с моделью.

Шаг 5. Память, которая не растёт бесконечно

Про память я говорил вначале: это массив сообщений, и мы отправляем его целиком на каждом шаге. Пока шагов три, беспокоиться не о чем. На десятом-двенадцатом начинается неприятное.

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

Лечится обрезкой:

def _trim(self, messages: list[dict]) -> list[dict]:
    def size(items: list[dict]) -> int:
        return sum(len(str(item.get("content") or "")) for item in items)

    head, tail = messages[:2], messages[2:]
    while tail and size(head + tail) > self.max_history_chars:
        tail.pop(0)
        # Результат инструмента без предшествующего вызова ломает валидацию
        # на стороне API, поэтому подчищаем осиротевшие ответы.
        while tail and tail[0].get("role") == "tool":
            tail.pop(0)
    return head + tail

messages[:2] — системный промпт и задача пользователя. Их не трогаем никогда: без них агент перестаёт понимать, зачем он работает. Выкидываем самое старое из середины.

Вторая петля выглядит странно, но она обязательна. Сообщения идут парами: «ассистент попросил вызвать инструмент» и следом «вот результат». Если выбросить первое и оставить второе, API вернёт ошибку валидации — ответ инструмента, к которому нет вызова. Я на это наступил и минут двадцать искал причину в своём коде, а причина была в порядке сообщений.

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

Шаг 6. Запускаем

Собираем всё вместе:

workspace = Workspace("путь/до/проекта")
tools = build_toolset(workspace)
agent = Agent(llm, tools, max_steps=12)

result = agent.run("Где считается скидка и есть ли в этом проблема?")
print(result.answer)

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

Задача: где считается скидка и есть ли в этом проблема?

  [1] search(query=discount, pattern=*.py)
  [2] read_file(path=app/billing.py, start=1, end=40)

Шагов: 2, оборвано лимитом: False

Ответ:
Скидка считается в app/billing.py:13 — функция apply_discount. Процент нигде
не ограничен, поэтому 150% превратят сумму в отрицательную. Проверку стоит
добавить до вызова.

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

Системный промпт здесь работает так же, как файлы правил в готовых ИИ-инструментах: вы описываете рамки, а решения внутри них принимает модель. Как эти правила пишут для редакторов вроде Cursor, мы разбирали подробно — Cursor Rules: как писать правила, чтобы они реально работали. Приёмы оттуда переносятся на свой агент почти без изменений.

Полный код — в репозитории [СВЕРИТЬ: ссылка на репозиторий]. Там же инструкция по запуску.

Как это отлаживать

Раздел, которого нет почти нигде, а он важнее половины остальных.

Агент недетерминирован. Один и тот же вопрос дважды даст разные маршруты — иногда правильные оба, иногда только один. Обычные приёмы отладки об это спотыкаются: воспроизвести баг «он выбрал не тот инструмент» вы не сможете по требованию.

Отсюда два вывода.

Первый: логируйте каждый шаг. У нас для этого класс Step — номер, инструмент, аргументы, результат, признак ошибки. Когда агент выдал ерунду, вопрос «на каком шаге он свернул не туда» решается чтением списка шагов. Без логов вы видите только финальный ответ и остаётесь с бесполезным «ну он неправильно понял».

Второй, и он же главный трюк: тестируйте на заглушке вместо живой модели. Живая модель для тестов не подходит по трём причинам: отвечает по-разному, стоит денег, требует сети. Но модель — не то, что мы писали. Мы писали цикл, диспетчер инструментов, обработку ошибок и лимит. Всё это проверяется без неё.

class FakeLLM:
    """Проигрывает заранее записанные ответы по порядку."""

    def __init__(self, script: list[Reply]) -> None:
        self.script = list(script)
        self.seen: list[list[dict]] = []

    def complete(self, messages: list[dict], tools: list[dict]) -> Reply:
        self.seen.append(list(messages))
        if not self.script:
            return Reply(text="Сценарий закончился.")
        return self.script.pop(0)

Двенадцать строк, а тестировать становится можно всё. Записываем сценарий — и проверяем поведение агента:

def test_вызывает_инструмент_и_возвращает_ответ(tools):
    script = [
        use_tool("read_file", path="app/billing.py"),
        say("Функция total складывает цены, см. app/billing.py:1"),
    ]
    agent, llm = agent_with(script, tools)
    result = agent.run("что делает total?")

    assert result.tool_calls == 1
    assert "def total" in result.steps[0].result
    # Результат инструмента обязан попасть в историю следующего запроса,
    # иначе модель отвечает вслепую.
    assert any(m["role"] == "tool" for m in llm.seen[-1])

Заодно FakeLLM копит все запросы в self.seen — проверить можно и результат, и то, что именно ушло в модель. Ошибка «результат инструмента не дописался в историю» иначе не ловится: агент продолжает работать, просто отвечает хуже, и понять почему невозможно.

Что стоит покрыть тестами в первую очередь — по опыту этой сборки:

  • обычный путь: вызвал инструмент, получил ответ;

  • несколько шагов подряд;

  • модель просит инструмент, которого нет;

  • модель передаёт неверные аргументы;

  • зацикливание обрывается лимитом;

  • выход за пределы рабочего каталога запрещён.

Последний — тот самый тест, который однажды спасёт вам данные:

def test_выход_за_пределы_каталога_запрещён(tools):
    script = [use_tool("read_file", path="../../../etc/passwd"), say("нельзя")]
    agent, _ = agent_with(script, tools)
    result = agent.run("прочитай системный файл")

    assert result.steps[0].failed
    assert "вне рабочего каталога" in result.steps[0].result

Девять таких тестов прогоняются за 0,08 секунды и не требуют ни ключа, ни сети.

Сколько это стоит?

Считать надо за шаг — здесь новичков ждёт сюрприз.

Модель не помнит предыдущие сообщения, поэтому на каждом шаге мы отправляем всю историю заново. Шаг первый — задача и описания инструментов. Шаг второй — то же плюс ответ модели и результат первого инструмента. Шаг третий — всё предыдущее плюс новое. История растёт, и каждый следующий шаг дороже предыдущего.

Я измерил, сколько наш агент отправляет за прогон из примера выше — тот самый, с двумя вызовами инструментов:

запрос 1:  1 661 знак   (история 2 сообщения + схемы инструментов)
запрос 2:  1 952 знака  (история 4 сообщения + схемы)
запрос 3:  2 588 знаков (история 6 сообщений + схемы)
─────────────────────────
всего отправлено:  6 201 знак
получено назад:      231 знак

Две вещи бросаются в глаза. Первая: запросы растут — 1 661, 1 952, 2 588. Это и есть та самая история, которую мы отправляем заново.

Вторая неожиданнее. Схемы инструментов занимают 1 172 знака и уходят в каждом запросе. Три запроса — три с половиной тысячи знаков только на описания трёх функций. Это 57% всего исходящего трафика. Отсюда правило, которое я не встречал в статьях: не давайте агенту инструменты про запас. Каждый лишний инструмент вы оплачиваете на каждом шаге, даже если он ни разу не вызвался.

Практический вывод про инструменты: длина их ответов влияет на стоимость сильнее, чем длина вопроса. Один read_file, вернувший файл на тысячу строк, будет тянуться через все оставшиеся шаги и оплачиваться на каждом. Отсюда MAX_CHARS = 4000 из третьего шага — ограничение не ради аккуратности, а ради счёта.

Что снижает расход, по убыванию эффекта:

  1. Ограничить объём вывода инструментов. Читать диапазоны строк, а не файлы целиком.

  2. Снизить max_steps. Разница между 12 и 30 шагами на счёте видна.

  3. Отдавать в search короткие фрагменты строк, а не абзацы вокруг совпадения.

  4. Для простых задач брать модель поменьше, для длинных цепочек — побольше. Дешёвая модель, которая путается на шестом шаге и уходит в цикл, обойдётся дороже.

Теперь в рублях. Переведём знаки в токены: для смеси русского текста и кода это примерно 3–4 знака на токен, то есть наш прогон — около 1 550–2 070 входящих токенов и 60–80 исходящих. По тарифам Yandex AI Studio на 1 августа 2026 года (цены с НДС, синхронный режим):

Модель

Вход, ₽/1000 токенов

Выход, ₽/1000

Один прогон агента

Alice AI LLM Flash

0,1

0,2

0,17–0,22 ₽

YandexGPT Lite

0,2

0,2

0,32–0,43 ₽

YandexGPT Pro 5.1

0,8

0,8

1,29–1,72 ₽

Порядок величины — рубль за задачу. Кажется, что мелочь, но агент, которого дёргают тысячу раз в день, обойдётся уже в тысячу рублей, а мы посчитали прогон всего с двумя шагами.

Начать можно бесплатно. У GigaChat есть Freemium: 365 миллионов токенов на 12 месяцев, из них 250 миллионов на моделях Lite. Для обучения и первых экспериментов это практически безлимит — наш прогон съедает около двух тысяч токенов.

Цены живут своей жизнью, поэтому сверяйтесь с тарифами провайдера: у Сбера они лежат в разделе «Тарифы GigaChat API для физлиц», у Яндекса — в «Правилах тарификации AI Studio».

Пять ошибок, на которых спотыкаются все

Слишком широкая задача. «Разберись в проекте и улучши его» — не задача. Агент сделает что-то бессвязное, и виновата будет формулировка. «Найди все места, где скидка применяется без проверки процента» — задача.

Нет лимита шагов. Про while True я уже говорил, но повторю: это самая дорогая ошибка из списка, потому что счёт приходит потом.

Инструменты без ограничений. Дали доступ ко всей файловой системе, потому что «так проще для теста». Тест закончится, а код останется.

Вера в ответ без проверки. Агент назвал файл и номер строки — откройте и посмотрите. В нашем примере он сослался на app/billing.py:13, и там действительно apply_discount. Но модель с такой же уверенностью назовёт строку 13 в файле, который не читала.

Секреты в контексте. Всё, что попало в историю сообщений, ушло провайдеру модели. Файл .env агент прочитает с удовольствием, если ему разрешить. Мы фильтруем скрытые файлы в list_files — этого мало для продакшена, но лучше, чем ничего:

if path.is_dir() or any(
    part.startswith(".") or part == "__pycache__" for part in path.parts
):
    continue

Куда двигаться дальше

Заготовка из статьи закрывает базу. Дальше есть три направления, и они разной сложности.

Инструменты, которые меняют мир, а не просто читают. Самый естественный следующий шаг и самый опасный. Минимум, который стоит сделать до него: подтверждение от человека на необратимые операции и запись всех изменений в лог.

MCP — общий протокол для инструментов. Вместо того чтобы писать обёртку под каждый сервис, вы подключаете готовый сервер. Наши три инструмента по такому протоколу переписываются почти без изменений, а взамен агент получает доступ к чужим готовым серверам. Тема на подъёме: в январе 2026 года «mcp сервер» искали 2 772 раза за месяц, в июне — 5 841, рост вдвое за полгода. Яндекс уже завёл у себя MCP Hub и MCP Gateway API, а русскоязычных разборов почти нет.

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

Если хочется копнуть глубже в устройство самих обвязок — есть отдельный разбор: Анатомия агентного харнесса. Там про то, что происходит вокруг цикла, который мы написали.

Практику с ИИ в разработке мы собрали в двух курсах: ИИ для разработчиков — про повседневную работу с моделями в коде, и LLM-разработчик — про сборку систем на LLM, включая агентов. Если с ИИ пока знакомы поверхностно, начните с бесплатного ИИ и нейросети для начинающих.

Короткие ответы на частые вопросы

Нужно ли уметь программировать?

Для агента из этой статьи — да, на уровне уверенного чтения Python. Без кода тоже можно, через визуальные платформы, но отладка там устроена хуже.

Можно ли бесплатно?

Да. Локальная модель через Ollama — если есть подходящая видеокарта. Либо Freemium у GigaChat: 365 миллионов токенов на год, чего с запасом хватит и на обучение, и на первые рабочие задачи.

Сколько времени занимает сборка?

Код из статьи — вечер, если Python знакомый. Доведение до состояния, в котором агенту доверяют рабочие задачи, — недели, и уйдут они не на код, а на выяснение, где он врёт.

Чем это отличается от RPA?

RPA повторяет записанные действия и ломается от смены интерфейса. Агент каждый раз решает заново — гибче, но и предсказуемости меньше.

Обязателен ли фреймворк?

Нет. Двадцати строк цикла хватает надолго. Фреймворк берите, когда упрётесь в конкретное ограничение, а не заранее.

Какую модель выбрать?

Ту, что поддерживает вызов функций и доступна вам без ухищрений. Для длинных цепочек шагов разница между большой и маленькой моделью существенна, для двух-трёх шагов — почти нет. Если выбираете между конкретными, мы сравнивали их на задачах с кодом: ChatGPT против Claude.

Никита Вихров

9 часов назад

0

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845