Оценка в магистратуре по гуманитарным наукам основана на атмосфере — я создал недостающий слой, определяющий, какие пары будут вместе.
Как я создал легковесную систему оценки, которая измеряет достоверность, выявляет галлюцинации и преобразует субъективные результаты LLM в воспроизводимые метрики — и всё это на чистом Python.
Делиться

Вкратце:
В этой статье представлена полностью рабочая реализация на чистом Python с реальными результатами бенчмарков.
Большинство команд оценивают ответы LLM, просто читая их и пытаясь угадать. Это не работает при масштабировании.
Настоящая проблема не в том, что модели галлюцинируют. Проблема в том, что ничто не может выявить уверенные ответы, ответы, набравшие 0,525 балла, превышающие ваш пороговый уровень и незаметно ошибочные.
Я создал систему оценки, которая разделяет достоверность на два сигнала: атрибуцию и специфичность. Высокая специфичность в сочетании с низкой атрибуцией — это признак галлюцинации. Один-единственный показатель всегда её не обнаруживает.
Это не скрипт оценки. Это механизм принятия решений, который находится между вашей моделью и вашим пользователем.
Я изменил одну строчку в подсказке. Всё сломалось.
Три слова сломали мою систему оценки: «будьте конкретны и подробны».
Я добавил их в командную строку системы во вторник днем. Обычное изменение. Такое, которое вносишь десятки раз при настройке конвейера RAG. Через час я запустил следующую партию тестов, и на третий вопрос пришел такой результат:
«Контекстная инженерия была изобретена в Массачусетском технологическом институте в 1987 году и в основном используется для оптимизации аппаратного кэша в процессорах. Она не имеет ничего общего с языковыми моделями».
Мой оценщик поставил 0,525. Это выше моего проходного балла в 0,5. Зеленый свет.
Я чуть не пропустил это. Я бегло просматривал результаты, как это делают, когда два часа смотришь на итоговые тесты, проверяя баллы, а не читая предложения. Единственная причина, по которой я это заметил, заключалась в том, что слово «1987» показалось мне неправильным. Я прочитал его дважды и открыл контекстную документацию. Модель выдумала каждую конкретную деталь в этом предложении.
Оценка повысилась, потому что ответ стал более конкретным. Качество ухудшилось, потому что модель стала более уверенной в том, что она выдумывает. Мой слой оценки содержал одно число для обоих направлений, и он не мог их различить.
В тот раз я заметил это вручную. Это не процесс. Это удача. И весь смысл системы оценки в том, что она не должна зависеть от того, внимательно ли вы читаете в тот или иной день.
Но как только вы пытаетесь это исправить, всё усложняется. Например, как вообще определить, что значит «хорошо»? Если вы просто попросите другого магистра права оценить первый ответ, вы просто поднимете проблему на новый уровень. Настоящая опасность заключается не в некачественном ответе, а в том, который звучит как экспертный, но на самом деле вас обманывает.
В большинстве руководств советуют просто вызвать модель и посмотреть, «правильно ли выглядит» результат. Но посмотрите на цифры. Что происходит, когда ваш ответ получает общий балл 0,525, что технически приемлемо, но его базовый балл равен 0,428, а специфичность — 0,701? Такое сочетание означает уверенность, но отсутствие базовых знаний. Это не пограничный ответ. Это галлюцинация в деловом костюме.
Это не редкие исключения. Так происходит по умолчанию в производственных системах LLM, и вы не сможете это обнаружить с помощью проверки вибраций.
Ответ кроется в недостающем слое, который большинство команд полностью игнорируют. Между выводом LLM и доставкой пользователю есть важный этап: решение о том, следует ли отправить ответ, повторить попытку или сгенерировать его заново. Я создал этот слой. Вот система, с реальными цифрами и кодом, который вы можете запустить.
Полный код: https://github.com/Emmimal/llm-eval-layer
Для кого это предназначено?
Такая архитектура полезна при создании систем RAG [1], где легко могут проскальзывать неверные ответы, или чат-ботов, обрабатывающих несколько ходов и требующих проверки ответов с течением времени. Она также полезна в любом конвейере LLM, где необходимо автоматически определять, что делать дальше, например, показывать ли ответ пользователю, пытаться снова или генерировать новый.
Для демонстрационных версий с одним ходом без производственного трафика лучше этого избегать. Если каждый ответ всё равно проходит проверку человеком, дополнительные затраты не оправданы. То же самое, если в вашем домене есть только один правильный ответ, и точное совпадение работает нормально.
Почему система оценки программ магистратуры не работает?
Большинство систем оценки дают сбой по трем причинам, и обычно это происходит еще до того, как кто-либо это заметит.
«Выглядит правильно» — не всегда означает правильность. Ответ может звучать бегло, быть хорошо структурированным и выглядеть уверенно, но при этом быть совершенно неверным. Беглость не гарантирует правдивости. При быстрой проверке результатов мозг обычно оценивает качество текста, а не точность. Вам приходится активно бороться с этим инстинктом, а большинство людей этого не делают.
Галлюцинации, имеющие значение, — это не те, которые легко заметить. Никто не выпускает модель, которая утверждает, что Эйфелева башня находится в Берлине. Это выявляется в первый же день. Опасными являются уверенные, узкоспециализированные утверждения, которые звучат правдоподобно для любого, кто не является экспертом в этой конкретной области [10]. Они проходят проверку незамеченными, попадают в производство и в конечном итоге оказываются перед пользователями.
Более глубокая проблема заключается в том, что оценка — это не решение. Вы устанавливаете пороговое значение в 0,5. Один ответ получает 0,51 и проходит проверку. Другой получает 0,95 и тоже проходит проверку. Вы относитесь к ним одинаково. Но одному из них, вероятно, требовалась проверка человеком. Они дают вам число, тогда как вам нужно: отправить, отметить или отклонить.
Оценка выросла. Качество упало. Одно число не может одновременно влиять на оба показателя.
Традиционные метрики, такие как BLEU и ROUGE, здесь не работают хорошо [2, 3]. Они проверяют, сколько слов соответствует эталонному ответу, что имеет смысл в машинном переводе, где обычно есть один правильный результат. Но ответы LLM не имеют единственного правильного варианта. Есть много способов сказать одно и то же. Поэтому использование BLEU для разговора вводит в заблуждение. Это как оценивать эссе, проверяя только количество слов, соответствующих образцовому ответу, вместо того, чтобы судить, действительно ли идея верна и хорошо объяснена.
Сейчас все обращаются к LLM-в качестве судьи [4]. Вы используете модель, подобную GPT-4, для оценки результатов другой модели GPT-4. Это улучшает BLEU, но имеет свои проблемы. Это дорого, каждый раз может давать немного разные результаты, и это создает зависимость от другой модели, которую вы не полностью контролируете. Кроме того, это не масштабируется, когда вы оцениваете каждый ответ в производственной системе.
Фреймворки, такие как RAGAS [6], продвинули это вперед, но они по-прежнему зависят от судьи LLM для оценки и не являются детерминированными между запусками. На самом деле вам нужен слой оценки, который работает локально, не имеет затрат на каждый вызов и каждый раз выдает согласованные результаты.
Что необходимо для реальной системы оценки
Прежде чем писать какой-либо код, я установил пять жестких ограничений. Он должен выполняться за миллисекунды, потому что слой оценки, замедляющий отклик пользователя, не может быть развернут. Также запрещены вызовы API по стандартному пути. Судья LLM — это запасной вариант, а не значение по умолчанию, потому что оплата за каждый вызов оценки не масштабируется. И один и тот же входной параметр, один и тот же результат каждый раз, иначе регрессионное тестирование совершенно бесполезно.
Два других пункта касались объяснимости. Каждое отклонение должно было сопровождаться простым объяснением причины, а не просто числом, потому что «оценка: 0,43» ничего не говорит о том, что именно нужно исправить. И добавление новых оценщиков никогда не должно требовать изменения логики принятия решений. Именно так системы со временем приходят в упадок.
Архитектура
Три слоя. Каждый из них выполняет свою специфическую функцию.

