Inicio / Artículos / Validar la salida de un LLM con un grafo de conocimiento ejecutable y dos compuertas

Validar la salida de un LLM con un grafo de conocimiento ejecutable y dos compuertas

RelationalAI como capa de verificación: afirmaciones estructuradas del LLM, validación de esquema, un grafo de política de reembolsos y una segunda compuerta que rechaza respuestas contrarias a las reglas de negocio.

1546 palabras

Fluido no es lo mismo que correcto

Los modelos de lenguaje escriben respuestas fluidas, pero la fluidez no garantiza que sean correctas. Supongamos que un modelo dice: «Este cliente tiene derecho a un reembolso completo». Suena bien. Pero el modelo puede haberse inventado una política, haber leído mal una fecha, haberse saltado una excepción o haber supuesto datos que faltaban. En flujos de cara al cliente eso es inaceptable, así que generar lenguaje y comprobar hechos tienen que ser tareas separadas.

Hay un precedente real. En Moffatt v. Air Canada, 2024 BCCRT 149, el chatbot de una aerolínea dijo que una tarifa por duelo podía solicitarse después del viaje, cuando la política real de la aerolínea no admitía solicitudes retroactivas. El Civil Resolution Tribunal de Columbia Británica declaró a Air Canada responsable de tergiversación negligente y le ordenó pagar daños, intereses y tasas. La lección: en cuanto un sistema de cara al cliente cita una política, la empresa puede quedar obligada por ella.

El objetivo, entonces, es conservar la flexibilidad de un LLM y a la vez tener algo que verifique los hechos antes de que la respuesta llegue al usuario. El enfoque que se describe aquí usa RelationalAI como grafo de conocimiento ejecutable que valida las afirmaciones estructuradas que produce un LLM.

Un bucle de agente con dos compuertas

El patrón procede de una charla de Frank Coyle, de UC Berkeley, en la AI Engineer World's Fair, sobre cómo combinar LLM con validación y verificación. El núcleo es un bucle de agente con una compuerta a cada lado de la ejecución de herramientas. Funciona así:

1. The user asks a question.
2. The LLM decides whether it needs to call a tool.
3. Before the tool runs, the system validates the tool input.
4. The tool runs against an authoritative system.
5. After the tool runs, the system validates the output.
6. The result is appended back into the conversation.
7. The loop repeats until the model produces a final answer.

En forma de diagrama:

LLM proposes an action
   |
Gate 1 validates the tool input
   |
Tool runs
   |
Gate 2 validates the tool output
   |
LLM continues or finishes

La compuerta 1 comprueba la forma. Pregunta si el modelo produjo una petición bien formada, y eso es tarea de la validación de esquema: antes de ejecutar ninguna herramienta, confirmar que están los campos obligatorios y que los valores encajan con tipos de afirmación que el sistema reconoce.

La compuerta 2 comprueba el fondo, es decir, si el resultado concuerda con los hechos y las reglas del dominio. Justo en ese punto resulta útil un grafo de conocimiento ejecutable.

Qué aporta RelationalAI

Con RelationalAI se coloca un grafo de conocimiento ejecutable sobre tus datos. Guarda hechos y, además, permite escribir reglas que producen nuevos hechos a partir de ellos. Eso es lo que separa un grafo en el que se pueden consultar cosas de uno con el que se puede razonar.

En este patrón actúa como capa de verificación. El LLM interpreta lo que quiere el usuario y emite una afirmación estructurada. RelationalAI decide si los hechos y reglas del negocio la respaldan, y la aplicación la acepta, la rechaza o la marca como no respaldada:

LLM output
   |
Pydantic validates the tool-call shape
   |
RelationalAI derives the authoritative value
   |
Claim is marked entailed, contradicted, or unsupported

Un ejemplo concreto muestra cómo encaja todo.

Paso 1: obtener afirmaciones estructuradas del modelo

