AI Prompt Engineering · Урок

Переписывание запросов и HyDE

Повышение полноты поиска

Урок 4 из 413 шагов

«Переписывание запросов и HyDE» — бесплатный урок AI Prompt Engineering на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Prompt Engineering, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Prompt Engineering содержит 4 уроков всего.

Запрос — слабое звено

Качество поиска ограничено запросом. Исходные пользовательские запросы часто коротки, неоднозначны, изобилуют местоимениями или сформулированы не так, как документы. Преобразование запроса изменяет запрос перед поиском, повышая полноту и точность.

Это одно из самых дешёвых и эффективных улучшений по сравнению с наивной RAG: исправьте запрос — и каждый следующий этап выиграет.

def transform_query(raw, history):
    # Resolve references, expand, decompose, or hypothesize
    # BEFORE embedding and retrieving.
    return rewrite(raw, history)

Переписывание запроса

Самое простое преобразование: LLM переписывает пользовательский запрос в более ясную, самостоятельную и удобную для поиска форму. Она исправляет опечатки, раскрывает сокращения и удаляет разговорный шум.

Это особенно важно в многоходовом диалоге, где последнее сообщение непонятно без контекста («А как насчёт второго?»).

def rewrite_query(raw):
    prompt = (
        'Rewrite the user question into a clear, standalone search '
        'query optimized for document retrieval. Keep key entities.\n'
        'Question: ' + raw
    )
    return llm(prompt, temperature=0).strip()

Конденсация диалога

В диалоговой RAG объединяйте диалог и новый ход в один самостоятельный вопрос. Без этого местоимения и многоточия делают векторное представление бессмысленным, а поиск перестаёт работать.

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

def condense(history, follow_up):
    prompt = (
        'Given the conversation, rewrite the follow-up as a standalone '
        'question with all references resolved.\nConversation:\n' +
        render(history) + '\nFollow-up: ' + follow_up
    )
    return llm(prompt, temperature=0).strip()

Расширение запроса

Расширение создаёт синонимы, связанные термины или альтернативные формулировки, чтобы расширить лексическое и семантическое покрытие. Оно устраняет несоответствие словаря: в документе написано «invalidate», а пользователь говорит «revoke».

Расширяйте запрос для поиска, затем выполняйте поиск по объединённому списку; не показывайте пользователю расширенный вариант. Остерегайтесь чрезмерного расширения: оно может привлечь результаты не по теме.

def expand(query, n=3):
    prompt = (
        'List ' + str(n) + ' alternative phrasings of this query using '
        'synonyms and domain terms, one per line.\n' + query
    )
    variants = parse_lines(llm(prompt, temperature=0.5))
    return [query] + variants

Поиск по нескольким запросам

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

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

def multi_query(raw, n=4):
    queries = expand(raw, n)
    rankings = [dense_retrieve(q, 30) for q in queries]
    fused = rrf(*rankings)
    return dedup(fused)

Декомпозиция запроса

Для сложных многошаговых вопросов требуется декомпозиция на подвопросы, поиск по каждому из них и последующее объединение результатов. На вопрос о том, какой CEO компании, приобретшей X, старше… нельзя ответить с помощью одного поиска.

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

def decompose_and_retrieve(question):
    subs = parse_lines(llm(
        'Break this into independent sub-questions, one per line.\n' +
        question, temperature=0))
    evidence = {s: rerank(s, dense_retrieve(s, 30))[:3] for s in subs}
    return evidence  # generator synthesizes the final answer

HyDE: основная идея

HyDE (гипотетические векторные представления документов, Гао и др., 2022) меняет саму постановку задачи. Вместо того чтобы создавать векторное представление короткого запроса, система просит LLM сгенерировать гипотетический документ с ответом, затем создаёт векторное представление этого документа и выполняет поиск по нему.

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