Слой оценки выдает числовые значения. Слой принятия решений преобразует эти значения в вердикт с подробным объяснением. Именно этот последний этап большинство систем пропускают, и он же наиболее полезен, когда в рабочей среде возникает ошибка, и вы понятия не имеете, почему.
Основные параметры оценки
Верность: атрибуция и специфичность
Это был самый важный бомбардир, и именно его я чуть не угадал.
Сначала я использовал единый показатель « достоверности ». Он учитывал такие факторы, как семантическое сходство и совпадение слов между контекстом и ответом. Это работало в простых случаях, но не срабатывало в действительно важных ситуациях.
Проблема в следующем : некоторые ответы звучат уверенно и подробно, но на самом деле не соответствуют заданному контексту.
Поэтому я разделил проверку на верность на две отдельные проверки.
Проверка атрибуции показывает, подтверждается ли ответ контекстом. Если ответ содержит утверждения, которые нельзя найти или вывести из входных данных, атрибуция низкая [8].
# Attribution: is it grounded? semantic = semantic_similarity(context, response) overlap = token_overlap(context, response) attribution = 0.60 * semantic + 0.40 * overlap
Конкретность проверяет, насколько подробным и конкретным является ответ. Ответ считается конкретным, если он содержит четкие детали и избегает расплывчатых фраз, таких как «это может быть полезно во многих ситуациях».
# Specificity: is it concrete? length_score = min(1.0, len(tokens) / 80) richness = len(set(tokens)) / len(tokens) hedge_penalty = min(0.60, hedge_count * 0.15) specificity = (0.40 * length_score + 0.60 * richness) - hedge_penalty # Composite faithfulness = 0.70 * attribution + 0.30 * specificity
Ключевой вывод : высокая специфичность плюс низкая способность к атрибуции приводят к галлюцинациям.

