Назад в блог

AI для новичковИнструкция

Как устроен ИИ-агент: цикл, который запускается без ключа

ИИ-агент — это цикл: модель просит инструмент, ваш код его запускает, результат возвращается. Запустите демо на Python без ключа, потом подключите модель.

Автор: CheapTokens TeamОпубликовано 6 мин чтения

Обложка: цикл агента — от модели к вызову инструмента и результату
Содержание
  1. Цикл на одной картинке
  2. Запускаем без ключа
  3. Тот же цикл — на настоящей модели
  4. Почему каждый круг дороже предыдущего
  5. Что дальше после 40 строк

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

ИИ-агент — это цикл. Ваша программа отправляет модели разговор; модель либо отвечает, либо просит инструмент («прочитай этот файл»); программа выполняет инструмент, добавляет результат в разговор и снова зовёт модель. И так, пока модель не ответит обычным текстом. Агенты для кода вроде Claude Code, Codex и агентного режима Cursor построены вокруг того же цикла — только инструментов у них больше, а аккуратности тоже. Ниже вы запустите версию в 40 строк совсем без ключа, а потом направите её на настоящую модель.

Цикл на одной картинке#

Представьте начальника, который ничего не может потрогать руками. Он только пишет записки: «принеси todo.txt». Помощник (ваша программа) идёт к полке, приносит файл и кладёт на стол. Начальник читает и пишет следующую записку или итоговый ответ.

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

Цикл агента: сообщения идут в модель, модель просит инструмент, программа его выполняет, результат возвращается в сообщения
Цикл: сообщения → модель → вызов инструмента → результат → снова модель

Сможете расставить один круг цикла по порядку?

Расставьте по порядку

Расставьте один круг цикла агента по порядку

  1. Отправить модели задачу и список инструментов
  2. Модель отвечает вызовом инструмента, а не текстом
  3. Ваша программа сама выполняет этот инструмент
  4. Результат попадает в сообщения как «tool»-сообщение
  5. Модель вызывается снова с результатом — пока не ответит обычным текстом

Запускаем без ключа#

Допустим, задача такая: «Сколько пунктов в todo.txt?». Инструмент — функция read_file, которая смотрит не на диск, а в маленький словарь. А вместо настоящей модели — сценарная: она всегда делает две вещи, сначала просит файл, потом отвечает. Этого достаточно, чтобы увидеть все детали. Сохраните как agent_loop.py (Python 3.10+, только стандартная библиотека):

python
import json, os, urllib.request

NOTES = {"todo.txt": "buy milk\ncall Anna\nsend invoice"}

def read_file(name):
    return NOTES.get(name, "no such file")

TOOLS = {"read_file": read_file}
TOOL_SPECS = [{"type": "function", "function": {
    "name": "read_file", "description": "Read a note by file name",
    "parameters": {"type": "object",
                   "properties": {"name": {"type": "string"}},
                   "required": ["name"]}}}]

def scripted_model(messages):
    if messages[-1]["role"] == "user":      # first lap: ask for the file
        return {"role": "assistant", "content": None, "tool_calls": [{
            "id": "call_1", "type": "function",
            "function": {"name": "read_file",
                         "arguments": "{\"name\": \"todo.txt\"}"}}]}
    return {"role": "assistant", "content": "The file has 3 items."}

def real_model(messages):
    body = {"model": os.environ["LLM_MODEL"], "messages": messages,
            "tools": TOOL_SPECS}
    req = urllib.request.Request(
        os.environ["LLM_BASE_URL"] + "/chat/completions",
        data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json",
                 "User-Agent": "agent-loop-demo/1.0",
                 "Authorization": "Bearer " + os.environ["LLM_API_KEY"]})
    with urllib.request.urlopen(req) as resp:
        data = json.load(resp)
    u = data["usage"]
    print("  usage: in", u["prompt_tokens"], "out", u["completion_tokens"])
    return data["choices"][0]["message"]

def run(task, model, max_steps=8):
    messages = [{"role": "user", "content": task}]
    for step in range(1, max_steps + 1):
        reply = model(messages)
        messages.append(reply)              # keep the model's turn in history
        calls = reply.get("tool_calls")
        if not calls:
            print(f"step {step}: answer -> {reply['content']}")
            return
        for call in calls:
            name = call["function"]["name"]
            args = json.loads(call["function"]["arguments"])
            result = TOOLS[name](**args)    # YOUR code does the work
            print(f"step {step}: tool {name}({args}) -> {result!r}")
            messages.append({"role": "tool", "tool_call_id": call["id"],
                             "content": result})
    print("stopped: max_steps reached")

model = real_model if os.environ.get("LLM_API_KEY") else scripted_model
run("How many items are in todo.txt?", model)

Запустите python agent_loop.py. Пока в окружении нет LLM_API_KEY, работает сценарная модель, и на экране ровно два круга:

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

Обратите внимание на три места: в них вся идея.

  • messages.append(reply) сохраняет собственный ход модели. Уберёте — следующий запрос будет содержать результат инструмента, на просьбу о котором API ничего не видел; большинство API отвечает на это ошибкой 400.
  • arguments приходит JSON-строкой, поэтому перед вызовом функции её нужно пропустить через json.loads.
  • max_steps — стоп-кран. Модель может просить инструменты бесконечно, поэтому у любого настоящего агента есть такой предел.

Тот же цикл — на настоящей модели#

