当前位置:   article > 正文

基于Langchain +LLM,分享3个我最喜欢的提升检索效果的方法_llm rag 如果 向量库中 没有答案 应该怎么办

llm rag 如果 向量库中 没有答案 应该怎么办

检索增强生成(Retrieval Augmented Generation,RAG)是一个众所周知的方法,通常与大型语言模型(Large Language Model,LLM)一起使用,用于回答外部知识并减少LLM的幻觉。

LLM的知识限制需要通过在我们的AI管道中添加向量数据库来进行覆盖,这里将使用RAG方法从向量数据库中检索相关文档。

然而RAG方法并不是真正聪明的,因为其中一些方法仍然使用语义搜索相似性来检索相关文档,而且还有一些用户提示问题与向量数据库中的文档不相关。

这可能导致RAG系统无法检索到正确的文档,LLM也无法总结出正确的答案。

基于这种情况,我分享了来自 langchain 的三种我最喜欢的检索技术,可以用于您的RAG系统。Langchain提供了许多工具,可用于创建良好的RAG。在这里,我想列出这些功能。

通俗易懂讲解大模型系列

技术交流&资料

技术要学会分享、交流,不建议闭门造车。一个人可以走的很快、一堆人可以走的更远。

成立了大模型技术交流群,本文完整代码、相关资料、技术交流&答疑,均可加我们的交流群获取,群友已超过2000人,添加时最好的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。

方式①、微信搜索公众号:机器学习社区,后台回复:加群
方式②、添加微信号:mlc2060,备注:来自CSDN + 技术交流

1. 多查询检索器


多查询检索器是一种查询扩展形式。使用LLM,我们将从原始查询生成更多问题,每个生成的问题将用于检索相关文档。

这种技术尝试回答用户提示不够具体的情况。在这种情况下,我们可以利用LLM的能力生成更多问题。这些生成的问题将用于从向量数据库中检索文档。

其思想是使查询与主题更相关,这些问题可以使用生成的问题检索到更多相关文档。

这里我将分享与该方法相关的代码:

#-------------------------------准备向量数据库----------------------
# 构建一个示例向量数据库
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
import os

os.environ["OPENAI_API_KEY"] = "您的OpenAI密钥"

# 加载博客文章
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()

# 分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0)
splits = text_splitter.split_documents(data)

# 向量数据库
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)

#---------------------------准备多查询检索器--------------------
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.chat_models import ChatOpenAI

question = "What are the approaches to Task Decomposition?"
llm = ChatOpenAI(temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vectordb.as_retriever(), llm=llm
)

#----------------------设置问答系统----------------------------------------
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate


qa_system_prompt = """
        使用以下检索到的上下文来回答问题。 \
        如果你不知道答案,就说你不知道。 \

        {context}"""

qa_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", qa_system_prompt),
        ("human", "{question}"),
    ]
)

def format_docs(docs):
    doc_strings = [doc.page_content for doc in docs]
    return "\n\n".join(doc_strings)


rag_chain = (
    {"context": retriever_from_llm | format_docs, "question": RunnablePassthrough()}
    | qa_prompt
    | llm
    | StrOutputParser()
)

rag_chain.invoke("What are the approaches to Task Decomposition?")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

以上问题的答案是:

有三种任务分解方法:

  1. 使用简单提示的LLM:这种方法涉及使用诸如GPT-3之类的语言模型为给定任务生成逐步说明。该模型可以通过简单的指令如“XYZ的步骤”提示,然后生成一系列子目标或步骤以完成任务。

  2. 任务特定指令:这种方法涉及提供任务特定的指令来指导分解过程。例如,如果任务是写小说,指令可以是“写故事大纲”。这种方法有助于为分解过程提供更具体的指导和结构。

  3. 人类输入:这种方法涉及将人类输入纳入任务分解过程中。人类可以提供他们的专业知识和经验,将复杂的任务分解为更小、更易管理的子任务。在处理需要领域特定知识或专业知识的任务时,这种方法可能很有用。

这些方法可以根据任务的性质和可用资源单独或结合使用。

2.长上下文重新排序

这种方法非常适用于我们希望从向量数据库返回超过10个文档的情况。为什么我们需要返回那么多文档呢?也许,我们的块很短,而向量数据库存储了很多块。

当LLM询问一个包含这么多文档的问题时,有些情况下,LLM可能无法理解位于检索文档中间的文档的上下文。

这个想法写在这篇论文:https://arxiv.org/abs/2307.03172

这个问题可以通过使用Langchain函数来解决,该函数可以帮助重新排序相关文档。这确保了相关文档位于文档列表的开头和结尾。

在将它们提供给LLM进行总结之前,需要执行以下步骤。

#-------------------------------准备向量数据库----------------------
# 构建一个示例向量数据库
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
import os

os.environ["OPENAI_API_KEY"] = "您的OpenAI密钥"

# 加载博客文章
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()

# 分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=0)
splits = text_splitter.split_documents(data)

# 向量数据库
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)

#--------------------------------问答部分和重新排序------------------------------------
from langchain.chat_models import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.document_transformers import (
    LongContextReorder,
)

