Что такое «prompt linting» (статический анализ промптов)?
Краткий тезис
Prompt linting — это автоматизированная проверка текстов промптов на потенциальные проблемы без вызова LLM. В отличие от динамического тестирования, линтинг выполняется статически (по исходному коду промпта) и позволяет выявить типовые ошибки: отсутствие обязательных инструкций, слишком длинный контекст, синтаксические ошибки в placeholders, признаки prompt injection и другие аномалии. Инструменты линтинга встраиваются в CI/CD пайплайн, что даёт быструю обратную связь разработчикам AI-агентов.
1. Что такое prompt linting и зачем он нужен
Prompt — это текст, который подаётся на вход LLM вместе с контекстом и инструментами. В RAG|agentic RAG промпты часто собираются динамически (добавляются результаты поиска, история диалога, описание tools) и содержат сложную логику (chain-of-thought, few-shot примеры, формат вывода). Ошибка в промпте может привести к галлюцинациям, игнорированию инструкций, утечке данных или бесконечному циклу вызовов.
Linting (от англ. lint — «ворсинки», мелкие дефекты) — статический анализ кода, обнаруживающий потенциальные проблемы без его исполнения. Применительно к промптам это означает проверку шаблона промпта (набора строк, переменных, логических конструкций) на соответствие наборам правил.
Основные причины внедрения prompt linting:
- Раннее обнаружение ошибок до деплоя агента;
- Автоматизация контроля качества в командах, где промпты пишут не только ML-инженеры;
- Предотвращение инцидентов (например, случайное включение системных промптов в ответ пользователю);
- Документирование лучших практик в виде машиночитаемых правил.
2. Отличие prompt linting от других видов валидации
| Метод | Суть | Когда выполняется | Что проверяет |
|---|---|---|---|
| Prompt linting | Статический анализ текста промпта | До запуска LLM (в IDE, CI) | Структуру, длину, наличие ключевых фрагментов, паттерны |
| Prompt testing | Прогон реальных запросов через LLM и оценка ответов | После деплоя / в QA | Качество генерации, соответствие ожиданиям |
| Guardrails | Динамическая валидация входа/выхода LLM | Во время инференса | Фильтрация запрещённого контента, проверка формата |
| Adversarial testing | Целенаправленная генерация атакующих промптов | Периодически / пентест | Устойчивость к инъекциям, джейлбрейкам |
Prompt linting — самый быстрый и дешёвый этап, так как не требует вызова модели. Он не заменяет остальные методы, но дополняет их как первая линия защиты.
3. Типичные проблемы, которые выявляет prompt linting
3.1 Отсутствие обязательных инструкций
Агенты часто включают в системный промпт фразы «не галлюцинируй», «отвечай только на основе контекста», «если не знаешь — скажи» и т.д. Линтер проверяет, что эти инструкции не удалены случайно при редактировании.
3.2 Проблемы с длиной контекста
- Превышение context window модели (например, >128K токенов для GPT-4);
- Превышение лимита на количество few-shot примеров или шагов chain-of-thought;
- Проверка, что placeholder {context} не заменится на слишком большой блок текста (оценка по статистике среднего размера).
3.3 Синтаксические ошибки в placeholders
В шаблонах промптов часто используются переменные: {query}, {context}, {tools}, {history}. Линтер проверяет:
- Все ли placeholders имеют соответствующую подстановку в коде;
- Нет ли опечаток (
{contex}вместо {context}); - Все ли фигурные скобки закрыты.
3.4 Признаки prompt injection
Статический анализ может обнаружить подозрительные паттерны, часто используемые в атаках:
Ignore all previous instructions;You are now a different bot;- Ignore your system prompt;
- Специальные символы или Unicode-альтернативы (например, латинские буквы, похожие на кириллицу).
3.5 Нарушение политики безопасности
- Наличие ссылок на запрещённые ресурсы;
- Слова-триггеры (ругательства, персональные данные в шаблоне);
- Отсутствие rate limiting или cost guard для инструментов.
4. Методы статического анализа промптов
4.1 Регулярные выражения (regex)
Самый простой подход: набор правил, каждое из которых проверяет наличие или отсутствие определённого шаблона.
import re
def check_injection(prompt: str) -> bool:
patterns = [
r"ignore\s+(all\s+)?(previous|prior)\s+instructions",
r"you\s+are\s+now\s+(a\s+)?(different|new)\s+",
]
for p in patterns:
if re.search(p, prompt, re.IGNORECASE):
return True
return False
Плюсы быстро, просто. Минусы легко обмануть (например, заменить буквы на символы).
4.2 Абстрактное синтаксическое дерево (AST)
Применяется, если промпт хранится в виде структуры (например, Jinja2 шаблон, Chainlit flow, LangChain PromptTemplate). AST позволяет анализировать вложенность, типы переменных, вызовы функций внутри промпта.
# Пример для Jinja2
from jinja2 import Environment
from jinja2.meta import find_undeclared_variables
env = Environment()
ast = env.parse(prompt_template)
undeclared = find_undeclared_variables(ast)
if undeclared:
print(f"Необъявленные переменные: {undeclared}")
4.3 Семантический анализ (embedding-based)
Передовые линтеры сравнивают эмбеддинги промпта с эмбеддингами эталонных «хороших» и «плохих» промптов. Например, можно оценить схожесть с известными атакующими паттернами. Этот подход уже требует небольшой модели, поэтому выходит за рамки чистого статического анализа, но часто включается в инструменты.
4.4 Статистические эвристики
- Доля токенов, приходящихся на контекст vs. инструкции;
- Количество повторяющихся фраз (может указывать на копирование лога);
- Наличие неиспользуемых placeholders (на основе анализа кода, который подготавливает переменные).
5. Инструменты и библиотеки
| Инструмент | Язык | Особенности |
|---|---|---|
| PromptLinter (библиотека) | Python | Проверка длины, placeholders, базовых инъекций; встроенные правила для GPT, Claude |
| Guardrails AI | Python | Статическая и динамическая валидация; декларативные Rail-файлы |
| Lakera Guard API | API | Облачный сервис, включает статический анализ и ML детекцию инъекций |
| Custom CI-скрипты | Bash/Python | Набор regex + AST для конкретного шаблонизатора; легко кастомизировать |
Пример простого линтера на Python с несколькими правилами:
class PromptLinter:
rules = []
@classmethod
def register(cls, name, check, severity="error"):
cls.rules.append((name, check, severity))
@classmethod
def lint(cls, prompt_text: str) -> list[dict]:
issues = []
for name, check, severity in cls.rules:
if not check(prompt_text):
issues.append({"rule": name, "severity": severity})
return issues
# Регистрация правил
PromptLinter.register("has_no_hallucination_instruction",
lambda p: "не галлюцинируй" in p or "don't hallucinate" in p)
PromptLinter.register("placeholder_query_present",
lambda p: "{query}" in p)
PromptLinter.register("length_under_100k",
lambda p: len(p) < 100000) # грубая оценка
issues = PromptLinter.lint("Ответь на вопрос: {quer}")
print(issues)
# [{'rule': 'placeholder_query_present', 'severity': 'error'}, ...]
6. Интеграция в CI/CD и рабочий процесс
Prompt linting должен быть частью пайплайна разработки агента:
- IDE — плагин для VS Code, подсвечивающий ошибки в реальном времени;
- Pre-commit hook — при коммите нового или изменённого промпта;
- CI — запуск линтера на каждый Pull Request;
- Release gate — блокировка деплоя, если есть ошибки уровня
error.
# .github/workflows/prompt-lint.yml
name: Prompt Lint
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run prompt linter
run: python scripts/prompt_linter.py --path prompts/ --severity error
Такой подход гарантирует, что ни один промпт с синтаксической ошибкой или забытой инструкцией не попадёт в продакшен.
7. Примеры lint-правил (production-ready)
| Правило | Описание | Пример срабатывания |
|---|---|---|
no_empty_system_prompt | Системный промпт не может быть пустым | system: "" |
tool_description_exists | Для каждого инструмента есть описание | tool: search(google) # без description |
max_tokens_in_prompt | Полный промпт (с подстановками) не превышает N токенов | Статистическая оценка по среднему количеству токенов на символ |
avoid_echo | Промпт не должен содержать копию запроса пользователя внутри системной части | User query: {query} System: {query}... |
forbidden_keywords | Отсутствие стоп-слов | password, secret, ignore all |
8. Ограничения и ложные срабатывания
- False positives: например, линтер может посчитать {history} за неиспользуемый placeholder, если код использует другой способ вставки.
- Контекстная зависимость: статический анализ не может проверить, насколько хорошо промпт будет работать для конкретного запроса (это задача динамического тестирования).
- Обход regex: злоумышленник может видоизменить промпт так, чтобы правила не сработали (например, разбить слово
ignoreнаi gnoreс нулевой шириной пробела). - Сложность с многомодельными системами: разные модели имеют разные требования к формату промпта (ChatML, Llama2, Claude). Линтер должен быть параметризован.
Лучшая практика — использовать комбинацию статического и динамического анализа, дополненную семантическим детектором на основе ML.
Пет-проект для закрепления
Задача: Разработать библиотеку prompt-lint-cli на Python для статического анализа промптов, используемых в LangChain.
Инструменты:
- Python 3.10+
- regex (расширенные регулярные выражения)
- jinja2 (для AST)
tiktoken(подсчёт токенов для OpenAI)argparse(CLI)
Шаги:
- Разработать структуру правил (класс
Ruleс методомcheck(template: str) -> bool). - Реализовать 7 правил: проверка placeholders, длины в токенах, наличие системной инструкции, отсутствие инъекций, закрытие скобок, отсутствие дублирования, запрещённые ключевые слова.
- Написать CLI, который принимает файл или директорию с
.txtпромптами, выводит отчёт в JSON. - Протестировать на реальных промптах из open-source проектов (например, Dify, R2R).
- Добавить флаг
--config, позволяющий загружать кастомные правила из YAML.
Ожидаемый результат: Рабочий линтер, который можно запустить командой prompt-lint ./prompts/ --severity warning и получить список проблем. Проект можно выложить на GitHub с README и примером CI-интеграции.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 801 | Общая архитектура RAG-агента (почему важен контроль промптов) |
| 803 | Рекуррентный цикл вызова (промпт может быть повреждён на каждом шаге) |
| 804 | Тестирование агентов (динамическое тестирование vs статический линтинг) |
| 806 | Guardrails (дополнение статического анализа динамической валидацией) |
| 802 | Tool calling (проверка формата описаний инструментов) |
Навигация
- Предыдущий: 804
- Следующий: 806
- Индекс: 00. Индекс разборов