SHACL TDD для агентов GraphRAG: одно правило Executive Cap, прекращающее нежелательные действия
Закодируйте политику утверждения руководством с лимитом ответственности в 30% в формате SHACL, докажите её с помощью pytest, и посмотрите, как брандмауэр онтологии остановит агента во время демонстрации контракта на сумму 2,3 млн долларов.
Чтение архитектурных записок — это не то же самое, что внедрение правила управления. В этой статье продолжается трехстороннее сравнение — обычного RAG, GraphRAG и GraphRAG с использованием OWL/SHACL/policy — на примере договора стоимостью 2,3 млн долларов; основное внимание уделяется переносимой навыку: кодировке бизнес-правила в виде структуры, её проверке с помощью автоматизированного инструмента и отказу агента продолжать работу.
В репозитории Ontology RAG Firewall хранятся словарь cont:, файлы с структурами и офлайн-демо, использованные здесь. Клонируйте его, убедитесь, что всё работает корректно в ветке main, а затем, по желанию, запустите более старый коммит, чтобы лично увидеть процесс переключения между состояниями «красный» и «зелёный».
Проверьте базовое состояние в ветке main
git clone https://github.com/cloudbadal007/ontology-rag-firewall
cd ontology-rag-firewall
pip install -e ".[dev]"
pytest -q # 18 passed (full suite)
python examples/demo_offline.py
Фиксация версии на 6318929 является необязательной, если важен последний проверенный совет из статьи; совет в ветке main может уже быть более свежим.
В режиме здоровой работы отображается 18 пройденных проверок. В офлайн-демо уже должно появиться предупреждение, ориентированное на руководство, в разделе об ответственности, примерно с таким текстом:
Safe to act: 🚫 NO
- Flagged: 5
...
⚠️ EXECUTIVE APPROVAL: Liability cap is below 30% of contract value. Cap ratio: 25.00%. Agent action requires executive sign-off.
...
AGENT ACTION: HALTED. Routed to human review queue.
Total value protected: $2,300,000
Именно такое предупреждение было введено с помощью новой структуры. Остальная часть описывает, как она была сформирована благодаря подходу разработки, основанному на тестировании в первую очередь.
Шаблон политики
Уже одиннадцать шаблонов узлов находятся в файле contract_domain_shacl.ttl и охватывают условия оплаты, сроки уведомления, SLA по времени работы, извлечение данных с низкой степенью уверенности, пробелы в мерах исправления, автопродление, отсутствие положений об ответственности, анализ прямых убытков, коэффициент ограничения 10% от стоимости, случаи с высокой стоимостью и низким абсолютным лимитом, а также коэффициент для руководства, на который обращается внимание в этом обзоре.
В случае демо-сделки верхний лимит в 575 тыс. долларов составляет 25% от 2,3 млн долларов — что выше нижнего порога в 10% — поэтому старая формула коэффициента остается без действий. Формула для руководства устраняет этот недостаток при заключении дорогостоящих соглашений.
Дополнительное требование отдела закупок простыми словами:
Каждый раз, когда стоимость соглашения составляет не менее 500 тыс. долларов, а лимит компенсации меньше 30% от этой суммы, агент обязан получить одобрение руководства перед принятием действий.
Это условие преобразуется в ExecutiveCapRatioShape вместе с двумя тестами pytest.
Шаг 1 — Сначала проверка на сбой
Всегда пишите проверку до TTL.
В текущей версии main эти проверки уже проходят. Чтобы увидеть сбой, перейдите к версии bbeb15e (предварительная форма), вставьте тесты, обратите внимание на красный цвет, добавьте формулу из Шага 2, затем вернитесь к main.
Добавьте или сравните этот случай в tests/test_shacl_constraints.py:
def test_liability_cap_below_30_percent_on_high_value_contract() -> None:
"""
25% cap on a $2.3M contract must trigger ExecutiveCapRatioShape.
Existing shapes (10% ratio, $100K absolute) do not catch 575K / 2.3M.
"""
clause = ExtractedClause(
"test-cap-ratio",
"LiabilityClause",
"text",
{"liabilityCap": 575_000, "liabilityScope": "DirectDamagesOnly"},
0.9,
1,
)
graph = ClauseRDFBuilder().build(clause, 2_300_000)
conforms, violations, _ = SHACLContractValidator().validate(graph)
assert not conforms
assert any(
"30%" in v or "executive" in v.lower() for v in violations
), violations
def test_liability_cap_at_32_percent_no_executive_flag() -> None:
"""32.6% cap on $2.3M should not trigger the 30% executive rule."""
clause = ExtractedClause(
"test-cap-ratio-ok",
"LiabilityClause",
"text",
{"liabilityCap": 750_000, "liabilityScope": "FullDamages"},
0.9,
1,
)
graph = ClauseRDFBuilder().build(clause, 2_300_000)
_, violations, _ = SHACLContractValidator().validate(graph)
cap_ratio_hits = [
v for v in violations if "30%" in v or "executive" in v.lower()
]
assert len(cap_ratio_hits) == 0, cap_ratio_hits
Выполните:
pytest tests/test_shacl_constraints.py::test_liability_cap_below_30_percent_on_high_value_contract -v
Как выглядит красный цвет
До появления формы (например, в bbeb15e):
FAILED tests/test_shacl_constraints.py::test_liability_cap_below_30_percent_on_high_value_contract
AssertionError: ... executive ...
В текущей версии main одинаковый вызов отображается зеленым. Затем следует сам TTL — он уже объединён в основной код, воспроизведён для того, чтобы шаблон можно было использовать повторно.
Шаг 2 — Создание авторства формы
Добавьте содержимое в ontologies/contract_domain_shacl.ttl. Убедитесь, что cont: указывает на пространство имён OWL через исходный IRI GitHub (избегайте создания пути /contract#, который не разрешается):
https://raw.githubusercontent.com/cloudbadal007/ontology-rag-firewall/main/ontologies/contract_domain_owl.ttl#
Создатели экземпляров формируют URI под тем же базовым именем (…#instance/).
Повторная обработка с bbeb15e? Используйте уже имеющийся URI префикса из файла SHACL этой ревизии. Для ветки main предпочтительнее использовать чистый IRI, чтобы онтология, формы и тесты совпадали.
cont:ExecutiveCapRatioShape a sh:NodeShape ;
sh:targetClass cont:LiabilityClause ;
sh:severity sh:Warning ;
sh:message "⚠️ EXECUTIVE APPROVAL: Liability cap is below 30% of contract value. Cap ratio: {?capRatio}%. Agent action requires executive sign-off." ;
sh:sparql [
a sh:SPARQLConstraint ;
sh:select """
PREFIX cont: <https://raw.githubusercontent.com/cloudbadal007/ontology-rag-firewall/main/ontologies/contract_domain_owl.ttl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT $this ?capRatio WHERE {
?contract cont:hasLiabilityClause $this ;
cont:contractValue ?v .
$this cont:liabilityCap ?cap .
BIND((xsd:decimal(?cap) / xsd:decimal(?v) * 100) AS ?capRatio)
FILTER (xsd:decimal(?v) >= 500000)
FILTER (?capRatio < 30)
}
""" ;
] .
Три дизайнерских решения приняты намеренно:
FILTER, требующий значение>= 500000, ограничивает правило сделками с высокой стоимостью; тот же процент имеет другое значение для контракта стоимостью 50 тыс.- Включение
{?capRatio}в текст сообщения позволяет рецензентам видеть точный процент вместо расплывчатого предупреждения. - Порог в 30% является организационной политикой — измените это значение, если юридический отдел требует 40%. Сам файл и является документом, фиксирующим эту политику.
Шаг 3 — Связывание нарушений с понятными пунктами
Формы генерируют нарушения правил машины; брандмауэр преобразует совпадения ключевых слов в строки отчета на уровне предложений. В файле firewall.py в разделе, отвечающем за обязательства, уже указан токен EXECUTIVE внутри блока main:
"LiabilityClause": ["LIABILITY", "LOW CONFIDENCE", "LEGAL REVIEW", "HIGH-VALUE", "EXECUTIVE"],
При повторном запуске bbeb15e необходимо добавить этот токен рядом с соответствующей формой — иначе демо-версия может вычислить нарушение, не связав его с пунктом об ответственности.
Шаг 4 — Перезапуск набора тестов и демо-версии
pytest -q # 18 passed (entire repo)
pytest tests/test_shacl_constraints.py -v # 8 passed (this file)
python examples/demo_offline.py
Полный набор тестов будет готов за несколько секунд. В отчете демо-версии появится отдельная строка, касающаяся пункта об ответственности (количество флагов может оставаться неизменным, если несколько нарушений относятся к одному пункту):
⚠️ EXECUTIVE APPROVAL: Liability cap is below 30% of contract value. Cap ratio: 25.00%. Agent action requires executive sign-off.
AGENT ACTION: HALTED. Routed to human review queue.
Total value protected: $2,300,000
Одна политика — зашифрованная, проверенная и видимая. Именно такой цикл позволяет расширять его на новые правила домена.
Почему не «просто запросить у нее»?
Применение тех же 30%-ных рекомендаций в системный промпт терпит неудачу, когда юрист перефразирует положение, когда инструкция теряется в длинном контексте, когда кто-то редактирует промпты без учета контекста соблюдения правил, или когда аудиторы спрашивают, какая версия применила какое правило в тот или иной день.
Форма в типизированном RDF является детерминистичной, находится под контролем версий, сопровождается тестом на регрессию, генерирует структурированные доказательства, включая измеренное соотношение, и не может исчезнуть только потому, что кто-то стремился к более высокой плавности в других частях.
Управление агентными системами требует формальных ограничений помимо функций поиска и генерации. Политика заключена в форме; доказательства — в тестах; текст нарушения является результатом аудита.
Рецепт повторяемого расширения
docs/extending.md подробно описывает это; краткая форма выглядит так:
- Сформулируйте правило на языке, понятном ответственному за соблюдение правил.
Целевое состояние: у каждой структуры есть тест; каждый тест соответствует какому-либо бизнес-эффекту. Изменения верифицируются с учетом TTL; CI выполняет проверки; структура остается подлежащей анализу.
План развития за пределами одного соглашения
В настоящее время брандмауэр использует пути, основанные на одном соглашении. Остаются две проблемы в производственной среде: подготовка пакетных сводок по всему портфелю (пример — examples/demo_batch_processing.py) и обработка контекста многократных запросов к поставщикам (хранение данных, инциденты) после создания связанной графики свойств — а не использование простых URL.
До тех пор практикуйте следующий цикл: напишите структуру, проверьте ее, наблюдайте за результатами, затем примените ту же схему в следующей области.
Ведите отдельный учетный журнал для каждой структуры — с информацией об владельце, дате вступления в силу, идентификаторе исходной записи и идентификаторе узла pytest — чтобы строка с информацией о изменениях в git превращалась в полную историю аудита. При изменении пороговых значений версионируйте строку сообщения и добавляйте тесты на границы, чтобы старые значения не могли возвращаться незаметно. Рассматривайте карты «ключевое слово → пункт» как интерфейс API: сохраняйте результаты демонстрации в CI, чтобы при рефакторинге нельзя было удалить EXECUTIVE из списка токенов ответственности без сбоя задачи. Предпочитайте создание новых структур вместо редактирования общих SPARQL-объектов; независимые структуры позволяют чисто восстанавливать состояние при неудаче эксперимента с политикой. Наконец, публикуйте измеренные коэффициенты во всех предупреждениях, предназначенных для людей — рецензенты больше доверяют цифрам, которые они могут пересчитать из RDF, чем общим сообщениям вроде «требуется одобрение».