Главная / Статьи / Практические заметки: Серия AI: Самая сложная часть RAG, часть 2 — Почему совершенство

Практические заметки: Серия AI: Самая сложная часть RAG, часть 2 — Почему совершенство

Пошаговое руководство по практическим заметкам: Серия AI: Самая сложная часть RAG, часть 2 — Почему идеален: контракты, проверки и слоты для вставки кода для команд, использующих эту модель.

1548 слов

В этом руководстве пошагово описывается процесс создания рабочей системы от сырья до готового решения для проекта «Серия ИИ: Самая сложная часть RAG, часть 2 — Почему даже идеальные фрагменты данных приводят к плохому результату поиска». Основное внимание уделяется практически выполнимым шагам, четким проверкам и коду, который можно просто добавить в репозиторий без необходимости угадывать его назначение. На этапе обзора необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не пытаясь угадать скрытое состояние системы. Рядом с функциональными результатами следует записывать время выполнения и стоимость в виде токенов или запросов. Отображение затрат с самого начала помогает избежать неожиданных расходов при переходе от демо-версии к общедоступным средам.

Что же на самом деле такое эмбеддинг?

При работе над первым этапом «Что это?» сначала запишите условия работы алгоритма: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет избежать ошибок при последующих изменениях кода. Храните конфигурацию отдельно от кода приложения. Файлы с настройками окружения, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Измеряйте уровень точности по фиксированному набору вопросов перед настройкой подсказок. Изменение подсказок редко помогает улучшить качество поиска.

"How do I reset my password?"     → [0.81, 0.12, -0.44, 0.09]
"Steps to change your password"   → [0.79, 0.15, -0.41, 0.11]
"How do I cancel my subscription?"→ [0.22, 0.68, 0.05, -0.39]
from numpy import dot
from numpy.linalg import norm

def cosine_similarity(a, b):
    return dot(a, b) / (norm(a) * norm(b))

query = [0.80, 0.13, -0.42, 0.10]  # "reset my password"

print(cosine_similarity(query, [0.81, 0.12, -0.44, 0.09]))  # 0.998 - very close
print(cosine_similarity(query, [0.22, 0.68, 0.05, -0.39]))  # 0.310 - far apart

Почему поиск действительно сложен

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

Как выглядит настоящий пайплайн поиска

При работе над этапом «Что такое настоящее извлечение данных» сначала запишите условия взаимодействия: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Предпочитайте небольшие, тестируемые модули большим скриптам. Когда какой-то шаг терпит неудачу, ошибка должна указывать на конкретную ответственность, а не на запутанную цепочку операций. Измеряйте показатель воспроизводимости на фиксированном наборе вопросов перед настройкой подсказок. Частая смена подсказок редко помогает улучшить качество извлечения данных. При работе над этапом «Что такое настоящее извлечение данных» сначала запишите условия взаимодействия: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость в токенах или запросах. Отслеживание затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.

Гибридный поиск: почему одного только векторного поиска недостаточно

Гибридный поиск с использованием векторов работает наилучшим образом, когда рассматривается как измеримая поверхность. Соберите один идеальный пример, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Разделяйте политику разбиения данных на части и политику поиска. Изменение одной из них не должно приводить к необходимости переписывания другой при изменении показателей качества.

from collections import defaultdict

def reciprocal_rank_fusion(result_lists, k=60):
    scores = defaultdict(float)
    for result_list in result_lists:
        for rank, doc_id in enumerate(result_list):
            scores[doc_id] += 1 / (k + rank + 1)
    return sorted(scores, key=scores.get, reverse=True)

vector_ids = [d.metadata["chunk_id"] for d in vector_store.similarity_search(query, k=20)]
bm25_ids = [d.metadata["chunk_id"] for d in bm25_index.search(query, k=20)]

final_ranking = reciprocal_rank_fusion([vector_ids, bm25_ids])

Переупорядочивание — шаг, который никто не пропускает дважды

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

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-base")

def rerank(query, candidates, top_n=5):
    pairs = [(query, c["text"]) for c in candidates]
    scores = reranker.predict(pairs)
    for c, s in zip(candidates, scores):
        c["rerank_score"] = float(s)
    return sorted(candidates, key=lambda c: c["rerank_score"], reverse=True)[:top_n]

Исправление проблемы, а не только индекса

Этап «Исправление проблемы» работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг терпит неудачу, причина сбоя должна указывать на конкретный ответственный элемент, а не на запутанную цепочку операций. Разделяйте политику разбиения на части и политику извлечения данных. Изменение одной из них не должно вынуждать переписывать другую при изменении показателей качества. Этап «Исправление проблемы» работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.

def hyde_retrieve(query, llm, vector_store, k=5):
    hypothetical = llm.invoke(f"Write a short passage answering: {query}")
    return vector_store.similarity_search(hypothetical, k=k)

Измерение вместо оценки глазом

Для этапа «Измерение вместо оценки» необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища конфиденциальных данных и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Указывайте те участки текста, которые легли в основу ответа. Без цитат операторы не смогут отличить галлюцинации от пробелов в индексации.

def recall_at_k(retrieved_ids, relevant_ids, k):
    top_k = set(retrieved_ids[:k])
    return len(top_k & relevant_ids) / len(relevant_ids) if relevant_ids else 0.0

Что бы вы сказали тем, кто начинает работу сегодня

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

Неприятная правда о RAG

Для этапа «Неприятная правда» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Лучше использовать небольшие, тестируемые единицы кода вместо обширных скриптов. При сбое шага он должен указывать на конкретную причину, а не на сложную структуру обработки данных. Указывайте те фрагменты текста, которые легли в основу ответа. Без цитат операторы не смогут отличить вымысел от проблем с индексацией. Для этапа «Неприятная правда» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Записывайте время выполнения, а также стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости заранее предотвращает неожиданные счета при переходе от демо-версии к публичной.

среды.

Чек-лист операционной работы

Этот этап чек-листа работает наилучшим образом, когда его рассматривают как измеримую основу. Соберите один идеальный пример вывода, один случай сбоя и запись о откате перед расширением объёма работ.

Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи.

Разделяйте политику разбиения данных на части и политику их извлечения. Изменение одной из них не должно приводить к переписыванию другой при изменении показателей качества.

Оценивайте ответы, полученные за один обмен сообщениями, и последовательности обменов сообщениями отдельно. Суммирование оценок чата скрывает сбои в работе инструментов.

Напишите краткий руководство: как обновлять ключи, как опустошать очередь, как откатить последнюю загрузку данных.

Документируйте одновременно успешный сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не последующим доработкам.

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

Примечание для da3fc7392f6d: не храните ключи поставщика в репозитории, установите лимит токенов на сессию и сохраняйте отчеты рядом с фикстурами для оценки, чтобы последующие замены моделей оставались сопоставимыми.