El LLM no puede responder solo con prosa libre. También tiene que generar afirmaciones estructuradas que se puedan comprobar. Por ejemplo:

{
  "claim_type": "refund_method",
  "subject_id": "A-91",
  "value": "ORIGINAL_PAYMENT",
  "evidence": "Refund policy for GOLD uses ORIGINAL_PAYMENT."
}

Así el sistema tiene algo concreto que probar. La pregunta pasa de «¿suena bien este párrafo?» a «¿es cierta esta afirmación concreta para este pedido concreto?». Esa es la compuerta 1 en acción. Un tipo de afirmación desconocido o un campo obligatorio ausente detienen la llamada a la herramienta antes siquiera de consultar el grafo.

Paso 2: codificar la política de reembolsos en el grafo

Después, RelationalAI modela la lógica de negocio de referencia. La demo usa un pequeño dominio de reembolsos con tres conceptos: Customer, Order y RefundPolicy. Las relaciones y las propiedades derivadas describen cómo se conectan:

- an order belongs to a customer
- a refund policy applies to a customer tier
- refund eligibility depends on final-sale status and return window
- refund amount depends on eligibility and restocking fee
- refund method comes from the applicable policy

A partir de esos hechos y reglas, el grafo deriva los valores que hay que comprobar:

- customer tier
- refund eligibility
- refund amount
- restocking fee
- refund method
- return window
- whether shipping is refundable
- whether the order was defective

No hay que confiar en que el LLM invente o recuerde estos valores; debe consultarlos al grafo. Digamos que los hechos conocidos son:

Order A-91 belongs to Customer C-184
Customer C-184 has tier GOLD
GOLD customers have a 30-day return window

RelationalAI puede derivar entonces:

Order A-91 is refund eligible
Order A-91 refund amount is 129.99
Order A-91 refund method is ORIGINAL_PAYMENT

El grafo se convierte en una fuente de verdad ejecutable. Una versión abreviada del modelo se ve así:

model = Model("refund_policy_kg")

Customer = model.Concept("Customer", identify_by={"customer_id": String})
Order = model.Concept("Order", identify_by={"order_id": String})
RefundPolicy = model.Concept("RefundPolicy", identify_by={"policy_id": String})

Customer.tier = ...

Order.customer = ...
Order.amount = ...
Order.final_sale = ...
Order.request_age_days = ...

RefundPolicy.tier = ...
RefundPolicy.return_window_days = ...
RefundPolicy.refund_method = ...

Luego vienen los valores derivados que consultará la compuerta 2:

Order.expected_refund_method = model.Property(
    f"{Order} has {String:expected_refund_method}"
)
Order.expected_eligible_for_refund = model.Property(
    f"{Order} has {Boolean:expected_eligible_for_refund}"
)

model.where(
    Order.customer(Customer),
    Customer.tier == RefundPolicy.tier,
).define(
    Order.expected_refund_method(RefundPolicy.refund_method)
)

model.where(
    Order.customer(Customer),
    Customer.tier == RefundPolicy.tier,
    Order.final_sale == False,
    Order.request_age_days <= RefundPolicy.return_window_days,
).define(
    Order.expected_eligible_for_refund(True)
)

Ni el derecho al reembolso ni el método de reembolso salen del LLM. Los calcula un grafo de conocimiento construido sobre reglas de negocio, que los expertos del dominio y los desarrolladores pueden revisar, versionar y gobernar.

Paso 3: validar lo que dijo el modelo

Con el grafo listo, se pueden probar las afirmaciones. Supongamos que el modelo responde: «Reembolsa el pedido A-91 como crédito en tienda, ya que el cliente es Platinum». Parece verosímil, pero el grafo puede demostrar lo contrario:

  • el cliente es GOLD y no PLATINUM
  • el método de reembolso es ORIGINAL_PAYMENT y no STORE_CREDIT
  • el pedido no está marcado como defectuoso
  • la política no hace reembolsable el envío

