Inicio / Artículos / SHACL TDD para agentes GraphRAG: Una regla de límite ejecutivo que detiene las acciones incorrectas

SHACL TDD para agentes GraphRAG: Una regla de límite ejecutivo que detiene las acciones incorrectas

Codifique una política de aprobación ejecutiva con límite de responsabilidad del 30% en formato SHACL, pruébela con pytest, y observe cómo un firewall de ontología detiene al agente en una demostración de contrato por 2,3 millones de dólares.

1411 palabras

Leer notas de arquitectura no es lo mismo que implementar una regla de gobernanza. Este artículo continúa con una comparación tripartita —RAG simple, GraphRAG y GraphRAG integrado con OWL/SHACL/policy— en un contrato de muestra valorado en 2,3 millones de dólares, y se centra en una habilidad transferible: codificar una política empresarial como una estructura, probarla mediante una verificación automática y observar cómo el agente se niega a continuar.

El repositorio Ontology RAG Firewall contiene el vocabulario cont:, los archivos de estructuras y la demostración sin conexión utilizada aquí. Clónelo, confirme que el conjunto funcione correctamente en main, y luego, opcionalmente, reproduzca un commit anterior para experimentar personalmente el ciclo rojo-verde.

Confirme la línea de base en 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

Fijarse en 6318929 es opcional si la última sugerencia verificada del artículo es relevante; la sugerencia de main podría ser ya más reciente.

Una ejecución correcta muestra 18 aprobados. La demostración sin conexión debería ya mostrar una advertencia orientada a los ejecutivos en la sección de indemnizaciones, más o menos con estos términos:

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

Esa advertencia es exactamente lo que introdujo la nueva estructura. El resto explica cómo se implementó mediante un enfoque de desarrollo basado primero en pruebas.

La política que se está codificando

Ya existen once estructuras de nodos en contract_domain_shacl.ttl que abarcan los términos de pago, períodos de notificación, acuerdos de nivel de servicio de disponibilidad, extracción con baja confianza, brechas en las soluciones, renovación automática, falta de cláusulas de indemnización, revisión de daños directos, un ratio máximo del 10% sobre el valor, un caso de alto valor con límite absoluto bajo, y la estructura del ratio ejecutivo que destaca esta explicación.

En el acuerdo de demostración, un techo de $575K representa el 25% de $2.3M, lo cual está por encima del mínimo del 10%, por lo que la forma de ratio anterior no se activa. La forma ejecutiva corrige ese punto ciego en los acuerdos costosos.

El requisito adicional de compras, en lenguaje sencillo:

Cada vez que el valor del acuerdo sea de al menos $500K y el techo de indemnización esté por debajo del 30% de ese valor, un agente debe obtener la aprobación ejecutiva antes de actuar.

Esa oración se convierte en ExecutiveCapRatioShape junto con un par de casos pytest.

Paso 1: Fallar primero

Siempre escriba la afirmación antes del TTL.

En el main actual esas verificaciones ya pasan. Para sentir la falla, cambie a bbeb15e (versión previa), inserte las pruebas, observe que aparezca en rojo, agregue la forma del Paso 2 y luego regrese a main.

Agregue o compare este caso en 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

Ejecute:

pytest tests/test_shacl_constraints.py::test_liability_cap_below_30_percent_on_high_value_contract -v

Cómo se ve el color rojo

Antes de que exista la forma (por ejemplo en bbeb15e):

FAILED tests/test_shacl_constraints.py::test_liability_cap_below_30_percent_on_high_value_contract
AssertionError: ... executive ...

En el main actual, la misma invocación muestra color verde. A continuación viene el TTL en sí; ya se fusionó en la versión principal y se reprodujo para que el patrón sea reutilizable.

Paso 2: Autorizar la forma

