langchain从入门到精通(二十七)——RAG优化策略(五)混合策略实现doc-doc对称检索
在使用 HyDE混合策略 转换 query 时,生成的 doc 如下作者可能会说,艺术比工程更持久和独立。之前学习的优化策略都是将对应的 查询 生成 新查询,通过 新查询 来执行相应的检索,但是在数据库中存储的数据一般都是 文档 层面上的,数据会远远比 查询 要大很多,所以 query 和 doc 之间是不对称检索,能找到的相似性文档相对来说也比较少。对于 doc-doc 类型的检索,虽然在语义空
1. HyDE 混合策略
之前学习的优化策略都是将对应的 查询 生成 新查询,通过 新查询 来执行相应的检索,但是在数据库中存储的数据一般都是 文档 层面上的,数据会远远比 查询 要大很多,所以 query 和 doc 之间是不对称检索,能找到的相似性文档相对来说也比较少。
例如:**今天回家的路上看到了美丽的风景,非常开心!想学习 python 该怎么办?**这个请求中,前面的风景、开心等词语均为无关信息。会对真实的请求学习 python 产生干扰。如果直接搜索用户的请求,可能会产生不正确或无法回答的 LLM 响应。因此,有必要使得用户查询的语义空间与文档的语义空间保持一致。
特别是 query 和对应的相关内容(答案)可能只存在弱相关性,导致难以找到最相关的文档内容。在这篇论文《Precise Zero-Shot Dense Retrieval without Relevance Labels》
中提出了一个 HyDE混合策略 的概念,首先 利用 LLM 将问题转换为回答问题的假设性文档/假回答,然后使用嵌入的 假设性文档 去检索真实文档,前提是因为 doc-doc 这个模式执行相似性搜索可以尝试更多的匹配项。
论文地址:https://arxiv.org/pdf/2212.10496
假回答和真回答虽然可能存在事实错误,但是会比较像,因此能更容易找到相关内容。
简单来说,就是先根据 query 生成一个 doc,然后根据 doc 生成对应的 embedding,再执行相应的检索,运行流程如下:
在 LangChain 中,并没有封装基于 HyDE混合策略 的检索器,所以需要自定义一个检索器,并实现 _get_relevant_documents() 方法,具象化代码如下:
from typing import List
import dotenv
import weaviate
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.language_models import BaseLanguageModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.retrievers import BaseRetriever
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_weaviate import WeaviateVectorStore
from weaviate.auth import AuthApiKey
dotenv.load_dotenv()
class HyDERetriever(BaseRetriever):
"""HyDE混合策略检索器"""
retriever: BaseRetriever
llm: BaseLanguageModel
def _get_relevant_documents(
self, query: str, *, run_manager: CallbackManagerForRetrieverRun
) -> List[Document]:
"""传递检索query实现HyDE混合策略检索"""
# 1.构建生成假设性文档的prompt
prompt = ChatPromptTemplate.from_template(
"请写一篇科学论文来回答这个问题。\n"
"问题: {question}\n"
"文章: "
)
# 2.构建链应用
chain = (
{"question": RunnablePassthrough()}
| prompt
| self.llm
| StrOutputParser()
| self.retriever
)
return chain.invoke(query)
# 1.构建向量数据库与检索器
db = WeaviateVectorStore(
client=weaviate.connect_to_wcs(
cluster_url="https://mbakeruerziae6psyex7ng.c0.us-west3.gcp.weaviate.cloud",
auth_credentials=AuthApiKey("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"),
),
index_name="DatasetDemo",
text_key="text",
embedding=OpenAIEmbeddings(model="text-embedding-3-small"),
)
retriever = db.as_retriever(search_type="mmr")
# 2.创建HyDE检索器
hyde_retriever = HyDERetriever(
retriever=retriever,
llm=ChatOpenAI(model="gpt-3.5-turbo-16k", temperature=0),
)
# 3.检索文档
documents = hyde_retriever.invoke("关于LLMOps应用配置的文档有哪些?")
print(documents)
print(len(documents))
输出内容:
[Document(metadata={'source': './项目API文档.md', 'start_index': 0.0}, page_content='LLMOps 项目 API 文档\n\n应用 API 接口统一以 JSON 格式返回,并且包含 3 个字段:code、data 和 message,分别代表业务状态码、业务数据和接口附加信息。\n\n业务状态码共有 6 种,其中只有 success(成功) 代表业务操作成功,其他 5 种状态均代表失败,并且失败时会附加相关的信息:fail(通用失败)、not_found(未找到)、unauthorized(未授权)、forbidden(无权限)和validate_error(数据验证失败)。\n\n接口示例:\n\njson { "code": "success", "data": { "redirect_url": "https://github.com/login/oauth/authorize?client_id=f69102c6b97d90d69768&redirect_uri=http%3A%2F%2Flocalhost%3A5001%2Foauth%2Fauthorize%2Fgithub&scope=user%3Aemail" }, "message": "" }'), Document(metadata={'source': './项目API文档.md', 'start_index': 1621.0}, page_content='id -> uuid:应用 id,类型为 uuid。\n\nname -> string:应用名称。\n\nicon -> string:应用图标。\n\ndescription -> string:应用描述。\n\npublished_app_config_id -> uuid:已发布应用配置 id,如果不存在则为 null。\n\ndrafted_app_config_id -> uuid:草稿应用配置 id,如果不存在则为 null。\n\ndebug_conversation_id -> uuid:调试会话记录 id,如果不存在则为 null。\n\npublished_app_config/drafted_app_config -> json:应用配置信息,涵盖草稿配置、已发布配置,如果没有则为 null,两个配置的变量信息一致。\n\nid -> uuid:应用配置 id。\n\nmodel_config -> json:模型配置,类型为 json。\n\ndialog_round -> int:携带上下文轮数,类型为非负整型。'), Document(metadata={'source': './项目API文档.md', 'start_index': 5818.0}, page_content='json { "code": "success", "data": { "list": [ { "id": "1550b71a-1444-47ed-a59d-c2f080fbae94", "conversation_id": "2d7d3e3f-95c9-4d9d-ba9c-9daaf09cc8a8", "query": "能详细讲解下LLM是什么吗?", "answer": "LLM 即 Large Language Model,大语言模型,是一种基于深度学习的自然语言处理模型,具有很高的语言理解和生成能力,能够处理各式各样的自然语言任务,例如文本生成、问答、翻译、摘要等。它通过在大量的文本数据上进行训练,学习到语言的模式、结构和语义知识'), Document(metadata={'source': './项目API文档.md', 'start_index': 490.0}, page_content='带有分页数据的接口会在 data 内固定传递 list 和 paginator 字段,其中 list 代表分页后的列表数据,paginator 代表分页的数据。\n\npaginator 内存在 4 个字段:current_page(当前页数) 、page_size(每页数据条数)、total_page(总页数)、total_record(总记录条数),示例数据如下:')]
2. 局限性与失败案例
对于 doc-doc 类型的检索,虽然在语义空间上保持了一致,但是在 query->doc 的过程中,受限于各种因素,仍然可能产生错误信息。
- 第一个场景是在 query 没有足够上下文时,HyDE 容易误解对应的词,从而产生错误的信息。
例如提问 Bel是什么?,在没有执行 HyDE 混合策略而是直接查询得到答案如下:
Bel 是由 Paul Graham 在四年的时间里(2015年3月26日至2019年10月12日),用 Arc 语言编写的一种编程语言。它基于 John McCarthy 最初的 Lisp,但添加了额外的功能。它是一个以代码形式表达的规范,旨在成为计算的形式化模型,是图灵机的一种替代方案。但是执行 HyDE 混合策略生成假设性 doc 如下,Bel 是 Paul Graham 的化名,他是这段信息背后的作者,当时需要种子资金以维持生活,并且参与了一项交易,后来成为 Y Combinator 模式的典范。在这个例子中,HyDE 在没有文档上下文的情况下错误地解释了 Bel,这会导致完全检索不到相关的文档信息。 - 第二个场景是一些 开放式的查询,HyDE 可能会产生偏见,例如提问 作者会如何评价艺术与工程的区别?,无需转换 query 即可得到正确的响应回答
作者可能会说,艺术和工程是两种需要不同技能和方法的学科。艺术更注重表达和创造力,而工程更专注于解决问题和技术知识。作者还暗示,艺术学校并不总是提供与工程学校同等水平的严谨性,绘画学生常常被鼓励发展个性化风格,而不是学习绘画的基础知识。此外,作者可能会指出,工程学相比艺术能提供更多的财务稳定性,正如作者自己创业初期需要种子资金来生活的经历所证明的那样。
在使用 HyDE混合策略 转换 query 时,生成的 doc 如下作者可能会说,艺术比工程更持久和独立。他们提到,今天编写的软件几十年后就会过时,系统工作也不会长久。相比之下,他们指出绘画可以保留数百年,而且作为艺术家是可以谋生的。他们还提到,作为艺术家,你可以真正独立,不需要老板或研究资金。此外,他们指出艺术可以成为收入来源,适合那些无法接触传统就业形式的人,比如例子中的模特,能够通过为当地古董商建模和制作赝品而谋生。
总的来说,HyDE 是一个无监督的方法,可以帮助 RAG 提高效果。但是因为它不完全依赖于 embedding 而是强调问题的答案和查找内容的相似性,也存在一定的局限性。 比如如果 LLM 无法理解用户问题,自然不会产生最佳结果,也可能导致错误增加。因此,需要根据场景决定是否选用此方法
更多推荐
所有评论(0)