def hyde(query):
    hypo = llm(
        'Write a short passage that would answer this question, as if '
        'from a reference document.\nQuestion: ' + query,
        temperature=0.3)
    return dense_retrieve_by_vector(embed(hypo), k=30)  # embed the answer

Почему HyDE повышает полноту поиска

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

Точность гипотетического документа не особенно важна: ему достаточно правильных слов и структуры, чтобы оказаться рядом с настоящими документами в пространстве векторных представлений.

# Robustness: average several hypothetical docs to reduce variance
def hyde_avg(query, n=3):
    vecs = [embed(llm(HYDE_PROMPT + query, temperature=0.5))
            for _ in range(n)]
    centroid = sum(vecs) / n
    return dense_retrieve_by_vector(centroid, 30)

Компромиссы и сценарии отказа HyDE

HyDE добавляет генерацию LLM перед поиском, увеличивая задержку и стоимость, и может галлюцинировать гипотетический документ, который уйдёт от темы. Это ухудшает полноту поиска для узкоспециализированных запросов или запросов вне распределения, о которых модель ничего не знает.

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

def hyde_hybrid(query):
    a = dense_retrieve_by_vector(embed(hyde_doc(query)), 30)
    b = dense_retrieve(query, 30)          # raw-query fallback
    return dedup(rrf(a, b))                 # robust to bad hypotheticals

Выбор и сочетание методов

Эти преобразования дополняют друг друга. Используйте конденсацию для диалогов, расширение и поиск по нескольким запросам при несоответствии словаря, декомпозицию для многошаговых вопросов и HyDE при несоответствии стиля вопроса и документа. Во многих производственных системах выстраивают цепочку: condense, затем HyDE, затем объединение с исходным запросом и переранжирование.

Каждое дополнительное преобразование требует отдельного вызова LLM, поэтому включайте их в зависимости от типа запроса, а не запускайте всегда все сразу.

def route_transform(query, history, qtype):
    q = condense(history, query) if history else query
    if qtype == 'multi_hop': return decompose_and_retrieve(q)
    if qtype == 'mismatch':  return hyde_hybrid(q)
    if qtype == 'vocab_gap': return multi_query(q)
    return dense_retrieve(q, 30)

Оценка преобразований

Оценивайте каждое преобразование по приросту полноты поиска при k результатах и точности итогового ответа по сравнению с исходным запросом на наборе без утечки данных. Отслеживайте добавленную задержку и стоимость LLM, чтобы оставлять только те преобразования, которые окупаются.

Будьте внимательны: преобразование, которое повышает полноту поиска, но добавляет отвлекающие результаты, может снизить точность ответа, если не сочетать его с переранжированием и сжатием.

def eval_transform(name, fn, eval_set):
    return {
        'recall@10': recall_at_k(eval_set, retriever=fn, k=10),
        'answer_acc': answer_accuracy(eval_set, retriever=fn),
        'extra_latency_ms': transform_latency(fn),
    }

Быстрая проверка

Объясните, почему HyDE работает, несмотря на несовершенство гипотетических ответов.

Итоги

Основные выводы:

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

Изучай AI Prompt Engineering с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
53
Уроки
199

Часто задаваемые вопросы

Урок «Переписывание запросов и HyDE» бесплатный?

Да — полный текст урока «Переписывание запросов и HyDE» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Prompt Engineering, подпишись на CoddyKit PRO. Курс AI Prompt Engineering содержит 4 уроков всего.

Чему я научусь в уроке «Переписывание запросов и HyDE»?

Повышение полноты поиска Ты практикуешь AI Prompt Engineering с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Prompt Engineering?

Предыдущий опыт не требуется. AI Prompt Engineering на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Переписывание запросов и HyDE»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Prompt Engineering?

Да. Каждый урок AI Prompt Engineering включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. За пределами наивного RAG
  2. Переранжирование найденных фрагментов
  3. Сжатие контекста
  4. Переписывание запросов и HyDE
← Назад к AI Prompt Engineering