El sistema rechaza la respuesta. No lo decidió un segundo LLM, sino lógica de negocio ejecutable sobre hechos verificados. El código de validación es sencillo a propósito: para cada afirmación, buscar la propiedad esperada correspondiente en RelationalAI, consultarla y comparar el resultado con el valor que propuso el modelo.

EXPECTED_PROPERTY_BY_CLAIM = {
    "refund_method": "expected_refund_method",
    "eligible_for_refund": "expected_eligible_for_refund",
    "refund_amount": "expected_refund_amount",
    "customer_tier": "expected_customer_tier",
}

def check_claims(model, concepts, claims):
    Order = concepts["Order"]
    results = []

    for claim in claims:
        expected_property = getattr(
            Order,
            EXPECTED_PROPERTY_BY_CLAIM[claim.claim_type],
        )

        df = model.where(
            Order.order_id == claim.subject_id
        ).select(
            Order.order_id.alias("subject_id"),
            expected_property.alias("expected"),
        ).to_df()

        if df.empty:
            status = "unsupported"
            expected = None
        else:
            expected = str(df.iloc[0]["expected"])
            status = "entailed" if claim.value == expected else "contradicted"

        results.append((claim, status, expected))

     return results

En producción habría que normalizar los valores con cuidado, tratar de forma explícita las afirmaciones no admitidas y registrar la evidencia detrás de cada decisión. Pero el flujo central sigue siendo el mismo: el LLM propone y RelationalAI verifica.

Por qué repartir así el trabajo

Los LLM son buenos expresándose. Entienden la intención, redactan respuestas y explican resultados en lenguaje natural. No deberían tener la última palabra sobre políticas, precios, elegibilidad, cumplimiento normativo u otros hechos operativos. La secuencia más adecuada:

  1. Deja que el LLM interprete la petición.
  2. Haz que emita afirmaciones estructuradas.
  3. Valida el objeto generado con Pydantic.
  4. Deriva la respuesta de referencia en RelationalAI.
  5. Devuelve solo las afirmaciones que respalda el grafo de conocimiento.

El LLM se encarga del lenguaje y la interacción, y RelationalAI de la verificación.

El bucle en código

A grandes rasgos, el sistema se ve así:

context = [user_question]

while True:
    response = llm.generate(context)

    if response.stop_reason != "tool_use":
        return response.text

    tool_call = response.tool_call

    # Gate 1: validate the model's requested action.
    claims = validate_tool_input(tool_call)

    # Tool execution: query authoritative facts and rules.
    graph = relationalai_model()
    checked_claims = graph.validate_claims(claims)

    # Gate 2: only grounded claims move forward.
    if checked_claims.has_contradictions():
        context.append(rejection_message(checked_claims))
        continue

    context.append(tool_result(checked_claims.entailed()))

El bucle en sí es rutinario. Lo que cuenta es quién se encarga de qué:

  • el modelo sugiere una afirmación o una consulta
  • la primera compuerta inspecciona la forma de la sugerencia
  • RelationalAI determina qué hechos se cumplen
  • la segunda compuerta descarta todo lo que se contradice o no está respaldado
  • solo las afirmaciones que sobreviven llegan a la respuesta

Más allá de los reembolsos

El caso de los reembolsos es pequeño a propósito, pero la misma estructura sirve para cláusulas contractuales, normas regulatorias, lógica de descuentos y precios, límites de la cadena de suministro, quién puede optar a qué producto y a qué tiene derecho cada cliente. Cualquier flujo en el que un LLM hable en nombre de una empresa puede usar una capa de verificación, y cuanto más caro sea el error, más importa separar el lenguaje generado de la verdad ejecutable.

Referencias:

  • https://www.youtube.com/watch?v=Sir59K8ZDPU (la charla de la conferencia)
  • https://www.canlii.org/en/bc/bccrt/doc/2024/2024bccrt149/2024bccrt149.html (Moffatt v. Air Canada)
  • https://docs.relational.ai/ (documentación de RelationalAI)