
Введение
Галлюцинации — одна из самых известных проблем, с которой могут столкнуться большие языковые модели (LLM) при генерации ответов. Они возникают, когда модель выдаёт фактически неверное, бессмысленное или полностью выдуманное утверждение, обычно из-за нехватки внутренних знаний по теме.
Хотя за последние годы появилось много решений для борьбы с галлюцинациями, методологическим фреймворкам для их внутренней диагностики уделялось сравнительно меньше внимания. Недавнее исследование исследователей Amazon предлагает использовать графы знаний для анализа и выявления галлюцинаций в LLM. Представленный фреймворк получил название GraphEval.
В этой статье мы на практическом примере с лёгковесным кодом разберём концептуальные основы GraphEval, чтобы вы могли легко воспроизвести его на своей машине.
GraphEval в двух словах
GraphEval использует графы знаний для выявления и подсветки галлюцинаций в ответах языковых моделей. В отличие от классических метрик, дающих одну обобщённую оценку (например, точность или уверенность), GraphEval применяет двухэтапный процесс с акцентом на объяснимость — он не просто говорит, что ответ неверен, а указывает, где именно произошла галлюцинация.
Для этого GraphEval выполняет два шага:
- Построение графа знаний из сгенерированного ответа модели. Граф состоит из семантических троек вида (Субъект, Отношение, Объект), где субъекты и объекты — узлы, а отношения — рёбра между ними.
- Проверка каждой тройки на соответствие исходному контексту (эталонному массиву знаний) с помощью модели вывода на естественном языке (NLI). Если тройка не может быть логически выведена из контекста по оценке NLI-движка — она противоречит ему или нейтральна — она помечается как галлюцинация.
Иллюстрация GraphEval на примере кода
Перед началом кода, симулирующего применение GraphEval, убедимся, что установлены необходимые библиотеки:
!pip install -q transformers networkx matplotlib torchЦель этого примера — прояснить, как работает методология GraphEval, поэтому мы заменим вычислительно затратные этапы реального сценария упрощёнными симуляциями.
Мы сымитируем эталонную базу знаний (контекст), содержащую фактическую информацию. В продакшене такой контекст, например, извлекался бы из векторной базы данных системы retrieval-augmented generation (RAG). Для простоты мы создадим контекст вручную и сохраним в переменной source_context.
# Эталонный контекст, предоставленный LLM
source_context = (
"GraphEval is a hallucination evaluation framework based on representing information "
"in Knowledge Graph (KG) structures. It acts as a pre-processing step and utilizes "
"out-of-the-box NLI models to detect factual inconsistencies."
)Теперь предположим, что следующий ответ сгенерировала LLM на промт «объясни кратко, что такое GraphEval». Для инициализации первого этапа оценки мы попросим вспомогательную LLM построить граф знаний из этого ответа. Сам ответ и промт для извлечения графа показаны ниже:
# Сгенерированный ответ, который мы хотим оценить (содержит галлюцинацию)
llm_output = (
"GraphEval is an evaluation framework that uses Knowledge Graphs. "
"It requires a highly expensive, enterprise-level server farm to operate."
)
# Шаблон промта, который теоретически передавался бы локальной/бесплатной LLM
KG_EXTRACTION_PROMPT = f"""
You are an expert information extractor. Extract the core information
from the following text as a Knowledge Graph.
Return the output strictly as a Python list of tuples in the format:
(Subject, Relationship, Object).
Text: {llm_output}
"""Опять же для упрощения, чтобы избежать тяжелой вычислительной нагрузки от запуска большой модели, предположим, что мы получили следующие тройки графа:
# Симулированное извлечение (чтобы не запускать массивную LLM локально)
extracted_triples = [
("GraphEval", "is", "evaluation framework"),
("GraphEval", "uses", "Knowledge Graphs"),
("GraphEval", "requires", "expensive enterprise server farm")
]
print("Извлечённые тройки:")
for t in extracted_triples:
print(t)Вывод:
Извлечённые тройки:
('GraphEval', 'is', 'evaluation framework')
('GraphEval', 'uses', 'Knowledge Graphs')
('GraphEval', 'requires', 'expensive enterprise server farm')Мы специально добавили тройку, которая является чистой галлюцинацией (никакого дорогого серверного кластера не требуется), чтобы показать, как последующий NLI-процесс её обнаружит.
Теперь перейдём к главному — процессу NLI. Следующий фрагмент кода — ключевой для идей GraphEval. Он использует предобученную NLI-модель с Hugging Face (модель общедоступна, токен не нужен) и сравнивает каждую тройку с эталонным контекстом. Если NLI-модель для тройки не предсказывает «entailment», она помечается как галлюцинация.
from transformers import pipeline
# Загружаем открытую NLI-модель
print("Загрузка DeBERTa NLI-модели...")
nli_evaluator = pipeline("text-classification", model="cross-encoder/nli-deberta-v3-small")
def evaluate_triple(context, triple):
subject, relation, obj = triple
hypothesis = f"{subject} {relation} {obj}"
# Проверяем, следует ли гипотеза из контекста
result = nli_evaluator({"text": context, "text_pair": hypothesis})
# NLI-модели обычно выдают: 'entailment', 'neutral' или 'contradiction'
label = result['label'].lower()
# В GraphEval всё, кроме 'entailment', считается галлюцинацией
is_hallucinated = label != 'entailment'
return is_hallucinated, label, hypothesis
# Запускаем пайплайн оценки
evaluation_results = []
print("\n--- Результаты GraphEval ---")
for t in extracted_triples:
is_hallucinated, nli_label, hypothesis = evaluate_triple(source_context, t)
evaluation_results.append((is_hallucinated, nli_label))
status = "🚨 ГАЛЛЮЦИНАЦИЯ" if is_hallucinated else "✅ ДОСТОВЕРНО"
print(f"{status} | Тройка: {t} | NLI: {nli_label}")Вывод:
--- Результаты GraphEval ---
✅ ДОСТОВЕРНО | Тройка: ('GraphEval', 'is', 'evaluation framework') | NLI: entailment
✅ ДОСТОВЕРНО | Тройка: ('GraphEval', 'uses', 'Knowledge Graphs') | NLI: entailment
🚨 ГАЛЛЮЦИНАЦИЯ | Тройка: ('GraphEval', 'requires', 'expensive enterprise server farm') | NLI: neutralКак и ожидалось, последняя тройка в графе знаний определена как галлюцинация.
В завершение добавим визуализацию графа знаний исходного ответа LLM вместе с результатами проверки:
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
def visualize_grapheval(triples, eval_results):
G = nx.DiGraph()
edge_colors = []
for (triple, res) in zip(triples, eval_results):
sub, rel, obj = triple
is_hallucinated = res[0]
G.add_node(sub)
G.add_node(obj)
G.add_edge(sub, obj, label=rel)
# Раскрашиваем рёбра по результатам NLI
edge_colors.append('red' if is_hallucinated else 'green')
# Настройка сюжета
plt.figure(figsize=(10, 6))
pos = nx.spring_layout(G, seed=42)
# Узлы
nx.draw_networkx_nodes(G, pos, node_color='lightblue', node_size=2500)
nx.draw_networkx_labels(G, pos, font_size=10, font_weight='bold')
# Рёбра и подписи
nx.draw_networkx_edges(G, pos, edge_color=edge_colors, width=2.5, arrowsize=20)
edge_labels = nx.get_edge_attributes(G, 'label')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color='black')
# Легенда
green_patch = mpatches.Patch(color='green', label='Достоверно (Entailment)')
red_patch = mpatches.Patch(color='red', label='Галлюцинация (Neutral/Contradiction)')
plt.legend(handles=[green_patch, red_patch], loc='lower right')
plt.title("Карта галлюцинаций GraphEval", fontsize=14, fontweight='bold')
plt.axis('off')
plt.tight_layout()
plt.show()
# Отрисовываем граф
visualize_grapheval(extracted_triples, evaluation_results)Результат визуализации:

Заключение
GraphEval — это методология оценки, предложенная для обнаружения и локализации корневых причин галлюцинаций в ответах LLM. В статье мы превратили ключевые принципы и этапы метода в смоделированный практический сценарий, чтобы лучше понять его полезность и возможности для внедрения в реальных системах.