🧠 Декларативные AI‑сценарии для GitHub Actions и локальной разработки
🚧 Альфа‑версия. Возможны изменения DSL и API.
- 🚀 Что это и чем полезен
- 📦 Установка
- ⚡ Быстрый старт: AI‑ревью в GitHub Actions
- 📚 Быстрый старт: генерация README в GitHub Actions
- 🧠 Архитектура и ключевые концепции
- 📜 YAML‑DSL: структура сценария
- 🔧 Типы шагов
- 🗣️ Промпты для
llm‑шагов - 📦 Входы/выходы и артефакты
- 🛡️ Approvers и безопасность инструментов
- 🤖 Пример: сценарий AI‑ревью
PipelineAI (PAI) — это CLI‑агент на Go, который исполняет сценарии из YAML с помощью LLM и shell‑команд.
Главная идея: вы декларативно описываете сценарий работы агента (шаги, зависимости, инструменты), а рантайм детерминированно исполняет его локально или в CI (например, GitHub Actions).
Почему это удобно:
- Декларативные сценарии — сложное поведение агента (ревью, рефакторинг, документация) описывается в YAML в виде инструкций и наборов парамеров.
- Простое подключение инструментов — достаточно описать функцию в
functionsс JSON‑схемой параметров и shell‑шаблоном, и LLM сможет её вызывать какtool. - Чёткий DAG и артефакты — планировщик строит граф по
needs, разворачиваетmatrix‑шаги и складывает всё в.agent/artifacts. Тем самым можно описывать даже сложные сценарии с множеством зависимостей. - CI‑friendly — сценарии можно запускать в GitHub Actions и локально; есть готовый пример для AI‑ревью pull request’ов и ответов на вопросы с тегом
#ai.
go install github.com/zvuk/pipelineai/cmd/pipelineai@latest
pipelineai --helpPAI читает настройки LLM из .env (если он есть) и переменных окружения (см. .tpl.env):
LLM_BASE_URL— URL OpenAI‑совместимого API (например,http://localhost:1234/v1для LM Studio илиhttp://localhost:8000/v1для vLLM)LLM_MODEL— идентификатор модели (в примерах:openai/gpt-oss-20b)LLM_API_KEY— ключ (опционально для локальных рантаймов)LLM_REQUEST_TIMEOUT— таймаут запроса к LLM
Для локальной сборки из этого репозитория рекомендуется make build: цель автоматически подтягивает libtokenizers.a и включает exact tokenizer backend (tokenizers_hf) для model-specific подсчёта токенов.
Если собирать бинарь напрямую через go build/go install без тега tokenizers_hf, PAI всё равно будет работать, но для неизвестных или неподдержанных exact-backend моделей переключится на byte-based fallback для предрасчётов.
Самый простой способ познакомиться с PAI — подключить готовый reusable workflow из этого репозитория:
.github/workflows/reusable-ai-review.yml + сценарий ci/configs/ai-review.yml.
В репозитории, где нужно AI‑ревью, добавьте workflow, который вызывает reusable workflow PipelineAI:
name: AI review (PAI)
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
ai_review:
uses: zvuk/pipelineai/.github/workflows/reusable-ai-review.yml@main
with:
llm_base_url: http://localhost:1234/v1
llm_model: openai/gpt-oss-20b
review_parallel: 8
review_ignore: "**/generated/**,**/mocks/**"
secrets:
llm_api_key: ${{ secrets.LLM_API_KEY }}Примечания:
llm_api_keyможно не задавать, если ваш LLM не требует ключ.- Если вы используете
localhost(LM Studio / vLLM), workflow должен выполняться на self-hosted runner, где запущен LLM.
LLM_BASE_URL,LLM_MODEL,LLM_API_KEY— параметры LLM (в reusable workflow пробрасываются через inputsllm_base_url,llm_model,llm_api_key).PAI_REVIEW_IGNORE— список glob‑паттернов через запятую для игнора файлов/директорий в пофайловом ревью (в reusable workflow пробрасывается через inputreview_ignore).- Параллелизм matrix‑шагов задаётся через input
review_parallel(или через флаг CLI--parallel N).
По умолчанию используются ресурсы из PipelineAI (ci/prompts, ci/rules).
Если нужно переопределить промпты/правила в вашем репозитории, задайте абсолютные пути:
PAI_AI_REVIEW_PROMPTS_DIR="$GITHUB_WORKSPACE/ci/prompts"PAI_AI_REVIEW_RULES_DIR="$GITHUB_WORKSPACE/ci/rules"
Workflow запускается на событиях pull request и выполняет:
- пофайловое ревью и публикацию inline‑комментариев;
- summary‑комментарий в PR;
- ответы на комментарии с тегом
#ai.
Артефакты сохраняются в .agent/artifacts и загружаются как GitHub Actions artifacts.
Для автогенерации/обновления README.md можно подключить reusable workflow
.github/workflows/reusable-docs.yml + сценарий ci/configs/readme-generation.yml.
Пример workflow в вашем репозитории:
name: Docs (PAI)
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
docs:
uses: zvuk/pipelineai/.github/workflows/reusable-docs.yml@main
with:
llm_base_url: http://localhost:1234/v1
llm_model: openai/gpt-oss-20b
create_pr: true
secrets:
llm_api_key: ${{ secrets.LLM_API_KEY }}Примечания:
- Если вы используете
localhost(LM Studio / vLLM), workflow должен выполняться на self-hosted runner, где запущен LLM. - Для библиотек можно выставить
is_lib: true(будет использованci/configs/lib-readme-generation.yml).
На уровне рантайма PAI состоит из:
- CLI
pipelineai(см.cmd/pipelineai/main.go); - YAML‑DSL, описывающего:
- глобальный контекст агента (
agent); - дефолтные таймауты и окружение (
defaults); - пользовательские функции/инструменты (
functions); - шаги сценария (
steps);
- глобальный контекст агента (
- планировщика, который строит DAG по
needsи исполняет шаги с учётом зависимостей и параллелизма; - системы артефактов (
pkg/artifacts), которая складывает всё в.agent/artifacts.
Ключевые возможности:
- Шаги
llm— общение с моделью, вызовtools(встроенных и пользовательских). - Шаги
shell— запуск команд в контейнере/локально с явными входами и выходами. - Шаги
plan— этапы планирования и построения стратегии/манифестов перед выполнением основной работы. - Шаги
matrix— поэлементный прогон одного и того же шаблонного шага по списку элементов (файлы, сервисы и т. д.). - Inputs/Outputs — явная передача файлов и значений между шагами.
- Approvers — политика безопасности для
shellиapply_patch.
Базовый каркас сценария (подробно см. ci/configs/ai-review.yml):
version: 1
agent:
name: pai-ai-review
model: "openai/gpt-oss-20b"
artifact_dir: .agent/artifacts
model_context_window: 131072
tool_output_warn_percent: 10
auto_compact_percent: 85
tokenizer_cache_dir: .agent/cache/tokenizers
openai:
base_url: '{{ env "LLM_BASE_URL" "http://localhost:1234/v1" }}'
api_key_env: LLM_API_KEY
defaults:
scenario_timeout: 120m
step_timeout: 120m
tool_timeout: 120s
functions:
# пользовательские инструменты (GitHub API, HTTP, и т. д.)
steps:
# описание шагов сценарияОсновные поля:
name— человекочитаемое имя агента (попадает в логи).model— идентификатор модели Chat Completions.artifact_dir— корень для артефактов сценария (обычно.agent/artifacts).openai.base_url— URL совместимого API.openai.api_key_env— имя переменной окружения с API‑ключом модели.model_context_window— явное переопределение размера контекстного окна модели в токенах.tool_output_warn_percent— порог, после которого tool result сначала заменяется warning вместо полного payload. По умолчанию10.auto_compact_percent— порог автоматического сжатия истории через LLM compaction. По умолчанию85.tokenizer_cache_dir— каталог для кэша model-specific tokenizer файлов.
Дополнительно в шаблонах доступны функции, например:
env "KEY"/env "KEY" "fallback"— безопасное чтение переменных окружения с защитой от утечек секретов.
Позволяет задать:
scenario_timeout— общий таймаут выполнения сценария (запускаpipelineai run).step_timeout— таймаут по умолчанию для любого шага.tool_timeout— таймаут инструментов (shell, пользовательских функций).
Эти значения можно переопределять на уровне конкретного шага.
Функции — это пользовательские tools, которые LLM может вызывать через механизм function calling.
Определяются в корне YAML в списке functions и включаются в шаг через tools_allowed.
functions:
- name: http_request
description: "HTTP вызов через curl"
prompt: |
Tool: http_request
Use this tool to perform HTTP calls. Always pass method and url.
parameters: # JSON Schema (минимум: required)
type: object
properties:
url: { type: string }
method: { type: string, enum: [GET, POST, PUT, PATCH, DELETE] }
headers: { type: array, items: { type: string } }
data: { type: string }
required: [url, method]
implementation:
type: shell
shell_template: |
set -euo pipefail
curl -sS -X {{ .params.method }} {{ .params.url | shq }}Важно:
promptфункции добавляется вsystem_promptшага, когда функция разрешена черезtools_allowed, за исключением некоторых моделей семействаgpt-oss, которые лучше работают без описания инструментов в system prompt.parametersописывают схему аргументов (JSON Schema): модель видит её и сама подбирает аргументы.implementation.shell_templateвыполняется черезbash -lcс контекстом.params; для безопасной подстановки естьshq.
Встроенные инструменты:
shell— выполнение команд;apply_patch— безопасное применение патчей к файлам.
Поведение больших tool outputs:
- если сериализованный результат тула оценивается больше заданного процента окна контекста, агент сначала отдаёт модели warning вместо полного payload;
- модель может либо сузить запрос, либо повторить тот же вызов с
force_full_output: true; - на повторе того же вызова полный результат также возвращается без suppress.
Автоматическое сжатие контекста:
- перед очередным запросом к модели PAI оценивает текущий prompt budget;
- при превышении
agent.auto_compact_percentвыполняется отдельный LLM compaction pass; - старый хвост истории заменяется кратким handoff summary, а актуальный хвост текущего диалога сохраняется.
Любой шаг имеет базовые поля:
id— уникальный идентификатор шага;type—shell,plan,llmилиmatrix;name— человекочитаемое имя;needs— список зависимостей ([<step-id>, ...]);template: true— пометка, что шаг служит шаблоном (используется изmatrix.run.step).
Далее уже зависят от типа шага (см. ниже).
Шаг shell запускает bash‑скрипт:
- id: build_pr_context
type: shell
name: "Сбор PR‑контекста и манифеста файлов"
shell:
dir: "."
run: |
set -euo pipefail
# здесь команды: curl к GitHub API, jq, генерация pr-context.jsonОсобенности:
shell.run— скрипт, обычно в строгом режиме (set -euo pipefail).shell.dir— рабочая директория.timeoutна шаге может переопределитьdefaults.step_timeout.- Через
outputsможно сохранитьstdout/stderrили файлы (см. раздел про артефакты).
Шаг plan используется для построения стратегии выполнения и поддерживает два режима:
plan.engine: shell(по умолчанию) — выполнениеplan.runчерезbash -lc;plan.engine: partition— встроенный движок декларативного разбиения элементов.
- id: plan_review_units
type: plan
name: "Build review units strategy"
plan:
engine: partition
partition:
source_path: ".agent/artifacts/plan/review-candidates.json"
select: "items"
manifest_json_path: ".agent/artifacts/plan/review-units.json"
manifest_yaml_path: ".agent/artifacts/plan/review-units.yaml"
switch_to_buckets_at: 40
bucket_max_items: 4
bucket_max_weight: 700
priority_weight: 220
priority_any_ext: [".go", ".ts", ".py"]
priority_any_glob: ["**/auth/**", "**/security/**"]
non_priority_any_ext: [".md", ".txt"]
outputs:
- id: review_units
type: file
from: path
path: ".agent/artifacts/plan/review-units.yaml"Особенности:
plan.engine=shell:plan.runобязателен и проверяется аналогичноshell.run;plan.engine=partition:plan.partition.*задаёт стратегию приоритизации и чанкования;outputsподдерживаются так же, как уshell(включаяfrom: path);planможно использовать как обычный шаг DAG или какtemplateдляmatrix.
Вход для partition — массив элементов. Обычно это файлы, где каждый элемент содержит:
file_path— путь;item_hash— стабильный хэш версии элемента (например, хэш diff);item_weight— «вес» элемента (например, число изменённых строк).
Дальше движок делает 4 шага:
- Определяет режим:
- если элементов меньше
switch_to_buckets_at— делает по одному юниту на элемент; - если элементов больше/равно порогу — включает bucket-режим.
- если элементов меньше
- Делит элементы на
priorityиnon-priority:priority_any_glob/priority_any_extпомечают элемент как приоритетный;non_priority_any_glob/non_priority_any_extснимают приоритет;- дополнительно учитывается
item_weightиpriority_weight.
- Формирует юниты:
- приоритетные элементы идут отдельными юнитами (чтобы не потерять фокус);
- не приоритетные собираются в группы, пока не достигнут
bucket_max_itemsилиbucket_max_weight.
- Пишет manifest (
items) дляmatrix-шага.
Итог: для небольших изменений вы получаете детальный поэлементный прогон, а для больших — контролируемое число юнитов без взрыва по количеству LLM-вызовов.
Пример для большого MR/PR (code review):
- id: build_review_units
type: plan
plan:
engine: partition
partition:
source_path: ".agent/artifacts/plan/review-candidates.json"
select: "items"
manifest_json_path: ".agent/artifacts/plan/review-units.json"
manifest_yaml_path: ".agent/artifacts/plan/review-units.yaml"
# Переключаемся на bucket-режим с 50 файлов
switch_to_buckets_at: 50
bucket_max_items: 5
bucket_max_weight: 800
priority_weight: 250
# Критичные зоны и типы файлов — в приоритет
priority_any_glob: ["**/auth/**", "**/security/**", "**/billing/**"]
priority_any_ext: [".go", ".ts", ".py", ".sql"]
# Документация/метаданные — в неприоритетные
non_priority_any_ext: [".md", ".txt", ".json", ".yaml", ".yml"]Что будет в таком MR/PR:
- 2-3 файла из
auth/securityскорее всего станут отдельнымиunit_type: file; - массовые изменения в
docs/*.mdи конфигах соберутся в несколькоunit_type: group; matrixзатем выполнит review-шаг по каждому юниту из manifest.
Шаг llm общается с моделью и может вызывать инструменты:
- id: review_file
type: llm
template: true
name: "AI‑ревью файла {{ .matrix.file_path }}"
allow_failure: true
retries: 3
max_tokens: 25000
tools_allowed:
- shell
- github_get_file_diff
- github_post_inline_comment
inputs:
- id: pr_context
type: file
path: "{{ .outputs.build_pr_context.pr_context.path }}"
user_prompt_path: "../prompts/file-review.md"
outputs:
- id: file_review_log
type: file
from: llm_text
path: "{{ .agent.artifact_dir }}/ai-review/files/{{ .matrix.item_id }}.md"Ключевые поля:
tools_allowed— список встроенных и пользовательских инструментов, которые модель может вызывать.inputs— входы (файлы, inline‑шаблоны), доступные в промптах.user_prompt_path/system_prompt— промпты для шага.allow_failure,retries— политика ошибок для шага (важно для нестабильного LLM‑прокси).max_tokens— верхний лимит токенов ответа, пробрасывается в запрос к модели. Chat completions API относит этот параметр к опцуиональным, но при использовании LLM-proxy рекомендуется все же его установить, иначе есть риск получения ошибки 400, когда по какой-то причине LLM-прокси отдает в этом поле отрицательное значение
matrix разворачивает шаблонный шаг по списку элементов (например, файлов):
- id: review_per_file
type: matrix
name: "Пофайловое AI‑ревью изменённых файлов"
needs: [build_pr_context]
matrix:
from_yaml: "{{ .outputs.build_pr_context.file_manifest.path }}"
select: items
item_id: "{{ .item.id }}"
inject:
file_path: "{{ .item.file_path }}"
run:
step: review_fileОсобенности:
matrix.from_yaml— путь к manifest (YAML/JSON), где есть список элементов.matrix.select— путь внутри manifest (items,services.backendи т. п.).matrix.item_id— идентификатор элемента, используемый в артефактах.matrix.inject— ключи, доступные внутри дочернего шага как{{ .matrix.<key> }}.run.step— id шаг‑шаблона (template: true), который клонируется для каждого элемента.
У llm‑шага всегда есть два уровня промптов:
- system‑промпт — «роль» и глобальные правила для модели;
- user‑промпт — конкретная задача для этого шага.
В DSL это задаётся полями в секции llm шага:
steps:
- id: example
type: llm
llm:
system_prompt: |
Ты — помощник для генерации README.
user_prompt: |
Сгенерируй краткое описание проекта.Поддерживаются inline‑варианты и варианты с путём к файлу:
system_prompt/user_prompt— inline‑шаблоны (Go templates), рендерятся с контекстом:agent,step,functions,defaults,inputs,outputs, а также дополнительнымextra(например,.matrix.*);
system_prompt_path/user_prompt_path— путь к файлу с шаблоном:- путь сначала рендерится как шаблон;
- если не абсолютный — считается относительно директории конфига (
.yaml); - содержимое файла затем обрабатывается как шаблон так же, как inline.
Приоритет такой:
- если указан
*_prompt_path— промпт читается из файла; - иначе используется inline
*_prompt.
Особенности:
- если итоговый user‑промпт пустой — шаг считается некорректно сконфигурированным (ошибка на старте);
- если system‑промпт явно не задан (ни через
system_prompt, ни черезsystem_prompt_path) или получился пустым:- PAI подставляет встроенный дефолтный системный промпт из файла
internal/tools/prompts/default_system_prompt.md; - он описывает поведение агента, работу с
AGENTS.md, требования к логам и стилю ответов; - этот дефолтный промпт также рендерится как шаблон (можно использовать те же переменные, что и в пользовательских промптах).
- PAI подставляет встроенный дефолтный системный промпт из файла
Для моделей семейства gpt‑oss PAI дополнительно оборачивает user‑промпт:
- добавляет блок
<user_instructions>с инструкциями изAGENTS.md; - добавляет
<environment_context>(cwd, режим sandbox, shell и т. д.); - затем приклеивает ваш
user_prompt— вам не нужно делать это вручную в YAML.
PAI делает передачу данных между шагами явной:
inputsна шаге описывают, что нужно подготовить (файлы, inline‑строки, артефакты других шагов).outputsописывают, что шаг производит (файлы/логи/комментарии).
Пример:
inputs:
- id: pr_context
type: file
path: "{{ .outputs.build_pr_context.pr_context.path }}"
outputs:
- id: pr_review_comment
type: file
from: llm_text
path: "{{ .agent.artifact_dir }}/ai-review/comment.md"Все файлы и служебные JSON/лог‑файлы кладутся под {{ .agent.artifact_dir }}, обычно .agent/artifacts.
В CI эти директории сохраняются как артефакты джоб.
Через блок approvers можно описать правила для инструментов shell и apply_patch:
- ограничить разрушительные команды (
rm -rf /,git push --force); - ограничить области, где
apply_patchможет создавать/обновлять/удалять файлы; - задать разные политики для всего сценария и отдельных шагов.
Пример (упрощённо):
approvers:
- tool: shell
rules:
- regex: "(?i)rm\\s+-rf\\s+/"
message: "This command is forbidden by policy: destructive delete"
- tool: apply_patch
rules:
- glob_patterns: ["docs/**", "**/*.md"]
allow_create: true
allow_update: true
allow_delete: falseЕсли approvers не указан — по умолчанию инструменты разрешены без ограничений (подходит для локальной разработки или доверенных пайплайнов).
Сценарий ci/configs/ai-review.yml показывает, как собрать реальный production‑pipeline:
- собрать контекст PR и манифест изменённых файлов (
build_pr_context); - запустить пофайловое ревью в
matrix‑шаге (review_per_file+review_file); - собрать summary по всему PR и опубликовать его через GitHub API (
review_summary); - ответить на все комментарии с тегом
#ai(общие и inline) в шагеreview_responses.
Рекомендуется изучить этот файл как образец:
- как описывать пользовательские функции (GitHub tools);
- как связывать шаги через
needsиinputs/outputs; - как конфигурировать ретраи,
allow_failure,max_tokensиtools_allowedдля LLM‑шагов; - как использовать
matrixдля масштабируемого пофайлового ревью.
Дальше вы можете:
- на базе этого сценария собрать свои пайплайны (линтеры, автогенерация документации, миграции и т. д.);
- добавлять новые инструменты в
functionsи включать их вtools_allowedLLM‑шагов без правок кода рантайма; - запускать те же сценарии локально с тем же YAML‑конфигом, что и в CI.
PAI старается быть «прозрачным» слоем над LLM: всё поведение задаётся явным YAML‑конфигом, логи и артефакты лежат в понятной структуре, а интеграция с CI остаётся декларативной и повторяемой.