Añada contenido a ontologies/contract_domain_shacl.ttl. Mantenga que cont: apunte al espacio de nombres OWL mediante el IRI original de GitHub (evite crear un camino /contract# que no se resuelva):

https://raw.githubusercontent.com/cloudbadal007/ontology-rag-firewall/main/ontologies/contract_domain_owl.ttl#

Los generadores de instancias crean URI bajo la misma base (…#instance/).

¿Reproducir con bbeb15e? Reutilice el URI de prefijo ya presente en el archivo SHACL de esa revisión. En la rama main, prefiera el IRI sin procesar para que la ontología, las formas y las pruebas estén alineadas.

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)
}
""" ;
    ] .

Tres decisiones de diseño son intencionadas:

  • El FILTER que exige el valor >= 500000 limita la regla a acuerdos de alto valor; el mismo porcentaje significa algo diferente en un contrato de $50K.
  • Incluir {?capRatio} en el mensaje para humanos permite a los revisores ver el porcentaje exacto en lugar de una advertencia vaga.
  • El umbral del 30% es una política organizacional: edite el valor literal si Legal lo desea en 40%. El archivo es el documento que refleja dicha política.

Paso 3: Asociar las infracciones a cláusulas legibles

Las formas emiten violaciones de la máquina; el firewall convierte los hits de palabras clave en líneas de informe a nivel de cláusula. En main, EXECUTIVE ya está listado entre los tokens de responsabilidad en firewall.py:

"LiabilityClause": ["LIABILITY", "LOW CONFIDENCE", "LEGAL REVIEW", "HIGH-VALUE", "EXECUTIVE"],

Al reproducir bbeb15e, añada ese token junto a la forma; de lo contrario, la demostración podría calcular la violación sin asociarla a la cláusula de indemnización.

Paso 4: Ejecutar nuevamente el conjunto de pruebas y la demostración

pytest -q                         # 18 passed (entire repo)
pytest tests/test_shacl_constraints.py -v   # 8 passed (this file)
python examples/demo_offline.py

Espere tener todo el conjunto listo en unos pocos segundos. El informe de la demostración incluirá una línea dedicada a la cláusula de indemnización (el conteo de indicadores puede permanecer constante cuando varias violaciones comparten una misma cláusula):

⚠️ 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

Una política codificada, probada y visible. Ese bucle es el que permite escalar a la siguiente regla de dominio.

¿Por qué no “solo pedírselo”?

Inyectar las mismas directrices del 30% en un prompt de sistema falla cuando el asesor reformula la cláusula, cuando la instrucción se pierde en un contexto largo, cuando alguien edita los prompts sin el contexto de cumplimiento, o cuando los auditores preguntan qué versión aplicó qué regla en qué día.

Una forma determinista en RDF tipado existe bajo control de versiones, incluye una prueba de regresión, emite evidencia estructurada que incluye la razón medida, y no puede desaparecer solo porque alguien priorizó la fluidez en otro lugar.

La gobernanza de los sistemas agentes necesita restricciones formales además de la recuperación y generación. La política está en la forma; la prueba está en el test; el texto de la violación es el artefacto de auditoría.

Receta repetible para extensiones

docs/extending.md lo explica en detalle; la forma abreviada es:

  1. Exponer la regla en un lenguaje que el responsable del cumplimiento reconozca.
  • Amplíe OWL solo cuando se requieran nuevos tipos o propiedades.
  • Agregue una forma por regla: algo pequeño y separable es mejor que un monolito.
  • Haga que pytest muestre rojo antes de la forma y verde después.
  • Estado deseado: cada forma tiene una prueba; cada prueba se relaciona con una consecuencia empresarial. Ediciones legales con TTL; pruebas automatizadas en CI; la situación permanece revisable.

    Ruta a seguir más allá de un único acuerdo

    El firewall actual utiliza rutas de un solo acuerdo. Quedan dos brechas en producción: resúmenes por lotes de todo el portafolio (examples/demo_batch_processing.py sirve como ejemplo), y un contexto de vendedor multi-hop (con datos de almacenamiento e incidentes) una vez que exista un grafo de propiedades vinculado, no una URL temporal.

    Hasta entonces, practique este ciclo: escriba la forma, pruébela, observe los resultados y luego aplique el mismo patrón al siguiente dominio.

    Mantenga un registro auxiliar para cada forma: propietario, fecha de entrada en vigor, ID del memorando de origen y ID del nodo pytest, de modo que una línea de git blame se convierta en un rastro de auditoría. Cuando cambien los umbrales, versione la cadena de texto y agregue pruebas de límite para que los criterios antiguos no vuelvan a aplicarse silenciosamente. Trate los mapas palabra clave→cláusula como una interfaz API: tome capturas de pantalla de la salida de las pruebas en CI para que los refactores no puedan eliminar EXECUTIVE de la lista de tokens de indemnización sin que se genere un trabajo fallido. Prefiera formas adicionales en lugar de editar bloques SPARQL compartidos; las formas independientes se restablecen sin problemas cuando falla un experimento de política. Finalmente, publique la proporción medida en cada advertencia dirigida a los usuarios: los revisores confían más en números que pueden volver a calcular a partir del RDF que en mensajes genéricos como “necesita aprobación”.

    Lecturas relacionadas

  • RDF a GraphRAG: Capas prácticas de ontología con ejemplos de VOO — Desde IRIs y triples pasando por RDFS, OWL y SHACL: cuándo las ontologías superan a las tablas y cómo estabilizan GraphRAG.