Перейти к содержанию

Документация, с которой ИИ-агент пишет проверяемый код

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

Возьмём обычную задачу: добавить в сервис заказов расчёт доставки. Расчёт сначала проверяет остаток на складе, потом считает доставку; на оба шага 600 мс, на каждый не больше 400 мс. В проекте используется deadline-budget. Обзор библиотеки объясняет идею, но не отвечает на вопросы, от которых зависит код: точный импорт, какие методы синхронные, в каких единицах таймаут и кто отменяет работу.

Соберём это в задание и короткое описание API, напишем функцию и проверим тестами всё, что обещает документация. Это пример подготовки документации, а не замер качества ИИ-модели.

Какой контекст нужен для задачи

Фраза «используй deadline-budget» оставляет открытыми несколько вопросов. Начинается ли отсчёт при создании бюджета? Нужен ли await перед timeout_for_call()? Отменит ли библиотека запрос к складу? Правдоподобная догадка в любом из этих мест может дать неверный код, и неважно, пишет его агент или человек. Для этой задачи задание вместе с нужными файлами может выглядеть так:

Реализуй shipping_quote(sku, *, stock, shipping) на deadline-budget==0.1.3 и Python 3.13.

Проверка склада и расчёт доставки вместе должны уложиться в 600 мс, а каждый шаг не больше чем в 400 мс. Через 600 мс отменяй всю операцию; DeadlineExceededError и TimeoutError пробрасывай наружу. Если остаток нулевой или отрицательный, верни None, иначе верни расчёт доставки.

Прежде чем выбирать вызовы, прочитай приложенное описание API и контракт адаптеров. Если чего-то в них нет, загляни в руководство deadline-budget 0.1.3 для агентов, а не угадывай. Запусти поведенческие тесты из test_contract.py и сообщи результат.

Адаптеры относятся к нашему приложению: stock возвращает количество товара целым числом, а shipping возвращает расчёт доставки. Обе функции асинхронные, принимают sku и именованный timeout в секундах, сами применяют этот таймаут и не подавляют отмену. Это сказано явно, чтобы читатель не решил, будто клиент склада входит в deadline-budget.

Импорты, значения по умолчанию и ответственность в одном месте

В шаблоне библиотек Bedrock для этого есть страница docs/agents.md. Для нашей задачи хватит короткого описания API:

Вопрос Ответ для примера
Что установлено deadline-budget==0.1.3, проверено на Python 3.13
Откуда импортировать from deadline_budget import BudgetContext, DeadlineExceededError
Как создать контекст BudgetContext.create(total_seconds, call_caps=None, *, min_timeout=0.1, safety_margin=0.0)
Когда начинается отсчёт В момент create()
Где нужен await Нигде: create(), remaining() и timeout_for_call() синхронные; await нужен только для адаптеров
В каких единицах время В секундах, float
Кто владеет бюджетом Один контекст на расчёт доставки, общий для обоих шагов
Кто отменяет работу Адаптеры и asyncio.timeout() в приложении
Какие ошибки важны DeadlineExceededError, если таймаут считают после того, как бюджет кончился; TimeoutError от asyncio.timeout() или адаптера

То же описание вместе с запускаемым кодом лежит в README практикума. В нём указаны версия и границы задачи, чтобы читатель понимал, когда пора открывать полное руководство для агентов. Добавьте короткую карту чтения с подписью-вопросом у каждой ссылки (например, статья о дедлайнах о том, как передавать бюджет между сервисами) вместо того, чтобы вставлять все страницы в каждую задачу.

Показываем ошибку и как правильно

Опасные ошибки здесь тихие. Вот одна из них: новый контекст на втором шаге.

caps = {"stock": 0.4, "shipping": 0.4}

# WRONG: the second context starts a fresh 600 ms
ctx = BudgetContext.create(total_seconds=0.6, min_timeout=0, call_caps=caps)
available = await stock(sku, timeout=ctx.timeout_for_call("stock"))
ctx = BudgetContext.create(total_seconds=0.6, min_timeout=0, call_caps=caps)
quote = await shipping(sku, timeout=ctx.timeout_for_call("shipping"))

