Гайд · TNWS AI

Как проверить разбор Markdown-ответа AI перед публикацией

6 мин—

Тестируем минимальную структуру Markdown от AI-редактора: H2, обязательный раздел, отсутствие H1 и пустых ссылок.

Задача и применимость

Не выпускать сгенерированную справку, если AI вернул заголовок H1 внутри тела, пропустил раздел «Ограничения» или оставил пустую ссылку. Это валидатор конкретного издательского формата, не полный Markdown-парсер. Зафиксируйте допустимую структуру в редакционном контракте: тело статьи содержит H2 «Шаги» и «Ограничения», H1 зарезервирован за заголовком страницы. Кодовые блоки могут содержать строки с #, поэтому их нужно исключить из проверки заголовков. Пустая ссылка вида [текст]() должна останавливать публикацию. Здесь используется локальная тестовая фикстура: значения приведены как пример входа и ожидаемого поведения, а не как измерение реального сервиса. Если ваш проект вызывает внешнюю модель, проверьте документацию выбранного поставщика отдельно и не считайте локальную симуляцию проверкой доступности API.

Названия методов Python, синтаксис GitHub Actions и используемые версии actions сверены с официальными первоисточниками 19.09.2026. Команды в шаблонах следует запускать в собственном репозитории после проверки путей; никакие цены, тарифы и результаты производительности в статье не предполагаются.

Подготовка данных и критерий ошибки

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

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

Входная фикстура, которую можно скопировать

Сохраните строки в обозначенный ниже файл; комментарии с путями — пояснение и не входят в JSONL или CSV. Если пример показывает две версии ответа, прогоните их отдельно, как описано в шагах.

## Шаги

1. Откройте настройки.

## Ограничения

Функция может быть недоступна на устройстве.

Пошаговая реализация

  1. Создайте каталог scripts и файл scripts/evaluate.py; при необходимости добавьте каталог data и пример из предыдущего блока. Убедитесь, что кодировка текстовых файлов — UTF-8.
  2. Скопируйте скрипт ниже и запустите команду python scripts/evaluate.py локально. Для проекта с отдельным модулем замените демонстрационную функцию на импорт приложения. Сначала сохраните исходное проверяемое поведение.
  3. Скопируйте скрипт и выполните оба тестовых случая. При внедрении передайте реальное тело в validate_markdown до POST в CMS; для проверки на PR читайте подготовленный Markdown из файла. Уточните названия H2 в соответствии с собственным шаблоном и отдельно проверяйте наличие ссылок на первоисточники: пустой target — лишь один возможный дефект. Добавьте негативную фикстуру, в которой # расположен внутри fenced code block, чтобы убедиться, что такой код не воспринимается как H1. Для сложного вложенного Markdown используйте полноценный парсер вместо наращивания regex.
  4. Добавьте workflow из следующего блока в .github/workflows/ai-evaluation.yml. Он запускается для pull request и не передаёт секреты коду из PR. Если сетевой интеграционный тест всё же необходим, выведите его в отдельный доверенный workflow и ограничьте доступ к ключу.
  5. Откройте успешный и намеренно неуспешный запуск. Убедитесь, что статус job соответствует коду завершения Python, а сообщение указывает на кейс, который нужно исправлять. После проверки верните корректные данные, не оставляйте искусственный сбой в ветке.
# scripts/evaluate.py
import re

def validate_markdown(body):
    visible = re.sub(r"(?ms)^(~{3}).*?^\1[^\n]*$", "", body)
    headings = re.findall(r"(?m)^## (.+)$", visible)
    missing = {"Шаги", "Ограничения"} - set(headings)
    if missing: raise ValueError("missing H2: " + ", ".join(sorted(missing)))
    if re.search(r"(?m)^# (?!#)", visible):
        raise ValueError("H1 inside body")
    if re.search(r"\[[^\]]+\]\(\s*\)", visible):
        raise ValueError("empty link target")

