GPT или GigaChat — ответит RAGAS

В предыдущей статье мы разбирались с тем, как RAGAS помогает оценить работу ретриверов в RAG-системах. Продолжая наше исследование, теперь мы переключаемся на другой важный аспект — качество языковых моделей, или LLM. Эти модели играют центральную роль в создании тех ответов, которые мы видим при общении с чат-ботами. Понять, насколько эффективны они в своей задаче, крайне важно, так как именно от их работы зависит успешное взаимодействие пользователей с системой.

Одной из основных задач в оценке LLM является выявление и анализ «галлюцинаций» — ситуаций, когда модель генерирует правдоподобные, но фактически некорректные ответы. Эта проблема особенно актуальна в свете того, что даже современные LLM могут порождать ошибочные или вводящие в заблуждение данные. Как же можно определить, насколько надежны и точны ответы, генерируемые моделью?

Я уже упоминал, что в фреймворке по сути два набора показателей — один отвечает за качество ретривера, другой за работу языковой модели. Качество генерации мы будем измерять с помощью метрик faithfulness и answer relevance. Пора снова заглянуть под капот движка RAGAS.

Faithfulness

Если верить официальной документации, то faithfulness оценивает фактическую последовательность ответа, сгенерированного LLM, по отношению к заданному контексту. Суть этой метрики заключается в проверке, можно ли все утверждения, сделанные в ответе, логически вывести из предоставленного контекста. Пока не очень понятно. Давайте погружаться в пайплайн ее вычисления.

ef6f628129e5ac6e4dc048fe3800e379.png

  1. Сначала из сгенерированного ответа выделяется набор утверждений. Для этого используется языковая модель, которой передается специальный промпт LONG_FORM_ANSWER_PROMPT. Представьте, что вы спрашиваете у бота: «Что такое солнце?» и получаете ответ: «Солнце — это звезда в центре Солнечной системы, которая вращается вокруг Земли, обеспечивая свет и тепло.»

    Теперь, используя LLM, я превращаю этот ответ в серию более детальных утверждений:

    1. Солнце является звездой.

    2. Солнце находится в центре Солнечной системы.

    3. Солнце вращается вокруг Земли.

    4. Солнце обеспечивает Землю светом и теплом.

  2. Далее утверждения, полученные из предыдущего шага, оцениваются на основе их соответствия истинности или ложности в контексте заданного вопроса и ответа. Этот этап также оценивает языковая модель с помощью промпта NLI_STATEMENTS_MESSAGE. Представим, что контекст для модели был следующим:

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

    1. Солнце является звездой.

    2. Солнце находится в центре Солнечной системы.

      • Оценка: '1'. Это утверждение также верно и соответствует контексту, указывая на положение Солнца в Солнечной системе.

    3. Солнце вращается вокруг Земли.

    4. Солнце обеспечивает Землю светом и теплом.

      • Оценка: '1'. Это утверждение верно и соответствует контексту, в котором Солнце является источником большей части энергии.

  3. Далее мы вычисляем метрику, как среднее значение вердиктов.

    Теперь я думаю более менее ясно, что эта метрика как раз и отвечает за меру галлюцинаций в ответах нашей LLM. Может показаться, что эта метрика дублирует context_recall. Но они совершенно разные. Recall показывает, насколько качественный контекст вы собрали, качество оценивается относительно ground truth, некоего эталонного ответа на определенный вопрос. Но далее, ваша LLM может очень сильно переврать даже этот качественный контекст, вот здесь как раз и выходит на сцену faithfulness.

Хорошо, но достаточно ли этой метрики, чтобы оценить качество генерации. Даже если языковая модель не галлюцинирует, она может давать неполные ответы, например вы можете спросить про массу и диаметр Солнца, но модель ответит только про массу (и укажет верное значение), те здесь faithfulness будет равна единичке, но по факту ответ не вполне качественный. Значит, пора разобрать еще одну метрику — answer relevance

Answer relevancy

Метрика оценивает, насколько хорошо сгенерированный ответ соответствует заданному вопросу, проводя анализ через процесс генерации вопросов из ответа и последующего сравнения этих вопросов с оригинальным вопросом. Если сгенерированные вопросы тесно связаны с оригинальным вопросом, это указывает на высокую релевантность ответа.

Механизм работы метрики основывается на предположении, что если ответ полностью и точно адресует заданный вопрос, то из этого ответа можно сформулировать вопросы, которые будут схожи или идентичны оригинальному вопросу. Такой подход позволяет автоматически и объективно оценить релевантность ответов, сгенерированных языковой моделью, без необходимости прямого сравнения с «правильным» ответом.

Теперь заглянем под капот вычислений.

  1. Для каждого ответа метрика использует промпт QUESTION_GEN, чтобы сгенерировать несколько вопросов, которые могли бы быть заданы к этому ответу. Количество генерируемых вопросов определяется параметром strictness, который задает строгость оценки.

  2. Затем, используя встроенные модели векторизации, метрика вычисляет косинусное сходство между вектором исходного вопроса и векторами сгенерированных вопросов. Это сходство показывает, насколько сгенерированные вопросы близки к оригинальному, отражая релевантность ответа.

  3. Итоговая оценка релевантности получается как среднее значение косинусного сходства между исходным вопросом и всеми сгенерированными вопросами, умноженное на индикатор того, что ответ не является уклончивым (committal).