Ничего не падает. Если проверка склада заняла 250 мс, доставка всё равно получит 400 мс вместо 350, и расчёт может выйти за 600 мс. Ошибка погромче, await BudgetContext.create(...), падает с TypeError при первом же запуске, поэтому пару «неправильно / правильно» на странице для агентов лучше потратить на тихие ошибки. Практикум запускает обе.

Правила внутри рабочего примера

Вот shipping.py. Единственный внешний импорт здесь настоящий API бюджета, а stock и shipping передаёт приложение:

import asyncio

from deadline_budget import BudgetContext


async def shipping_quote(sku, *, stock, shipping):
    ctx = BudgetContext.create(
        total_seconds=0.6,
        min_timeout=0,
        call_caps={"stock": 0.4, "shipping": 0.4},
    )
    async with asyncio.timeout(ctx.remaining()):
        available = await stock(sku, timeout=ctx.timeout_for_call("stock"))
        if available <= 0:
            return None
        return await shipping(sku, timeout=ctx.timeout_for_call("shipping"))

Один контекст, созданный до проверки склада; таймаут каждого вызова считается прямо перед ним. Рядом с этим кодом нужно проговорить три детали:

  • В версии 0.1.3 min_timeout по умолчанию равен 100 мс, и адаптеру могут велеть ждать 100 мс, когда осталось всего 20. В примере стоит ноль.
  • Бюджет только считает числа, а отменяет задачу asyncio.timeout(), когда время вышло. Истекают они одновременно, поэтому медленный шаг, который отдаёт управление, заканчивается TimeoutError; DeadlineExceededError перед вторым шагом значит, что первый заблокировал цикл событий или проглотил отмену.
  • У неизвестного имени в call_caps лимита нет. С опечаткой "stcok" вызов получает все 600 мс вместо 400, и никакой ошибки не возникает.

Проверяем то, что может устареть

Практикум проверяет версию, импорты, значения по умолчанию и поведение функции. Кроме того, он сравнивает shipping.py с его копиями в README практикума и в этой статье на обоих языках по синтаксическому дереву: если поменять код и забыть про документацию, проверка упадёт. Эти проверки нужны тем, кто поддерживает страницу; реализацию агента оценивают поведенческие тесты.

ИДЕЯ В СХЕМЕОт описания задачи к проверяемому коду
---
config:
  theme: default
  look: classic
  flowchart:
    useMaxWidth: false
    wrappingWidth: 150
    padding: 12
    nodeSpacing: 24
    rankSpacing: 32
---
flowchart TD
    accTitle: От описания задачи к проверяемому коду
    accDescr: Задача, сведения об API и рабочий пример складываются в контекст для агента. Код интеграции проходит проверки, а найденные ошибки возвращаются в документацию.
    A["Задача и правила"] --> D["Контекст для агента"]
    B["Версия, импорты, аргументы"] --> D
    C["Рабочий пример"] --> D
    D --> E["Код интеграции"] --> F["Исполняемые проверки"]
    F -.-> D

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

В поведенческих тестах часы внутри модуля бюджета подменены, так что тест тратит время бюджета, не дожидаясь его; asyncio живёт по настоящим часам. Тесты проверяют таймауты 400 и 350 мс, версию с ошибкой из раздела выше, остаток в 20 мс, который доходит до адаптера без изменений, пустой склад, опечатку в имени вызова, зависший шаг, который отменяет asyncio.timeout(), и шаг, который выходит за лимит, не отдавая управление. Четырнадцать проверок работают на зафиксированной версии пакета и unittest из стандартной библиотеки, без модели, внешних сервисов и Docker:

cd docs/blog/lab/2026-09-07-documentation-for-ai-coding-agents
uv run --no-project --python 3.13 --with-requirements requirements.txt python test_contract.py

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

Агент должен получить сам текст страницы

URL в задании ещё не значит, что страницу прочитали. Агенту, который умеет загружать файлы, дайте прямую ссылку на Markdown, иначе вставьте страницу ему в контекст. Документация библиотек Bedrock публикуется в Markdown рядом с HTML: например, страница deadline-budget для агентов лежит обычным .md-файлом, его создаёт скрипт экспорта из шаблона.

Оставляем следующему читателю проверяемую точку входа

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