Оценки для Retrieval Augmented Generation: TruLens + Milvus
Эта статья была изначально опубликована в The New Stack и перепечатана здесь с разрешения.
Растущая популярность больших языковых моделей (LLM) стимулировала развитие технологий векторного поиска, включая специализированные векторные базы данных, такие как Milvus и Zilliz Cloud, библиотеки векторного поиска, такие как FAISS, и плагины векторного поиска, интегрированные с традиционными базами данных.
Векторный поиск все чаще становится ключевым корпоративным сценарием использования генеративного ИИ в форме приложений для ответов на вопросы на основе retrieval augmented generation, или RAGs. Такой подход к построению позволяет LLM получать простой доступ к проверенной базе знаний, которую они могут использовать как контекст для ответов на вопросы. Milvus — это высокомасштабируемая векторная база данных с открытым исходным кодом, специально созданная для этого применения.
Создание RAG
При создании эффективного LLM-приложения в стиле RAG существует множество вариантов конфигурации, которые могут существенно повлиять на качество извлечения. Некоторые из этих вариантов включают:
Создание векторной БД
- Выбор данных
- Модель эмбеддингов
- Тип индекса
Поиск высококачественных данных, точно соответствующих требованиям вашего приложения, критически важен. Процесс извлечения может выдавать нерелевантные результаты, если у вас нет правильных данных.
После выбора данных обратите внимание на используемую модель эмбеддингов, поскольку она существенно влияет на качество извлечения. Даже если ваша база знаний содержит правильную информацию, механизм извлечения может выдавать неверные результаты, если модели эмбеддингов требуется семантическое понимание вашей предметной области.
Релевантность контекста — полезная метрика для оценки качества извлечения, и эти варианты выбора значительно на нее влияют.
Наконец, тип индекса может существенно влиять на эффективность семантического поиска. Это особенно справедливо для больших наборов данных; этот выбор позволяет вам находить компромисс между уровнем полноты, скоростью и требованиями к ресурсам. Milvus поддерживает различные типы индексов, такие как плоские индексы, индексы на основе product quantization и индексы на основе графов. Подробнее о различных типах индексов можно прочитать здесь.
Извлечение
- Объем извлекаемого контекста (top k)
- Размер фрагмента
Когда мы переходим к извлечению, top k — это часто обсуждаемый параметр, который управляет количеством извлекаемых фрагментов контекста. Более высокое значение top k дает нам более высокую вероятность извлечь нужную информацию и повышает вероятность того, что наша LLM включит нерелевантную информацию в свой ответ. Для простых вопросов более низкое значение top k часто оказывается наиболее эффективным.
Размер фрагмента управляет размером каждого извлекаемого контекста. Более крупный размер фрагмента может быть полезен для более сложных вопросов, тогда как меньших фрагментов достаточно для простых вопросов, на которые можно ответить, используя лишь совсем небольшой объем информации.
Для многих из этих вариантов не существует универсального решения. Производительность может сильно различаться в зависимости от размера и типа данных, используемых LLM, вашего приложения и многого другого. Нам нужен инструмент оценки, чтобы оценить качество этих извлечений для нашего конкретного сценария использования. Именно здесь на помощь приходит TruLens.
TruLens для отслеживания и оценки LLM
TruLens — это библиотека с открытым исходным кодом для оценки и отслеживания производительности LLM-приложений, таких как RAGs. С TruLens мы также получаем возможность использовать сами LLM для оценки вывода, качества извлечения и многого другого.
Когда мы создаем приложения на основе LLM, самый важный вопрос, который волнует многих, — это галлюцинации. RAG во многом помогают обеспечить точную информацию, предоставляя LLM извлеченный контекст, но они не могут этого гарантировать. Оценки здесь необходимы для проверки отсутствия галлюцинаций в нашем приложении. TruLens предлагает три теста для этой задачи: релевантность контекста, обоснованность и релевантность ответа. Давайте рассмотрим каждый из них, чтобы понять, какую пользу они могут нам принести.
Релевантность контекста
Первый шаг любого RAG-приложения — извлечение; чтобы проверить качество нашего извлечения, мы хотим убедиться, что каждый фрагмент контекста релевантен входному запросу. Это критически важно, потому что LLM будет использовать этот контекст для формирования ответа, поэтому любая нерелевантная информация в контексте может быть вплетена в галлюцинацию.
Обоснованность
После того как контекст извлечен, LLM формирует из него ответ. LLM часто отходят от предоставленных фактов, преувеличивая или расширяя их до ответа, который звучит правдоподобно. Чтобы проверить обоснованность нашего приложения, нам следует разделить ответ на отдельные утверждения и независимо искать доказательства, подтверждающие каждое из них, в извлеченном контексте.
Релевантность ответа
Наконец, наш ответ все еще должен полезно отвечать на исходный вопрос. Мы можем проверить это, оценив релевантность итогового ответа пользовательскому вводу.
RAG без галлюцинаций
Достигнув удовлетворительных оценок по этой триаде, мы можем сделать нюансированное утверждение о корректности нашего приложения; оно проверено как свободное от галлюцинаций в пределах своей базы знаний. Другими словами, если векторная база данных содержит только точную информацию, то ответы, предоставляемые RAG, также точны.
Сделаем это конкретным
Как мы упоминали ранее, многие варианты конфигурации нашего RAG могут существенно влиять на галлюцинации. Чтобы это проиллюстрировать, мы создадим RAG-приложение для ответов на вопросы поверх статей Wikipedia о небольшом наборе городов. LlamaIndex будет выступать в качестве фреймворка для этого приложения.
Следуйте этому примеру в Google Colab.
Загрузка данных из Wikipedia
Чтобы создать наше векторное хранилище, сначала нужно загрузить данные. Здесь мы будем использовать загрузчик данных из LlamaIndex для загрузки данных напрямую из Wikipedia.
from llama_index import WikipediaReader
cities = [
"Los Angeles", "Houston", "Honolulu", "Tucson", "Mexico City",
"Cincinatti", "Chicago"
]
wiki_docs = []
for city in cities:
try:
doc = WikipediaReader().load_data(pages=[city])
wiki_docs.extend(doc)
except Exception as e:
print(f"Error loading page for city {city}: {e}")
Настройка оценщиков
Далее мы хотим настроить наших оценщиков. В частности, мы будем использовать упомянутую ранее триаду: релевантность контекста, обоснованность и релевантность ответа, чтобы проверять наличие галлюцинаций.
TruLens предоставляет набор оценщиков, или функций обратной связи, с подсказками, полезными для этой оценки, которые используют определенного поставщика моделей, например OpenAI, Anthropic или HuggingFace.
# Initialize OpenAI-based feedback function collection class:
openai_gpt4 = feedback.OpenAI()
После того как мы задали поставщика модели, мы выбираем релевантность вопроса и утверждения для нашей первой оценки. Для каждой оценки в этом примере мы также будем использовать рассуждения по цепочке мыслей, чтобы лучше понимать оценки. Это обозначается суффиксом функции обратной связи 1_with_cot_reason.
Когда мы это делаем, нам также нужно выбрать, какой текст передать нашей функции обратной связи. TruLens сериализует приложение, которое затем индексируется JSON-подобной структурой. Мы будем использовать этот индекс для выбора текста. TruLens предоставляет ряд вспомогательных функций, чтобы упростить это:
on_input()автоматически находит основной входной параметр, переданный нашему приложению LlamaIndex, чтобы использовать его как первый текст, передаваемый в нашу функцию обратной связи.TruLlama.select_source_nodes()определяет исходные узлы, используемые при извлечении LlamaIndex.
Наконец, нам нужно агрегировать релевантность для каждого фрагмента контекста в единую оценку. В этом примере мы будем использовать максимум для агрегации, чтобы измерить релевантность самого релевантного фрагмента. Также можно использовать другие метрики, такие как среднее или минимум.
# Question/statement relevance between question and each context chunk.
f_context_relevance = Feedback(openai.qs_relevance_with_cot_reason, name = "Context Relevance").on_input().on(
TruLlama.select_source_nodes().node.text
).aggregate(np.max)
Обоснованность настраивается аналогично, но с немного другой агрегацией. В этом случае мы возьмем максимальную оценку обоснованности для каждого утверждения, а затем среднюю оценку обоснованности по всем утверждениям.
grounded = Groundedness(groundedness_provider=openai_gpt4)
f_groundedness = Feedback(grounded.groundedness_measure_with_cot_reason, name = "Groundedness").on(
TruLlama.select_source_nodes().node.text # context
).on_output().aggregate(grounded.grounded_statements_aggregator)
Релевантность ответа — самая простая функция обратной связи для настройки, поскольку она зависит только от входных и выходных данных. Для этого мы можем использовать новую вспомогательную функцию TruLens — .on_input_output().
# Question/answer relevance between overall question and answer.
f_qa_relevance = Feedback(openai.relevance_with_cot_reason,
name = "Answer Relevance").on_input_output()
Определение пространства конфигураций
Теперь, когда мы загрузили наши данные и настроили оценщики, пришло время построить наш RAG. В ходе этого процесса мы создадим серию RAG с разными конфигурациями, оценим каждую и выберем наилучший оптимальный вариант.
Как мы упоминали ранее, мы ограничим наше пространство конфигураций несколькими значимыми вариантами для RAG. В этом примере мы протестируем тип индекса, модель эмбеддингов, top k и размер фрагмента; однако рекомендуется также тестировать другие конфигурации, такие как различные метрики расстояния и параметры поиска.
Перебор наших вариантов
После определения пространства конфигураций мы используем itertools, чтобы попробовать каждую комбинацию этих вариантов и оценить каждую. Кроме того, Milvus дает нам полезное преимущество в виде параметра overwrite. Это позволяет легко перебирать различные конфигурации без медленных процедур очистки и создания заново, которые могут требоваться в других векторных базах данных.
В каждой итерации мы передадим выбранный параметр индекса в MilvusVectorStore и в наше приложение с использованием контекста хранилища. Мы передадим нашу модель эмбеддингов в контекст сервиса, а затем создадим наш индекс.
vector_store = MilvusVectorStore(index_params={
"index_type": index_param,
"metric_type": "L2"
},
search_params={"nprobe": 20},
overwrite=True)
llm = OpenAI(model="gpt-3.5-turbo")
storage_context = StorageContext.from_defaults(vector_store = vector_store)
service_context = ServiceContext.from_defaults(embed_model = embed_model, llm = llm, chunk_size = chunk_size)
index = VectorStoreIndex.from_documents(wiki_docs,
service_context=service_context,
storage_context=storage_context)
Затем мы можем создать query engine, используя этот индекс — задав здесь top_k:
query_engine = index.as_query_engine(similarity_top_k = top_k)
После создания мы используем TruLens, чтобы обернуть приложение. Здесь мы дадим ему легко идентифицируемое имя, запишем конфигурации как метаданные приложения и определим функции обратной связи для оценки.
tru_query_engine = TruLlama(query_engine,
app_id=f"App-{index_param}-{embed_model_name}-{top_k}",
feedbacks=[f_groundedness, f_qa_relevance, f_context_relevance],
metadata={
'index_param':index_param,
'embed_model':embed_model_name,
'top_k':top_k
})
Этот tru_query_engine будет работать так же, как исходный query engine.
Наконец, мы используем небольшой набор тестовых промптов для оценки, вызывая приложение, чтобы получить ответ на каждый промпт. Поскольку мы вызываем OpenAI API в быстрой последовательности, Tenacity полезно использовать здесь, чтобы помочь нам избежать проблем с лимитами запросов с помощью экспоненциальной задержки.
@retry(stop=stop_after_attempt(10), wait=wait_exponential(multiplier=1, min=4, max=10))
def call_tru_query_engine(prompt):
return tru_query_engine.query(prompt)
for prompt in test_prompts:
call_tru_query_engine(prompt)
Результаты
Какая конфигурация показала лучший результат?
| Тип индекса | Модель эмбеддингов | Similarity Top k | Размер чанка |
|---|---|---|---|
| IVF Flat | text-embedding-ada-002 | 3 | 200 |
Какая конфигурация показала худший результат?
| Тип индекса | Модель эмбеддингов | Similarity Top k | Размер чанка |
|---|---|---|---|
| IVF Flat | Multilingual MiniLM L12 v2 | 1 | 500 |
Какие режимы отказа были выявлены?
Один из режимов отказа, который мы наблюдали, — это извлечение информации о неправильном городе. Вы можете увидеть пример этого в рассуждении chain-of-thought ниже, где вместо Houston был извлечен контекст о Tucson.
Аналогично, мы также видели проблемы, когда извлекался контекст о правильном городе, но этот контекст был нерелевантен входному вопросу.
Учитывая этот нерелевантный контекст, модель завершения затем начала галлюцинировать. Здесь важно отметить, что галлюцинация не обязательно является фактически неверной; это просто ситуация, когда модель отвечает без подтверждающих доказательств.
Кроме того, мы даже нашли примеры нерелевантных ответов.
Понимание производительности
По типу индекса
Тип индекса не оказал значимого влияния на производительность с точки зрения скорости, использования токенов или оценок. Вероятно, это связано с небольшим размером данных, загруженных для этого примера, а для более крупных корпусов тип индекса может быть более важным параметром выбора.
По модели эмбеддингов
Text-embedding-ada-002 превзошла модель эмбеддингов MiniLM по groundedness (0,72 по сравнению с 0,60 в среднем) и релевантности ответа (0,82 по сравнению с 0,62 в среднем). Две модели эмбеддингов показали одинаково хорошие результаты по релевантности контекста.
Эти улучшенные оценки можно объяснить тем, что эмбеддинги OpenAI лучше подходят для информации из Wikipedia.
Similarity Top K
Увеличение top k привело к небольшому улучшению максимального качества извлечения (измеряемого релевантностью контекста). Извлекая большее количество чанков, retriever получает больше попыток извлечь высококачественный контекст.
Более высокий top k также улучшил groundedness (0,71 по сравнению с 0,62 в среднем) и релевантность ответа (0,76 по сравнению с 0,68 в среднем). Извлекая больше чанков контекста, мы предоставляем модели завершения больше доказательств, чтобы формулировать и поддерживать утверждения.
Как и ожидалось, эти улучшения имеют цену в виде значительно более высокого использования токенов (в среднем на 590 дополнительных токенов на вызов).
Размер чанка
Увеличение размера чанка снизило groundedness нашего retriever, поскольку вынудило включать окружающий текст, нерелевантный входному вопросу.
С другой стороны, больший размер чанка предоставил больше доказательств для проверки. Поэтому, когда LLM делает утверждения, они с большей вероятностью будут подкреплены извлеченным контекстом.
Наконец, увеличение размера чанка повысило среднее использование токенов на 400 токенов на запись.
Создайте лучший RAG с TruLens и Milvus
В этом посте мы узнали, как создать RAG с различными конфигурациями и параметрами, включая тип индекса, модель эмбеддингов, top k и размер чанка. Большое количество поддерживаемых конфигураций и поддержка перезаписи в Milvus позволили проводить такие динамические эксперименты. Что особенно важно, мы также использовали TruLens для отслеживания и оценки каждого эксперимента, выявления и объяснения новых режимов отказа, а также быстрого поиска наиболее производительной комбинации.
Чтобы попробовать самостоятельно. Вы можете ознакомиться с open source TruLens и установить open source Milvus или Zilliz Cloud.
Читать далее

Announcing VDBBench 1.0: Open-Source VectorDB Benchmarking with Your Real-World Production Workloads
Discover VDBBench 1.0, an open-source tool for benchmarking vector databases with real-world production data, streaming ingestion, and concurrent workloads.

Cosmos World Foundation Model Platform for Physical AI
NVIDIA's Cosmos platform enables safe, digital twin training of GenAI models for physical applications, overcoming data scarcity and safety challenges.

Vector Databases vs. Document Databases
Use a vector database for similarity search and AI-powered applications; use a document database for flexible schema and JSON-like data storage.