В прошлый раз, когда мы проверяли разные способы работы ретривера, мы смотрели на такие показатели, как точность и полнота контекста. Но для создания нашего синтетического набора данных мы использовали английские промпты для русскоязычного контента, что было не совсем правильно. Поэтому на этот раз пробуем сделать всё единообразно.

Генерируем хороший testset

Напомню, что данные мы собирали со страниц хабра. И можно было увидеть, что контент был разбавлен ненужной информацией, которая в дальнейшем может испортить нам жизнь. Прежде чем загружать всё в векторное хранилище и создавать тестовые данные, давайте почистим этот контент.

from langchain_community.document_loaders import AsyncHtmlLoader
from bs4 import BeautifulSoup # к сожалению в лангчейн урезанный функционал BS

loader = AsyncHtmlLoader(  
    ['https://habr.com/ru/articles/729664/',  
     'https://habr.com/ru/articles/733262/',  
     'https://habr.com/ru/articles/734146/',  
     'https://habr.com/ru/articles/735920/'  
     ]  
)

# загружаем сырые данные
html = loader.load()

# очищаем контент
def clean_content(docs):  
    for doc in docs:  
        soup = BeautifulSoup(doc.page_content, 'html.parser')  
        content = soup.find("div", class_="article-formatted-body article-formatted-body article-formatted-body_version-2")  
        doc.page_content = content.text  
      
    return docs

habr_docs = clean_content(html)

habr_docs[0]

ea137f166d7137f85045450acd1f3c40.png

В прошлый раз, было так:

d79cfbaa30501ecff0ee7e9f390de811.png

К счастью, для перевода на русский язык нет необходимости залезать в код и переводить промпты вручную. В библиотеке появилась автоматическая адаптация на любой язык (естественно делается это с помощью все той же LLM). Однако функция пока что работает не совсем эффективно, поскольку перевод касается только примеров, оставляя без изменений сами формулировки.

from ragas.testset import TestsetGenerator  
from ragas.testset.evolutions import simple, reasoning, multi_context

# задаем распределение по типам вопросов
distributions = {  
    simple: 0.6,  
    multi_context: 0.2,  
    reasoning: 0.2  
}

# создаем генератор и адаптируем
testsetgenerator = TestsetGenerator.with_openai(  
    generator_llm='gpt-4-0125-preview',  
    critic_llm='gpt-4-0125-preview',  
    chunk_size=700  
).adapt(language='russian', evolutions=distributions)

# адаптируем
testsetgenerator.adapt(language='russian', evolutions=distributions)

# в обновленной версии testset сразу включает ground_truth
testset = testsetgenerator.generate_with_langchain_docs(habr_docs, 150, distributions, raise_exceptions=False)

testset_pd = testset.to_pandas()

testset_pd.sample(10)

674e4beb0d533381bbd25d3f5ad20022.png

Можно заметить, что не все вопросы имеют ground_truth, тут как раз причина в слегка корявых промптах. Проблемные вопросы мы исключим из нашего датасета. После этого еще раз проведем эксперимент из предыдущей статьи.

llm = ChatOpenAI(model_name='gpt-3.5-turbo-1106')

def evaluate_ragas(ragas_dataset):  
  result = evaluate(  
    ragas_dataset,  
    metrics=[  
        context_precision,  
        context_recall,  
        faithfulness,  
        answer_relevancy,  
    ],  ) 
    
  return result

def create_data_for_ragas(qa_chain, eval_dataset):  
  rag_dataset = []  
  for row in tqdm(eval_dataset):  
    answer = qa_chain({'query' : row['question']})  
    
    rag_dataset.append(  
        {"question" : row['question'],  
         "answer" : answer['result'],  
         "contexts" : [context.page_content for context in answer['source_documents']],  
         "ground_truths" : [row['ground_truth']]  
         }    )  rag_df = pd.DataFrame(rag_dataset)  
  rag_eval_dataset = Dataset.from_pandas(rag_df) 
  
  return rag_eval_dataset
  
qa_chain_base = RetrievalQA.from_chain_type(  
    llm,  
    retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),  
    return_source_documents=True  
)

qa_ragas_baseline = create_data_for_ragas(qa_chain_base, eval_dataset)
baseline_result = evaluate_ragas(qa_ragas_baseline)

qa_chain_short = RetrievalQA.from_chain_type(  
    llm,  
    retriever=vectorstore.as_retriever(search_kwargs={'k': 3}),  
    return_source_documents=True  
)

qa_ragas_short = create_data_for_ragas(qa_chain_short, eval_dataset)
short_retrieval_result = evaluate_ragas(qa_ragas_short)

print(f'baseline:{baseline_result}')  
print(f'short_retrieval: {short_retrieval_result}')