good = "## Шаги\n\n1. Откройте настройки.\n\n## Ограничения\n\nПроверяйте совместимость."
bad = "# Гайд\n\n## Шаги\n\n[Источник]()"
validate_markdown(good)
try: validate_markdown(bad)
except ValueError as error: print("Expected rejection:", error)
else: raise SystemExit("Invalid Markdown was accepted")
print("Markdown contract verified")

Готовый workflow для проверки в pull request

Файл ниже запускает скрипт через Python. permissions: contents: read оставляет встроенному токену только чтение кода. Если вы уже используете другой CI, сохраняйте тот же негативный тест и ненулевой код выхода, а файл workflow адаптируйте к своей инфраструктуре.

name: AI evaluation
on:
  pull_request:
permissions:
  contents: read
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: '3.13'
      - run: python scripts/evaluate.py

Пример входа и ожидаемого результата

Вход с ## Шаги и ## Ограничения проходит. Вариант с # Гайд и пустой ссылкой отвергается, причём первым сообщением будет missing H2: Ограничения; после добавления раздела валидатор выявит H1, затем пустую ссылку. Это наблюдаемое поведение демонстрационного кода, а не отчёт о запуске на внешней модели. В production проверяйте, что конкретный контракт подходит к бизнес-процессу, и сохраняйте вход/ожидание рядом с именем кейса.

Где этот тест может обмануть

Порядок ошибок зависит от порядка проверок; тест должен требовать не произвольную фразу, а обнаружение дефекта. Регулярное выражение для блоков кода демонстрационное и не поддерживает все варианты вложения и отступов Markdown. Валидатор структуры не подтверждает, что инструкция фактически верна: ссылки и названия меню проверяйте отдельно. Если тест проверяет только формат, не представляйте его как оценку фактической правильности ответа. Если тест проверяет только список ID, не делайте вывода о полноте подтверждающих документов. Разделяйте сбой инфраструктуры, некорректный корпус и нарушение прикладного инварианта: у этих проблем разные действия.

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

Чек-лист финальной проверки

  • У входных данных есть понятный ID и проверенное ожидаемое поведение.
  • Успешная фикстура проходит локально, испорченная падает с понятной причиной.
  • Скрипт запускается по тому же пути в CI, что и в инструкции.
  • Вход, журнал и репозиторий не содержат секретов или реальных персональных данных.
  • Сравнивается актуальная версия кода; PR не подменяет эталон без ревью.
  • Проверены границы именно этого теста: что он обнаруживает и что не проверяет.

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

Почему нельзя просто искать строку "## Шаги"?

Она может встретиться в блоке кода или внутри обычного текста. Проверка ограничивает совпадение началом строки.

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

Извлеките targets парсером Markdown и сверяйте с источниками отдельно; здесь тестируется только пустой target.

Допустимы ли H3?

Да, они не заменяют обязательные H2 и не мешают данному контракту.

Почему не использовать regex для всего Markdown?

У Markdown есть сложные правила экранирования и вложенности; для полного синтаксиса лучше специальный парсер.

Как принять изменение после теста

Попросите второго участника проекта воспроизвести пример из статьи на своей ветке и оценить ожидаемый результат независимо от вывода скрипта. Особенно важно проверить, не изменили ли авторы PR эталон одновременно с функцией так, чтобы ошибка стала невидимой. Сохраните ссылку на конкретный запуск рядом с ревью и повторите тест после объединения изменений, если последующие коммиты затрагивают ту же обработку данных. При обнаружении настоящего сбоя сначала добавьте отдельную синтетическую фикстуру, которая воспроизводит проблему, и лишь потом исправляйте код. Так можно проверить, что исправление действительно удерживается, а тест не оказался формальным.

Официальная документация

Источники проверены 19.09.2026: 1, 2, 3, 4. Перед переносом на другой runtime ещё раз проверьте версии и актуальные поля документации.

Читайте также

Комментарии

Пока тихо. Скажите первое слово