Проверка ответов LLM исполняемым графом знаний и двумя шлюзами
RelationalAI как слой верификации: структурированные утверждения LLM, проверка схемы, граф политики возвратов и второй шлюз, который отклоняет ответы, противоречащие бизнес-правилам.
Гладко — не значит верно
Языковые модели пишут гладкие ответы, но гладкость не гарантирует правоты. Допустим, модель говорит: «Этот клиент имеет право на полный возврат». Звучит нормально. Но модель могла выдумать политику, неправильно прочитать дату, пропустить исключение или додумать недостающие факты. В сценариях, где модель общается с клиентами, это недопустимо, поэтому генерация текста и проверка фактов должны быть разными задачами.
Реальный прецедент есть. В деле Moffatt v. Air Canada, 2024 BCCRT 149, чат-бот авиакомпании сообщил, что льготный тариф в связи с утратой близкого можно запросить после поездки, хотя настоящая политика авиакомпании не допускала запросов задним числом. Трибунал по гражданским спорам Британской Колумбии признал Air Canada ответственной за небрежное введение в заблуждение и обязал выплатить ущерб, проценты и сборы. Вывод: как только система, работающая с клиентами, цитирует политику, компанию могут заставить её соблюдать.
Значит, цель — сохранить гибкость LLM и при этом поручить кому-то проверку фактов до того, как ответ дойдёт до пользователя. Описанный подход использует RelationalAI как исполняемый граф знаний, который проверяет структурированные утверждения, сгенерированные LLM.
Цикл агента с двумя шлюзами
Паттерн взят из доклада Фрэнка Койла из Калифорнийского университета в Беркли на AI Engineer World's Fair о сочетании LLM с валидацией и верификацией. В основе — цикл агента со шлюзом по обе стороны от выполнения инструмента. Работает он так:
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.
В виде схемы:
LLM proposes an action
|
Gate 1 validates the tool input
|
Tool runs
|
Gate 2 validates the tool output
|
LLM continues or finishes
Шлюз 1 проверяет форму. Он выясняет, сформировала ли модель корректный запрос, и это задача для валидации схемы: прежде чем запускать какой-либо инструмент, убедиться, что обязательные поля на месте, а значения соответствуют типам утверждений, которые система знает.
Шлюз 2 проверяет суть, то есть согласуется ли результат с фактами и правилами предметной области. Именно здесь исполняемый граф знаний и полезен.
Что даёт RelationalAI
RelationalAI позволяет положить исполняемый граф знаний поверх ваших данных. Он хранит факты, а вы ещё и пишете правила, которые выводят из них новые факты. Это и отличает граф, в котором можно что-то посмотреть, от графа, с помощью которого можно рассуждать.
В этом паттерне он служит слоем верификации. LLM интерпретирует, чего хочет пользователь, и выдаёт структурированное утверждение. RelationalAI решает, подкрепляют ли его бизнес-факты и правила, после чего приложение принимает утверждение, отклоняет его или помечает как неподтверждённое:
LLM output
|
Pydantic validates the tool-call shape
|
RelationalAI derives the authoritative value
|
Claim is marked entailed, contradicted, or unsupported
Как всё это складывается вместе, покажет конкретный пример.
Шаг 1: получить от модели структурированные утверждения
LLM не может отвечать одной лишь свободной прозой. Она также должна выдавать структурированные утверждения, которые можно проверить. Например:
{
"claim_type": "refund_method",
"subject_id": "A-91",
"value": "ORIGINAL_PAYMENT",
"evidence": "Refund policy for GOLD uses ORIGINAL_PAYMENT."
}
Так у системы появляется что-то конкретное для проверки. Вопрос меняется с «звучит ли этот абзац правильно?» на «верно ли это конкретное утверждение для этого конкретного заказа?». Это и есть работа шлюза 1. Незнакомый тип утверждения или пропущенное обязательное поле останавливают вызов инструмента ещё до обращения к графу.
Шаг 2: закодировать политику возвратов в графе
Дальше RelationalAI моделирует авторитетную бизнес-логику. В демо используется небольшая предметная область возвратов с тремя понятиями: Customer, Order и RefundPolicy. Связи и производные свойства описывают, как они соотносятся:
- 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
Из этих фактов и правил граф выводит значения, которые нужно проверить:
- customer tier
- refund eligibility
- refund amount
- restocking fee
- refund method
- return window
- whether shipping is refundable
- whether the order was defective
LLM нельзя доверять придумывать или вспоминать эти значения, она должна запрашивать их у графа. Допустим, известны такие факты:
Order A-91 belongs to Customer C-184
Customer C-184 has tier GOLD
GOLD customers have a 30-day return window
Тогда RelationalAI может вывести:
Order A-91 is refund eligible
Order A-91 refund amount is 129.99
Order A-91 refund method is ORIGINAL_PAYMENT
Граф становится источником исполняемой истины. Сокращённая версия модели выглядит так:
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 = ...
Затем идут производные значения, которые будет запрашивать шлюз 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)
)
Ни право на возврат, ни способ возврата не берутся от LLM. Их вычисляет граф знаний, построенный на бизнес-правилах, а эти правила эксперты предметной области и разработчики могут ревьюить, версионировать и контролировать.
Шаг 3: проверить, что сказала модель
Когда граф готов, утверждения можно проверять. Допустим, модель отвечает: «Верните заказ A-91 бонусами магазина, ведь клиент Platinum». Звучит правдоподобно, но граф может показать обратное:
- клиент —
GOLD, а неPLATINUM - способ возврата —
ORIGINAL_PAYMENT, а неSTORE_CREDIT - заказ не помечен как бракованный
- политика не делает доставку возвратной
Система отклоняет ответ. Это решение приняла не вторая LLM, а исполняемая бизнес-логика поверх проверенных фактов. Код валидации намеренно прост: для каждого утверждения найти соответствующее ожидаемое свойство в RelationalAI, запросить его и сравнить результат со значением, которое предложила модель.
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
В продакшене вы бы аккуратно нормализовали значения, явно обрабатывали неподдерживаемые утверждения и логировали доказательства за каждым решением. Но основной поток тот же: LLM предлагает, RelationalAI проверяет.
Зачем так делить работу
LLM хороши в выражении мыслей. Они понимают намерение, черновят ответы и объясняют результаты естественным языком. Но последнее слово в вопросах политики, цен, права на услуги, комплаенса и других операционных фактов не должно оставаться за ними. Последовательность получше:
- Пусть LLM интерпретирует запрос.
- Заставьте её выдавать структурированные утверждения.
- Проверьте сгенерированный объект через Pydantic.
- Выведите авторитетный ответ в RelationalAI.
- Возвращайте только утверждения, которые подтверждает граф знаний.
LLM отвечает за язык и взаимодействие, а RelationalAI — за проверку.
Цикл в коде
В общих чертах система выглядит так:
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()))
Сам цикл рутинный. Важно, кто за что отвечает:
- модель предлагает утверждение или запрос
- первый шлюз проверяет форму предложения
- RelationalAI вычисляет, какие факты верны
- второй шлюз отбрасывает всё, что противоречит фактам или не подтверждено
- в ответ попадают только уцелевшие утверждения
Не только возвраты
Пример с возвратами намеренно маленький, но та же структура работает для условий договоров, регуляторных правил, логики скидок и цен, ограничений цепочки поставок, того, кто имеет право на какой продукт, и того, что положено каждому клиенту. Слой верификации пригодится в любом процессе, где LLM говорит от имени бизнеса, и чем дороже ошибка, тем важнее держать сгенерированный текст отдельно от исполняемой истины.
Источники:
- https://www.youtube.com/watch?v=Sir59K8ZDPU (доклад с конференции)
- https://www.canlii.org/en/bc/bccrt/doc/2024/2024bccrt149/2024bccrt149.html (Moffatt v. Air Canada)
- https://docs.relational.ai/ (документация RelationalAI)