llm = ChatOpenAI()

qa_system_prompt = """
        使用以下检索到的上下文来回答问题。 \
        如果你不知道答案,就说你不知道。 \

        {context}"""

qa_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", qa_system_prompt),
        ("human", "{question}"),
    ]
)

def format_docs(docs):
    # 在此处调用重新排序函数
    reordering = LongContextReorder()
    reordered_docs = reordering.transform_documents(docs)
    doc_strings = [doc.page_content for doc in reordered_docs]
    return "\n\n".join(doc_strings)


rag_chain = (
    {"context": vectordb.as_retriever() | format_docs, "question": RunnablePassthrough()}
    | qa_prompt
    | llm
    | StrOutputParser()
)
rag_chain.invoke("What are the approaches to Task Decomposition?")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

上述问题的答案是:

有三种任务分解方法:

  1. 使用简单提示的LLM:这种方法涉及使用简单的提示来指导代理人将任务分解为较小的子目标。例如,可以提示代理人“XYZ的步骤”或“实现XYZ的子目标有哪些?”这种方法依赖于语言模型根据给定的提示生成适当的子目标。

  2. 任务特定指令:在这种方法中,通过向代理人提供任务特定的指令来进行任务分解。例如,如果任务是写小说,可以指示代理人“写一个故事大纲”。这种方法为代理人在分解任务方面提供了更具体的指导。

  3. 人类输入:任务分解也可以借助人类输入来完成。该方法涉及从人类那里接收输入或指导,将任务分解为较小的子目标。人类的输入可以是指令、建议或反馈,提供给代理人。

3. 上下文压缩


作为我们尝试的第一种方法,我们使用LLM来扩展我们的查询,对于这种方法,我们尝试从我们的相关文档中压缩或减少信息。

在我们的向量数据库中,每个块内的标记数量非常高,并且有些段落中的每个块有时包含不相关的信息。

为了使我们的LLM能够正确地进行总结,我们需要使用LLM从我们检索到的文档中删除那些不相关的段落。

以下是与该方法相关的代码:

#-------------------------------准备向量数据库----------------------
# 构建一个示例向量数据库
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
import os

os.environ["OPENAI_API_KEY"] = "您的OpenAI API密钥"

# 加载博客文章
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
data = loader.load()

# 分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=0)
splits = text_splitter.split_documents(data)

# 向量数据库
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=splits, embedding=embedding)

#----------------------------上下文压缩设置---------------------
from langchain.retrievers.document_compressors import DocumentCompressorPipeline, EmbeddingsFilter
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_transformers import EmbeddingsRedundantFilter
from langchain.retrievers import ContextualCompressionRetriever

splitter = CharacterTextSplitter(chunk_size=300, chunk_overlap=0, separator=". ")
redundant_filter = EmbeddingsRedundantFilter(embeddings=embedding)
relevant_filter = EmbeddingsFilter(embeddings=embedding, similarity_threshold=0.76)
pipeline_compressor = DocumentCompressorPipeline(
    transformers=[splitter, redundant_filter, relevant_filter]
)

compression_retriever = ContextualCompressionRetriever(
    base_compressor=pipeline_compressor, base_retriever=vectordb.as_retriever()
)

#--------------------------------问答部分------------------------------------
from langchain.chat_models import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI()

qa_system_prompt = """
        使用以下检索到的上下文来回答问题。 \
        如果你不知道答案,就说你不知道。 \

        {context}"""

qa_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", qa_system_prompt),
        ("human", "{question}"),
    ]
)

def format_docs(docs):
    doc_strings = [doc.page_content for doc in docs]
    return "\n\n".join(doc_strings)


rag_chain = (
    {"context": compression_retriever | format_docs, "question": RunnablePassthrough()}
    | qa_prompt
    | llm
    | StrOutputParser()
)

rag_chain.invoke("What are the approaches to Task Decomposition?")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

以上是以上述问题的答案:

有几种任务分解方法:

  1. 使用简单提示的LLM:这种方法涉及使用大型语言模型(LLM)如OpenAI的GPT-3来分解任务。通过提供简单的提示,如“XYZ的步骤”或“实现XYZ的子目标有哪些?”,LLM可以生成一系列子目标或步骤,以完成任务。

  2. 任务特定指令:在这种方法中,通过提供针对手头任务的具体指令来指导任务分解。例如,如果任务是写小说,则指令可以是“写一个故事大纲”。这有助于代理人将任务分解为较小的组件,并专注于每个步骤。

  3. 人类输入:任务分解也可以借助人类输入来完成。人类可以提供指导、专业知识和领域特定的知识,以帮助将复杂的任务分解为可管理的子目标。这种方法利用了人类的问题解决能力和直觉来辅助分解过程。

值得注意的是,这些方法可以结合使用,也可以单独使用,具体取决于任务的特定要求和约束。

结论

希望所有这些技术都能在您的下一个项目中得到应用。如果有一些情况存在重叠,所有这些技术都可以结合在一起使用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/481805
推荐阅读
相关标签
  

闽ICP备14008679号