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

Политика: из чего пайплайну можно состоять

Пишете свой бэкенд?

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

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

from stageflow import Context, Pipeline, Policy, Session

basic = Policy(
    stages={"LoadTicket", "ClassifyByRules", "Template"},
    node_types={"entry", "stage", "condition", "switch", "terminal"},
)

session = Session(id="run-1", pipeline=pipeline, context=Context(vars=vars),
                  policy=basic)

Политику сессии даёт хост, рядом с отладчиком. Из JSON пайплайна она не читается, и ничто в JSON не может её расширить: разрешение, которое граф способен себе поднять, — не разрешение.

None — это не пустое множество

Они означают противоположное, и в этом весь смысл:

Записано Значит
Policy() мнения нет — всё, что зарегистрировано в процессе
Policy(stages=set()) ничего
Policy(stages={"A"}) стадия A и никакая другая

Политика, забывшая перечислить стадии, не должна молча выдать все стадии, которые процесс случайно наимпортировал, — поэтому по умолчанию в каждом поле None, а пустое множество понимается буквально.

Отказ дважды: при валидации и на исполнении

pipeline.validate(basic)
# PipelineValidationError: Pipeline validation failed:
#   work: stage 'LlmReply' is not allowed by the policy
#   loop: node type 'map' is not allowed by the policy

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

Сессия проверяет и на ходу — на каждом узле и перед каждой стадией. Вторая линия нужна графу, собранному в Python и ни разу не провалидированному, а стоит она одного поиска по множеству, который вовсе не делается, если поле None.

Отказ во время исполнения — это PolicyViolationError, он же PermissionError и StageFlowError.

Субпайплайн — не лазейка

Дочерний граф узла subpipeline проверяется дважды, и по разным причинам. При валидации политика обходит объявленные subpipelines прямо в JSON, включая вложенные, и называет место в ошибке:

[inner] w: stage 'LlmReply' is not allowed by the policy

Без этого «сохранить можно» и «запустить нельзя» расходились бы на часы. А на исполнении дочерний граф становится Pipeline и валидируется ещё раз, уже с политикой родителя, которая едет вниз так же, как отладчик, — поэтому вложенный граф не лазейка ни в какой момент.

Как сказать клиенту, что ему доступно

capabilities() принимает политику и сужает ответ до неё:

capabilities(basic)
# {"stageflow": "0.13.0",
#  "node_types": ["condition", "entry", "stage", "switch", "terminal"],
#  "stages": 3}

Именно это бэкенду и стоит отдавать редактору: редактору нужно знать, что может рисовать этот вызывающий, а недоступен тип узла из-за старого ядра или из-за узкого разрешения — различие, которое ему проводить незачем.

Чего политика не делает

Она ограничивает, из чего пайплайн состоит, а не сколько он потребляет. Граф из разрешённых стадий по-прежнему может зациклиться, развернуться веером или работать долго — retry, map по большому списку, ветки parallel.

Это вторая половина, и живёт она в том же объекте: Policy(limits=...), см. Лимиты. Тариф — это одно значение: какие блоки и сколько чего.