9f8b2ce203740c1b2c9225f2e565d36e.png

А что GigaChat?

Langchain позволяет подключить множество языковых моделей, полный список можно посмотреть здесь В том числе есть разработки и отечественного филиала скайнета (YandexGPT и GigaChat). Чтобы подключить гигачат нужно будет немного потрясти бубном:

  1. Создать аккаунт на платформе

  2. Зарегистрировать проект

  3. Получить токен

a7fce4f37a9073a0c902b01e67e622c1.png

Далее нужно будет поставить саму библиотеку гигачата.

pip install --upgrade --quiet  gigachat 

Теперь вся мощь в наших руках.

from langchain_community.chat_models import GigaChat
from langchain.schema import HumanMessage, SystemMessage
  
chat = GigaChat(verify_ssl_certs=False)
chat.access_token = '' # подставьте ваш токен

messages = [  
    SystemMessage(  
        content='You are a helpful AI that shares everything you know. Talk in Russian.'  
    ),  
    HumanMessage(content='Что думаешь об интервью Такера Карлсона?'),  
]  
  
print(chat(messages).content)

3f21334b66b15b1de856a72859a75716.png

Ну и замеряем качество:

qa_chain_giga = RetrievalQA.from_chain_type(  
    chat,  
    retriever=vectorstore.as_retriever(search_kwargs={"k": 5}),  
    return_source_documents=True  
)  
  
qa_ragas_giga = create_data_for_ragas(qa_chain_giga, eval_dataset)  
giga_retrieval_result = evaluate_ragas(qa_ragas_giga)

print(f'baseline: {baseline_result}')  
print(f'short_retrieval: {short_retrieval_result}')
print(f'giga_chat: {giga_retrieval_result}')

b6b7c7ff68e13749dbd48f86e9d16f13.png

Довольно неплохо, ответы гигачата даже немного лучше gpt-3.5. Посмотрим на пример ответа сберовской модели.

print(giga_retrieval_result.iloc[13,:].question)
print(giga_retrieval_result.iloc[13,:].answer)
print(giga_retrieval_result.iloc[13,:].ground_truth)

e9fdc85111b007c8cb3b15ba1e89c779.pnge08b1d3defbe1137e5a61816e4fd71ad.png5c9e525d3438fcfeefa4e21655ed1885.png

Мы научились рассчитывать метрики, но насколько они устойчивы. Может быть нам просто повезло и в нашем синтетическом датасете оказались только «хорошие» вопросы? Я решил сгенерировать еще один тестовый сет и замерить на нем качество, цифры следующие:

f1acd3eab48b70cba56c5741788512af.png

Показатели немного другие, но, тем не менее, радикального разрыва нет. Пример также показывает, что все наши правки пайплайна нужно тестировать на одной и той же выборке.

Если не RAGAS, то что?

TruLens

Хорошо, мы убедились, что теперь качество чат-ботов можно оценивать вполне научно, но может быть есть не только RAGAS. Хорошая идея находит свое воплощение в разных формах, например в решении TruLens .

5d0ee59d34afe668326e1704e9679866.png

Метрики похожие: answer relevance, context relevance, groundedness — и они также оцениваю различные аспекты RAG. В библиотеке также есть реализация сбора обратной связи.

57531ade95fbf8788f35ac94f5b71c7b.png

ARES

В конце 2023 вышла статья, в которой была описана методология, улучшающая подход RAGAS — ARES (An Automated Evaluation Framework for Retrieval-Augmented Generation Systems).

743e9100818418441bad6f40de50d8b5.png

Для ARES нужно три типа входных данных: текстовых фрагментов из целевого корпуса, набора данных для проверки предпочтений человека и нескольких примеров запросов и ответов. ARES использует легкие языковые модели (например FLAN-T5 XXL), обученные на синтетических данных, и предиктивно-усиленный вывод (PPI) для расчета метрик: context relevance, answer faithfulness, answer relevance.

Подход показал значительно лучшие результаты, чем тот же RAGAS:

62b096a300af32e1586c9b91d50afadb.png

Показатели вызывают уважение, но сам подход я пока не тестировал.

Выводы

Конечно, предложенная методология не является серебряной пулей, и, безусловно, могут возникнуть ситуации, когда она окажется неэффективной, а, возможно, даже вредной.
Тем не менее, в качестве отправной точки для улучшения качества в QA-ситемах, она довольно перспективна. Сам фреймворк RAGAS еще пока сыроват, но с адекватной кривизной рук его можно поправить и прикрутить к вашему пайплайну чат-бота. Сам валидационный тестсет нужно хорошо подготовить, поскольку синтетика не всегда бывает адекватной, желательно в него также добавить и живую разметку. Ключевой момент — это механизм сбора обратной связи от пользователей, по сути это и есть главная метрика работы системы, в TruLens этот функционал уже зашит, в RAGAS его пока нет. Будет также полезным обновлять тестовую выборку новыми примерами.

Пишу про AI и NLP в телеграм.

© Habrahabr.ru