Автодокументация кода нейросетью: docstring, README и CI

Скрипт на Python находит функции без docstring, просит модель описать их и аккуратно вставляет текст в код. Плюс запуск в pre-commit и CI.

4 мин · · разработка · автоматизация

Автодокументация кода нейросетью решает скучную, но важную задачу: у функций без docstring появляется описание параметров и возвращаемого значения в едином стиле. Ниже — скрипт, который находит такие функции через модуль ast, генерирует текст через API и вставляет его так, чтобы файл остался валидным Python. Затем подключим его к pre-commit и GitHub Actions.

Где автоматизация помогает, а где нет

Хорошо получается:

  • описать параметры, типы и возвращаемое значение по сигнатуре и телу функции;
  • привести docstring к одному стилю (Google, NumPy или Sphinx);
  • закрыть «долг» в старом модуле, где документации нет вообще.

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

Схема работы

  1. Разобрать файл через ast и найти публичные функции и методы без docstring.
  2. Для каждой отправить в модель исходный код функции и требования к стилю.
  3. Вставить ответ первой строкой тела функции с правильным отступом.
  4. Проверить, что файл по-прежнему парсится, и только тогда сохранить.

Скрипт auto_doc.py

Нужны Python 3.9+, pip install openai и ключ fetch_... в переменной OPENAI_API_KEY. Скрипт принимает список файлов и правит их на месте.

bash
#!/usr/bin/env python3
import ast
import os
import sys
import textwrap

from openai import OpenAI

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["OPENAI_API_KEY"],
)
MODEL = os.environ.get("DOC_MODEL", "glm-5.3-flash")

На что обратить внимание:

  • Вставка снизу вверх. Функции сортируются по номеру строки в обратном порядке, поэтому вставка в конце файла не сдвигает номера строк для функций выше.
  • Отступ берётся у первой инструкции тела (col_offset), а не жёстко «4 пробела»: так работают методы классов и вложенные функции. Декораторы и многострочные сигнатуры тоже не мешают — точка вставки считается от тела, а не от строки def.
  • Проверка ast.parse перед записью. Если что-то пошло не так, скрипт упадёт с ошибкой, а исходный файл останется нетронутым.
  • Модель задаётся через DOC_MODEL. Начните с glm-5.3-flash как самой дешёвой и сравните на 20–30 функциях с claude-sonnet-5 или gpt-5.6-sol: какой вариант меньше правят на ревью, тот и оставьте. Актуальный список моделей — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете.

Запуск: python3 auto_doc.py src/module.py src/utils.py, затем git diff — и ревью как у обычного изменения. Подробнее о клиенте — в руководстве по Python SDK.

README для модуля

Тот же подход годится для краткого описания модуля. Чтобы не платить за весь код, отправляйте только сигнатуры и существующие docstring:

bash
def module_outline(path: str) -> str:
    with open(path, encoding="utf-8") as f:
        tree = ast.parse(f.read())
    parts = [ast.get_docstring(tree) or ""]
    for node in tree.body:
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
            header = ast.unparse(node).splitlines()[0]
            parts.append(f"{header}\n    {ast.get_docstring(node) or ''}")
    return "\n\n".join(parts)


def generate_readme_section(path: str) -> str:
    prompt = (
        "Write a short README section in Markdown for this Python module: "

Запуск через pre-commit

Вызов API на каждом коммите замедляет работу, поэтому удобнее держать хук в ручной стадии manual — по документации pre-commit она не срабатывает автоматически. Файл .pre-commit-config.yaml:

bash
repos:
  - repo: local
    hooks:
      - id: auto-docstring
        name: Generate missing docstrings
        entry: python scripts/auto_doc.py
        language: python
        additional_dependencies: [openai]
        types: [python]
        stages: [manual]

Запуск по требованию: pre-commit run auto-docstring --hook-stage manual --all-files.

Запуск в GitHub Actions

Workflow для pull request: генерирует docstring только в изменённых файлах и коммитит результат в ветку PR. Ключ хранится в секрете репозитория ZUKKO_API_KEY.

bash
name: auto-docstrings

on:
  pull_request:
    paths:
      - "**.py"

permissions:
  contents: write

jobs:
  docstrings:
    if: github.event.pull_request.head.repo.full_name == github.repository
    runs-on: ubuntu-latest

Детали: ref: github.head_ref нужен, чтобы пушить в ветку PR, а не в служебный merge-коммит; условие if пропускает PR из форков, где секреты недоступны; коммит создаётся, только если файлы изменились. Для CI заведите отдельный ключ с дневным лимитом и списком разрешённых моделей — см. лимиты ключей.

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

Допущения: промпт с требованиями к стилю — около 100 токенов, функция — около 300, итого 400 токенов входа; docstring — около 150 токенов выхода. На 1000 функций: 400 000 входа и 150 000 выхода.

МодельФормула на 1000 функцийИтогоНа 5000 функций
glm-5.3-flash ($0.04 / $0.04)(400 000 × 0.04 + 150 000 × 0.04) / 1 000 000$0.022$0.11
claude-sonnet-5 ($0.3 / $0.3)(400 000 × 0.3 + 150 000 × 0.3) / 1 000 000$0.165$0.825

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

Частые вопросы

Может ли модель описать функцию неверно?

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

Как обновлять docstring, когда меняется код?

Скрипт трогает только функции без docstring, поэтому существующие тексты он не перезапишет. Для обновления храните хеш тела функции рядом с кодом (например, в JSON-файле) и перегенерируйте docstring, когда хеш изменился:

bash
import hashlib

def body_hash(node: ast.AST) -> str:
    body = node.body[1:] if ast.get_docstring(node) is not None else node.body
    text = "\n".join(ast.unparse(stmt) for stmt in body)
    return hashlib.sha256(text.encode()).hexdigest()

Может ли вставка сломать код?

Скрипт проверяет результат через ast.parse перед записью, поэтому синтаксически сломанный файл он не сохранит. Сам docstring на выполнение функции не влияет — это строка, которая попадает в атрибут __doc__. Функции, у которых тело записано на одной строке с def, скрипт пропускает.

Учитываются ли type hints?

Да: модель получает исходный код функции целиком, вместе с аннотациями типов, и может описать параметры с их типами. Если аннотаций нет, типы в docstring будут предположением — проверяйте их на ревью.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.