Меняется одно: кто играет модель. Скрипт читает три переменные окружения, и если ключ есть, отправляет те же messages на OpenAI-совместимый адрес. Адрес CheapTokens — https://api.cheaptokens.dev/v1 (совместим с OpenAI Chat Completions и Anthropic Messages), а id модели нужен полный, из документации: deepseek-ai/DeepSeek-V4-Flash-0731.

Три способа запустить цикл

Без ключа

bash
python agent_loop.py

Сценарная модель играет оба круга. Ничего не уходит с вашего компьютера.

DeepSeek-V4-Flash

bash
export LLM_BASE_URL=https://api.cheaptokens.dev/v1
export LLM_API_KEY=<your key>
export LLM_MODEL=deepseek-ai/DeepSeek-V4-Flash-0731
python agent_loop.py

Контекст 380K и до 32 768 токенов в ответе: хватит места для длинного цикла.

GLM-5.3-Flash

bash
export LLM_BASE_URL=https://api.cheaptokens.dev/v1
export LLM_API_KEY=<your key>
export LLM_MODEL=zai-org/GLM-5.3-Flash
python agent_loop.py

Лёгкая модель с контекстом 390K для первого запуска многошагового цикла.

На экране будет та же картина: step 1: tool read_file(...), затем step 2: answer -> ..., а под каждым обращением к модели — строка usage:. Формулировка ответа будет отличаться от сценарной, а разговорчивая модель может сделать лишний круг. Это нормально: решает модель, цикл только подчиняется. Ключ в сам скрипт не вписывайте: переменная окружения — безопасное место (как хранить ключ).

Строки usage: — самое полезное. Сложите числа in в длинном прогоне, и станет видно, куда уходят деньги.

Почему каждый круг дороже предыдущего#

Между вызовами модель ничего не помнит. Каждый круг отправляет весь список messages заново: задачу, все прежние вызовы инструментов и все результаты. (Тот же закон делает дорогим длинный чат; на обычном разговоре его показывает статья про многошаговый чат.)

На круге 1 уходит одно сообщение. Если цикл продолжается, на круге 6 уйдёт одиннадцать.

На круге 1 отправляется только задача; на круге 6 — задача, пять вызовов инструментов и пять результатов
Что получает модель на круге 1 и на круге 6

Допустим, настоящая задача занимает 10 кругов. Начинается она примерно с 2 000 токенов (инструкции, список инструментов, ваш запрос), каждый круг добавляет около 1 500 (результат инструмента и заметка модели), а модель пишет около 300 токенов за круг. Круг 1 отправляет 2 000, круг 2 — 3 500, круг 10 — 15 500. Первая часть суммы: 10 × 2,000 = 20,000 токенов; рост добавляет 1,500 × 45 = 67,500 (45 — это 0+1+…+9), значит, вход за задачу — 20,000 + 67,500 = 87,500 токенов. Выход: 10 × 300 = 3,000 токенов.

Текст самой задачи — пара предложений, а входящих токенов 87 500. При цене $0.015 за 1M входных токенов вся задача на DeepSeek-V4-Flash обойдётся в $0.0016. Ниже та же нагрузка на разных моделях, для справки — OpenRouter. В сумме, с учётом входа и выхода, CheapTokens на этой нагрузке дешевле OpenRouter:

Модель Стоимость OpenRouter
GLM-5.3-Flash $0.0019 $0.0038
DeepSeek-V4-Flash $0.0016 $0.0046
MiniMax-M2.7 $0.0048 $0.0299

87 500 входных и 3 000 выходных токенов всего. Цены за 1M токенов актуальны и меняются вместе с нашими тарифами; столбец OpenRouter показывает справочные цены для той же нагрузки.

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

Калькулятор стоимости

Один день: 20 задач по 10 кругов

Входные токены на запрос: 8 750 · Выходные токены на запрос: 300 · Запросов в день: 200

МодельВ деньВ месяц
GLM-5.3-Flash$0.0374$1.12
DeepSeek-V4-Flash$0.0323$0.968
MiniMax-M2.7$0.0965$2.90

Поля — на один вызов модели: 8 750 — средний вход круга из примера выше (87 500 ÷ 10), а 200 вызовов — это 20 задач × 10 кругов. Поэтому счёт агента — это про историю, а не про вопрос. Тот же эффект подробнее разобран в примере на 20 шагов.

Быстрая проверка

На круге 3 цикл агента стоит дороже, чем на круге 1, хотя вы ничего нового не спрашивали. Почему?

Показать ответ

Ответ: В messages уже два вызова инструментов и два результата, и всё это отправляется заново

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

Что дальше после 40 строк#

Скрипт выше — учебный набросок. Если нужна полная версия, есть открытый курс на GitHub: justxor/agent-harness-course (README на русском, страницу смотрели 31 октября 2026 года). Это чистый Python 3.10+ без зависимостей, код под лицензией MIT, текст — CC BY 4.0. После клонирования python examples/02_agent_loop.py запускает цикл офлайн со сценарной моделью, а python -m unittest discover -s tests гоняет 19 тестов без сети. Дальше в курсе — сжатие контекста, память, субагенты, подтверждение рискованных действий человеком и учёт расходов. Для настоящей модели он читает LLM_PROVIDER, LLM_MODEL, LLM_API_KEY и, для OpenAI-совместимых адресов, LLM_BASE_URL: точное значение LLM_PROVIDER возьмите из его README.

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