Это опасно, потому что уверенные, подробные и неверные ответы сложнее выявить. Расплывчатые ответы, по крайней мере, показывают некоторую неуверенность. Уверенные, но необоснованные ответы этого не показывают.
Атрибуция — главный сигнал, поскольку обоснование имеет первостепенное значение. Специфичность второстепенна и в основном помогает выявить уверенные, но неверные ответы.
Вот как это выглядит на практике. В одном из ответов утверждается, что контекстная инженерия «была изобретена в Массачусетском технологическом институте в 1987 году и в основном используется для оптимизации аппаратного кэша»:
Attribution: 0.428 (low, weakly grounded in the context) Specificity: 0.701 (high, sounds detailed and authoritative) Decision: REJECT Reason: Confident hallucination detected
Единый показатель с пороговым значением, например, 0,5, всё ещё может это пропустить. Разделение между атрибуцией и специфичностью выявляет проблему, поскольку показывает не только оценку, но и причину сбоя ответа.
Релевантность ответа
Он измеряет, насколько непосредственно ответ отвечает на исходный вопрос.
Система оценки объединяет три сигнала: семантическое сходство между полным ответом и запросом, наилучшее совпадение одного предложения в ответе и простое перекрытие токенов [5, 6].
semantic = semantic_similarity(query, response) max_sent = max_sentence_similarity(query, response) overlap = token_overlap(query, response) relevance = 0.45 * semantic + 0.35 * max_sent + 0.20 * overlap
Компонент, оценивающий ответы на уровне предложений, поощряет целенаправленные ответы. Даже если ответ длинный или содержит дополнительную информацию, он все равно может получить высокий балл, если хотя бы одно предложение напрямую отвечает на вопрос.
Качество контекста: точность и полнота
Точность контекста отвечает на простой вопрос: модель выдумывает что-то или остается в рамках контекста? [7] Если точность низкая, ответ содержит утверждения, которые полученный контекст никогда не подтверждал. Модель отклонилась от сценария.
Функция Context Recall меняет ситуацию. Она проверяет, какая часть полученных данных действительно присутствовала в ответе. Низкий показатель полноты поиска означает, что при поиске были получены документы, которые модель в основном игнорировала. Вы получили много «шума».
prec = precision(context, response) # context -> response coverage rec = recall(response, context) # response -> context grounding f1 = 2 * prec * rec / (prec + rec) context_quality = 0.50 * f1 + 0.50 * semantic_similarity(context, response)
Качество контекста — это причинно-следственная, а не пассивная характеристика. Когда оно падает ниже порогового значения, система не просто помечает это. Она изменяет свои дальнейшие действия.
if context_quality < 0.40 and final_score < 0.65: action = "retrieve_more_documents" reason = "Root cause is retrieval, not the model"
Неправильный ответ, вызванный некачественным поиском информации, требует более качественных документов, а не более качественного запроса. Большинство оценочных систем не делают этого различия, и в итоге вы тратите час на отладку не того, что нужно.
Сигнал несогласия
Я начал внимательно изучать дисперсию после отладки сложного граничного случая. В логах отображались показатели достоверности 0.68 , релевантности 0.32 и качества контекста 0.71 .
Если просто усреднить эти числа, итоговый результат выглядит вполне приемлемым. Он проходит проверку. Но исходные данные рассказывают три совершенно разные истории об одном и том же ответе. Один показатель говорит о его точности, другой — о его нерелевантности, а третий — о том, что контекст был приемлемым.
Усреднение этих цифр полностью скрывает конфликт. На самом деле вам нужно отслеживать сигнал несогласия.
Это можно мгновенно обнаружить, рассчитав стандартное отклонение по всем показателям ваших измерений:
def _disagreement(scores: list[float]) -> float: n = len(scores) if n < 2: return 0.0 mean = sum(scores) / n return round(math.sqrt(sum((s - mean) ** 2 for s in scores) / n), 4)
Когда стандартное отклонение превышает 0.12 , система направляет ответ непосредственно в очередь на проверку человеком, полностью игнорируя итоговое среднее значение.
Если ваши оценщики тянут в совершенно разные стороны, система принципиально нестабильна. Это трение — лучший индикатор того, что автоматизация достигла своего предела и необходимо вмешательство человека.
Однако этот показатель несогласия не просто запускает проверку. Он также напрямую влияет на расчет достоверности, что подводит нас к следующему шагу.
Система подсчета очков: гибридная конструкция.
Весь процесс выполняется в три этапа.
Шаг 1: Эвристическая оценка
Все четыре параметра оценки вычисляются локально. Система полностью исключает вызовы внешнего API. Благодаря загрузке sentence-transformers непосредственно на ЦП, этот этап завершается примерно за 3 мс.
Шаг 2: Проверка на уверенность
Когда оценка оказывается в диапазоне от 0,45 до 0,65, происходит нечто интересное. Система перестаёт доверять одной лишь эвристике и обращается к судье LLM. За пределами этого диапазона локальная оценка достаточно надёжна, и вызов API не производится.
Шаг 3: Этап принятия решений

В журналы не записываются никакие необработанные числа с плавающей запятой. Вместо этого конвейер возвращает полную схему: ACCEPT , REVIEW или REJECT , с указанием типа ошибки, причины и конкретного дальнейшего действия. Судья LLM никогда не запускается по умолчанию. Он срабатывает только тогда, когда эвристические алгоритмы действительно не могут принять решение.
Этап принятия решений: от оценок к действиям
Большинство инструментов оценки пытаются ответить на основной вопрос: «Является ли этот ответ хорошим?»
Эта система полностью меняет вопрос: «Что нам делать с этим ответом?»
В основе логики принятия решений лежит трехмерная политика, которая работает непосредственно на основе ваших показателей обоснованности, специфичности и согласованности. Вместо того чтобы полагаться на единое среднее значение, она изолирует сбои с помощью явных программных правил:
# Confirmed hallucination: attribution is critically low and the response is vague if attribution < 0.35 and specificity <= 0.50: return REVIEW, "vague response, retry with specific prompt" # Confirmed hallucination: attribution is low but the response sounds confident if attribution < 0.35 and specificity > 0.50: return REJECT, "confident hallucination" # Confident hallucination: sounds authoritative but is poorly grounded if attribution < 0.45 and specificity > 0.60: return REJECT, "confident hallucination detected" # Poor retrieval: the context fetch itself is the root cause if context_quality < 0.40: return REVIEW, "retrieve_more_documents" # Hard guardrail: both attribution and context quality are weak # Two weak signals together are worse than one strong failure if attribution < 0.55 and context_quality < 0.50: return REJECT, "hallucination guardrail triggered" # Weak grounding if attribution < 0.55: return REVIEW, "weak grounding, retry with specific prompt" # Off-topic: response does not address the query at all if relevance_score < 0.30: return REVIEW, "off-topic, retry with clearer query" # High disagreement if disagreement > 0.12: return REVIEW, "uncertain scoring, human review recommended" # Borderline quality if final_score < 0.65: return REVIEW, "borderline, optional human review" # All gates passed successfully return ACCEPT, "serve_response"
Невозможно обрабатывать все некорректные результаты одинаково. Нечеткий ответ (низкая степень достоверности, низкая специфичность) просто требует переписывания, поэтому он отправляется на REVIEW с запросом на повторную попытку. Уверенная галлюцинация (низкая степень достоверности, высокая специфичность) опасна, поэтому она немедленно REJECT и принудительно перегенерируется. Различные сбои требуют различных последующих действий.
Как выглядит результат
Вот фактические результаты выполнения main.py в четырех случаях.
Пример 1: Обоснованный ответ
Final Score : 0.680 Attribution : 0.684 (grounding) Specificity : 0.713 (concreteness) Relevance : 0.657 Context Quality : 0.688 Disagreement : 0.016 (scorer std dev) No hallucination Decision : ACCEPT (confidence: 41%) Reason : All quality gates passed Next Action : serve_response Latency : 322ms
Пример 2: Уверенная галлюцинация
Final Score : 0.525 Attribution : 0.428 (grounding) Specificity : 0.701 (concreteness) Relevance : 0.613 Context Quality : 0.424 Disagreement : 0.077 (scorer std dev) Suspected weak grounding Failure Type : hallucination Decision : REJECT (confidence: 22%) Reason : Confident hallucination detected, attribution=0.428 (low grounding) but specificity=0.701 (high confidence). Response sounds authoritative but is not grounded in context. Next Action : regenerate_with_grounding_prompt Why : Confident but ungrounded response is more dangerous than a vague one Low-confidence sentences: It has nothing to do with language models.
Этот случай наглядно демонстрирует, почему оценка только по исходным показателям неэффективна. Если посмотреть только на итоговый балл 0.525 , он находится выше стандартного порогового значения 0.5 . Базовый конвейер метрик пропускает это без проблем. Но уровень принятия решений обнаруживает это и выдает предупреждение: показатель атрибуции 0.428 в сочетании с показателем специфичности 0.701 — это точный след уверенной галлюцинации.
Пример 3: Нечеткий ответ
Final Score : 0.295 Attribution : 0.248 (grounding) Specificity : 0.332 (concreteness) Decision : REVIEW (confidence: 32%) Reason : Uncertain / vague response, low grounding, low specificity. Not a confirmed hallucination. Next Action : retry_with_specific_prompt
Не принимайте уклончивый ответ за галлюцинацию. Низкая атрибуция в сочетании с низкой специфичностью говорит о том, что модель просто перестраховывается и уклоняется от ответа. Если вы принудительно выполните перегенерацию, вы получите только лишнюю информацию. Решение проблемы заключается в запуске повторной попытки с использованием более ограничительного шаблона запроса.
Пример 4: Ответ не по теме
Final Score : 0.080 Attribution : 0.017 (grounding) Specificity : 0.630 (concreteness) Decision : REJECT (confidence: 42%) Reason : Confident hallucination, attribution=0.017, specificity=0.630. Response sounds authoritative but is fabricated. Low-confidence sentences: The French Revolution was a period of major political and societal change... Marie Antoinette was Queen of France at the time.
Значение коэффициента атрибуции 0.017 при специфичности 0.630 означает, что модель вернула эссе о Французской революции в ответ на вопрос, требующий контекстной инженерии. Система мгновенно это обнаруживает, но не просто выдает слепой отказ. Она точно определяет и отображает те самые строки предложений, которые вызвали сигнал низкой достоверности.
Распределение решений
ACCEPT 1/4 (25%) REVIEW 1/4 (25%) REJECT 2/4 (50%)
Если отслеживать распределение этих метрик во времени в производственной среде, можно мгновенно увидеть, ухудшаются ли веса вашей модели, отбрасывает ли конвейер извлечения релевантные документы или теряют ли свои шаблоны подсказок свою эффективность. Это и есть реальная наблюдаемость системы, а не просто сброс бесполезных строк в агрегатор логов.
Реальные эталонные показатели
Проведено по всему набору из 5 случаев оценки RAG:
| ИДЕНТИФИКАТОР | Этикетка | Аттр. | Релевантный | Ctx | Финал | Галлюцинация | Решение |
| q_001 | хороший_ответ | 0,686 | 0,680 | 0,725 | 0,694 | Нет | ПРИНИМАТЬ |
| q_002 | галлюцинированный_ответ | 0,445 | 0,621 | 0,459 | 0,547 | Подозреваемый | ОТКЛОНЯТЬ |
| q_003 | хороший_ответ | 0,528 | 0,456 | 0,535 | 0,534 | Подозреваемый | ОБЗОР |
| q_004 | off_context_response | 0,043 | 0,682 | 0,091 | 0.337 | Подтвержденный | ОТКЛОНЯТЬ |
| q_005 | хороший_ответ | 0,625 | 0,341 | 0,628 | 0,536 | Нет | ОБЗОР |
Источником истины являются решения, а не оценки. Эти результаты носят иллюстративный характер — пять случаев не являются статистически значимой выборкой, и вам следует провести сравнение с вашими собственными размеченными данными, прежде чем доверять какому-либо пороговому значению.
Критерий точности
Давайте посмотрим на фактические показатели точности. Средний показатель для хороших результатов составляет 0.588 , а для плохих — 0.442 . Разница в 0.146 достаточно велика, чтобы установить точные и надежные границы. Кроме того, система идеально выявила 2 out of 2 галлюцинаций во время выполнения. Вы получаете полное покрытие обнаружения без ущерба для времени выполнения.
Тест на задержку (10 запусков, «теплый» режим)
| Операция | Задержка | Примечания |
| оценщик атрибуции | ~1,2 мс | Встраивание плюс перекрытие |
| Оценщик релевантности | ~1,1 мс | Оценка на уровне предложения |
| контекстный оценщик | ~0,8 мс | Точность плюс полнота |
| Уровень принятия решений | ~0,1 мс | Правила политики плюс уверенность |
| Полный конвейер.evaluate() | ~291 мс среднее значение | Звонки от LLM не принимаются |
| Судья с дипломом магистра права | ~340 мс | Только в исключительных случаях, зона от 0,45 до 0,65. |
При первом запуске вы столкнетесь с узким местом примерно в 800–1000 мс, пока модель sentence-transformers будет запущена. После этой первоначальной загрузки скорость значительно увеличится, в среднем до 291ms на вызов. Если вы предварительно загрузите веса в контейнер вашего приложения при запуске, вы сможете запустить весь этот слой оценки в производственной среде, увеличив задержку ответа менее чем 300ms .
Система регрессионного тестирования
Большинство команд пропускают этот этап. Это ошибка. Генерация оценочных баллов бессмысленна, если вы ничего с ними не делаете. Если вы изменяете шаблон запроса, и точность падает, вам нужно мгновенное оповещение. Если вы меняете стратегию получения данных, и три крайних случая, которые раньше проходили проверку, теперь полностью сломаны, вам нужно это обнаружить, прежде чем отправлять изменения в основную ветку. Набор регрессионных тестов решает эту проблему, сохраняя исторические базовые показатели и сравнивая текущие оценки с ними во время сборки CI.
suite = RegressionSuite("data/baselines.json") # Record baselines after validating your system suite.record_baseline("q_001", query, context, response, result) # After changing your prompt or model: report = suite.run_regression(pipeline, test_cases) # Treat failures like CI failures if report.failed > 0: raise SystemExit("Quality regression detected. Deployment blocked.")
Вот точный вывод терминала, когда изменение командной строки вызывает снижение производительности:
Regression Report -- CI/CD Quality Gate 3 REGRESSION(S) DETECTED -- DEPLOYMENT BLOCKED Total cases : 3 Passed : 0 Failed : 3 Mean delta : -0.4586 Threshold : +/- 0.05 Regressions -- score dropped beyond threshold: [q_001] 0.694 -> 0.137 (delta -0.556) [q_002] 0.547 -> 0.137 (delta -0.410) [q_003] 0.534 -> 0.124 (delta -0.410)
Простое изменение параметра запроса приводит к снижению показателя стабильной работы с 0.694 до 0.137 . Конвейер регрессионного анализа обнаруживает это, прерывая развертывание до того, как пользователи увидят последствия.
Это позволяет применять стандартные методы CI/CD к генеративному ИИ. Больше никаких ручных выборочных проверок. Если качество падает ниже порогового значения, сборка завершается с ошибкой. Это относится к оперативной разработке точно так же, как к покрытию кода или модульному тестированию [11].
От показателей к решениям и действиям
Вот как выглядит полная трансформация, которую обеспечивает эта система.
Old thinking: score = 0.68 # ship it? probably fine This system: signals -> reasoning -> decision -> action
Мы помещаем каждый выходной сигнал в предсказуемую схему. Вы получаете четкое решение (ПРИНЯТЬ, ПРОВЕРИТЬ или ОТКЛОНИТЬ), причину ошибки, тип сбоя, действие маршрутизации и процент достоверности. Эта структурированная полезная нагрузка — единственная причина, по которой систему можно отлаживать, когда возникают проблемы.
Метод to_dict() для каждого результата позволяет сериализовать его в формат JSON для использования в логах, панелях мониторинга и API:
result.to_dict() # { # "decision": "REJECT", # "confidence_pct": 22, # "failure_type": "hallucination", # "hallucination_status": "suspected", # "next_action": "regenerate_with_grounding_prompt", # "action_why": "Confident but ungrounded response is more dangerous than a vague one", # "scores": { # "final": 0.525, # "attribution": 0.428, # "specificity": 0.701, # "relevance": 0.613, # "context_quality": 0.424, # "disagreement": 0.077 # }, # "explanations": { # "reason": "Confident hallucination detected...", # "low_confidence_sentences": ["It has nothing to do with language models."] # }, # "meta": { # "passed": false, # "used_llm_judge": false, # "latency_ms": 301.0 # } # }
Подключите это к любой системе логирования, и вы получите полный журнал аудита качества для каждого ответа, когда-либо полученного вашей системой.
Честные дизайнерские решения
Разница в оценке в 0.146 совершенно нормальна для локальной эвристической системы. Хорошие и плохие ответы всегда будут сливаться посередине. Слой принятия решений исправляет это, анализируя взаимодействие атрибуции и специфичности, а не полагаясь на одно усредненное число. Попытки искусственно увеличить разрыв в оценке путем изменения весов лишь искажают результаты бенчмарков, не меняя при этом работу кода в производственной среде.
Весовые коэффициенты 0.70/0.30 и 0.60/0.40 не основаны на какой-либо универсальной теории. Я просто проводил тесты, пока эти числа не соответствовали данным из моей собственной базы знаний. Если вы запустите эту же конфигурацию на юридических контрактах, медицинских журналах или исходном коде, эти соотношения окажутся неверными. Именно поэтому я выделил их в отдельную директорию configs . Вы можете настроить параметры под свои конкретные данные, не редактируя основной код конвейера.
Порог галлюцинаций 0.35 срабатывает только тогда, когда атрибуция полностью достигает минимума. Если ваша область применения сильно зависит от перефразирования без точного совпадения слов, этот жесткий порог вызовет ложные срабатывания. Использование преобразователей предложений [9] обрабатывает семантическое значение гораздо лучше, чем базовое сопоставление TF-IDF. Если вы отключите его и перейдете в локальный резервный режим, конвейер автоматически станет гораздо более консервативным для защиты ваших данных. [5]
Диапазон значений LLM-критерия 0.45 до 0.65 напрямую связан с пороговыми значениями по умолчанию. Если вы измените значения REJECT_THRESHOLD или REVIEW_THRESHOLD , вам потребуется перенастроить окно критерия в соответствии с ними. Архитектура основана на строгом шаблоне: дорогостоящий LLM-критерий запускается только тогда, когда локальные эвристики сталкиваются с неопределенностью, и никогда не используется в качестве основного критерия.
Низкие показатели уверенности — например, 22% или 42% для пограничных результатов — это не ошибки. Такие ответы действительно нестабильны. Чрезмерно самоуверенный конвейер оценки, работающий с ненадежными входными данными, представляет собой огромную проблему для производства; вам нужна система, которая должным образом оценивает собственные сомнения.
Также не беспокойтесь о предупреждении embeddings.position_ids , которое появляется при запуске sentence-transformers . Оно носит чисто косметический характер и никак не влияет на производительность во время выполнения.
Чего это не решает
Самый сложный случай — это неявная галлюцинация. Если ответ использует контекстную лексику, но незаметно меняет смысл, локальный код обманывается, потому что исходные слова всё ещё совпадают. Эвристические алгоритмы не замечают такого рода семантического сдвига. Именно поэтому существует резервный вариант для судьи LLM.
Согласованность между документами также выходит за рамки данной задачи. Система оценки рассматривает каждый ответ в его собственном контексте изолированно. Если два связанных ответа противоречат друг другу, ничто здесь этого не выявит. А калибровка действительно зависит от конкретной предметной области — рассматривайте файл configs/thresholds.yaml как отправную точку, запускайте его на своих собственных помеченных случаях и настраивайте параметры, прежде чем доверять каким-либо значениям, указанным здесь. Медицинской системе контроля качества требуются гораздо более жесткие пороговые значения для обнаружения галлюцинаций, чем те, которые я использовал.
Что вы на самом деле построили
В итоге, после всего этого, вы получите не скрипт для оценки.
Система принимает на вход три параметра: запрос, контекст и ответ. На выходе получается строгий набор данных, содержащий решение, причину ошибки, тип сбоя, следующее действие, оценку достоверности и разбивку данных по категориям.
Каждый ответ, поступающий в вашу систему, оценивается, классифицируется и направляется по маршруту. Хорошие ответы поступают напрямую пользователю. Нечеткие ответы повторяются с более точным запросом. Галлюцинации блокируются еще до того, как кто-либо их увидит. И когда вы меняете запрос, и три случая, которые раньше оценивались в 0,69, внезапно получают оценку 0,13, набор регрессионных тестов обнаруживает это до того, как вы отправите изменения в основную систему, а не после того, как пользователь сообщит об этом.
Это недостающий слой в море демонстраций LlamaIndex, примеров LangChain и базовых руководств по RAG в интернете. Все показывают, как подключить векторную базу данных, но никто не показывает, как безопасно проверить выходные данные модели.
RAG предоставляет вам нужные документы. Оперативное проектирование предоставляет вам правильные инструкции. Этот уровень позволяет принять правильное решение о том, что делать с результатом.
Полный исходный код, данные для бенчмаркинга и скрипты для локальной реализации можно получить здесь: https://github.com/Emmimal/llm-eval-layer .
Ссылки
[1] Льюис, П., Перес, Э., Пиктус, А., Петрони, Ф., Карпухин, В., Гоял, Н., Кюттлер, Х., Льюис, М., Их, В.-Т., Рокташель, Т., Ридель, С., и Кила, Д. (2020). Генерация с расширенным поиском для задач обработки естественного языка, требующих больших объемов знаний. Достижения в области нейронных информационных систем, 33, 9459-9474. https://arxiv.org/abs/2005.11401
[2] Папинени, К., Рукос, С., Уорд, Т., и Чжу, В.-Дж. (2002). BLEU: метод автоматической оценки машинного перевода. Труды 40-го ежегодного собрания Ассоциации вычислительной лингвистики, 311-318. https://aclanthology.org/P02-1040/
[3] Лин, Ч.-Ю. (2004). ROUGE: Пакет для автоматической оценки резюме. Text Summarization Branches Out, 74-81. https://aclanthology.org/W04-1013/
[4] Чжэн, Л., Чан, В.-Л., Шэн, Ю., Чжуан, С., Ву, З., Чжуан, Ю., Линь, З., Ли, З., Ли, Д., Син, Э., Чжан, Х., Гонсалес, Дж. Э. и Стойка, И. (2023). Судейство в качестве судьи с помощью MT-Bench и Chatbot Arena. Препринт arXiv arXiv:2306.05685. https://arxiv.org/abs/2306.05685
[5] Реймерс, Н., и Гуревич, И. (2019). Sentence-BERT: Встраивание предложений с использованием сиамских BERT-сетей. Труды конференции 2019 года по эмпирическим методам обработки естественного языка, 3982-3992. https://arxiv.org/abs/1908.10084
[6] Эс, С., Джеймс, Дж., Эспиноса Анке, Л., и Шокаерт, С. (2023). RAGAS: Автоматизированная оценка расширенной генерации поиска. Препринт arXiv:2309.15217. https://arxiv.org/abs/2309.15217
[7] Мэннинг, К. Д., Рагхаван, П., и Шутце, Х. (2008). Введение в информационный поиск. Издательство Кембриджского университета. https://nlp.stanford.edu/IR-book/
[8] Девлин, Дж., Чанг, М.-В., Ли, К., и Тутанова, К. (2019). BERT: Предварительное обучение глубоких двунаправленных трансформеров для понимания языка. Труды NAACL-HLT 2019, 4171–4186. https://arxiv.org/abs/1810.04805
[9] Ван В., Вэй Ф., Донг Л., Бао Х., Ян Н. и Чжоу М. (2020).
MiniLM: Глубокая дистилляция с самовниманием для сжатия предварительно обученных трансформеров, не зависящего от задачи. Advances in NeurIPS, 33, 5776–5788. https://arxiv.org/abs/2002.10957
[10] Тонмой, С.М., Заман, С.М., Джайн, В., Рани, А., Равте, В., Чадха, А., и Дас, А. (2024). Комплексный обзор методов смягчения галлюцинаций в больших языковых моделях. arXiv:2401.01313.
https://arxiv.org/abs/2401.01313
[11] Брек Э., Кай С., Нильсен Э., Салиб М. и Скалли Д. (2017).
Оценка результатов тестирования машинного обучения: критерии готовности к внедрению машинного обучения в производство и сокращения технического долга. IEEE BigData 2017, 1123–1132.
https://doi.org/10.1109/BigData.2017.8258038
Раскрытие информации
Весь код в этой статье написан мной и является оригинальной работой, разработанной и протестированной на Python 3.12.6. Результаты бенчмарков получены в ходе реальных запусков на моей локальной машине (Windows 11, только процессор) и воспроизводятся путем клонирования репозитория и запуска файлов main.py, experiments/rag_eval_demo.py и experiments/benchmarks.py. Библиотека sentence-transformers используется в качестве необязательной зависимости для семантического встраивания в алгоритмы атрибуции и релевантности. Без нее система переключается на векторы TF-IDF с предупреждением, и вся функциональность остается работоспособной. Формулы оценки, логика принятия решений, правила обнаружения галлюцинаций и система регрессии являются независимыми реализациями, не основанными на каком-либо цитируемом коде. У меня нет финансовых связей ни с одним инструментом, библиотекой или компанией, упомянутыми в этой статье.
Эммимал П. Александр. Посмотреть все работы Эммимал П. Александра.
Источник: towardsdatascience.com

Добавить комментарий
Для отправки комментария вам необходимо авторизоваться.