微信扫码
与创始人交个朋友
我要投稿
随着生成式AI技术的蓬勃发展,我们迎来了将先进功能融入应用程序的新机遇。大型语言模型(LLM)以其惊人的能力,能够为用户提供详尽且准确的回答,这自然引发了将其集成到现有系统中的思考。想象一下,如果用户在应用程序中遇到困惑,他们可以通过内置的聊天功能迅速获得来自LLM的解答。更进一步,若我们的应用程序包含解释关键概念的博客文章,用户无需逐一阅读,而是可以直接向LLM提问,即时获取所需信息。
当决定在应用程序中集成LLM时,我们的目标是向用户提供全面而准确的信息。然而,很快我们便发现,直接使用现有的LLM模型存在局限性。这些模型往往缺乏关于我们特定应用程序的上下文知识,这导致它们在某些情况下无法提供满意的回答。更糟糕的是,当LLM对某个问题缺乏了解时,它可能会产生误导性的回答。
为了解决这个问题,我们考虑了RAG方法。RAG通过结合知识检索和文本生成技术,为LLM提供了必要的上下文信息。具体来说,当LLM接收到用户的问题时,它首先会检索与问题相关的知识片段(例如,来自我们应用程序的特定文档或数据)。然后,它将这些知识与问题一起作为输入,生成准确的回答。
然而,我们也意识到,为每个用户查询实时检索和传递大量数据可能是一项昂贵的操作。因此,在实施RAG方法时,我们需要仔细权衡性能与成本之间的关系,确保我们的系统既高效又经济。
通过采用RAG方法,我们有望克服LLM在缺乏上下文知识时的局限性,为用户提供更加准确和有用的回答。这将使我们的应用程序更加智能和高效,为用户带来更好的体验。
RAG 即 Retrieval Augmented Generation,检索增强生成,是一种结合信息检索和文本生成技术的方法,旨在增强大型语言模型(LLM)的推理能力。RAG并非与Transformer架构同时诞生,但它是基于Transformer等现代神经网络架构发展的产物。随着Transformer在文本处理中显示出强大的上下文学习能力,RAG方法逐渐崭露头角,用于在推理过程中为LLM提供额外的背景信息。
基本的 RAG 管道由三个步骤组成:索引、检索和生成。LLM 需要回答的所有信息都在向量数据库中编制索引。当用户提出问题时,我们可以从该向量数据库中检索信息的相关部分。最后,结合相关信息和用户的问题,我们可以提示 LLM 根据我们作为上下文提供的信息给出答案。让我们更详细地了解如何实现这一目标。
在索引阶段,我们从各种来源(如PDF文档、Word文档、Excel表格、Markdown文件等)中提取所需信息,并将其转换为纯文本格式。如果信息已经是纯文本(某些模型也可以使用图像或其他格式,也可以将其编入索引,但这是另一个话题,不在本文中讨论),那么可以直接使用。接下来,我们将这些文本数据送入向量数据库中进行索引。向量数据库存储了文本的嵌入表示,这些嵌入表示是通过深度学习模型计算得出的,能够捕捉文本中的语义信息。为了更高效地检索,我们通常将文本分割成较小的部分或块,并为每个部分计算嵌入表示,然后将其存储在数据库中。
当用户提出问题时,我们将问题转换为向量表示,使用与索引阶段相同的嵌入模型。然后,我们在向量数据库中搜索与问题向量最相似的文本块。通过计算问题与数据库中每个文本块之间的相似度得分,我们选取与问题最相关的前K个文本块。这些文本块可能包含与问题相关的背景信息或潜在答案。
在生成阶段,我们构建一个包含用户问题、相关上下文以及之前对话历史的提示,并将其传递给LLM。这些上下文信息可以帮助LLM更好地理解问题并生成准确的答案。与传统的仅依赖预训练数据的生成方式不同,RAG方法使LLM能够结合外部知识库中的信息来回答问题,从而提高其推理能力和准确性。
RAG方法具有以下优势:
增强的推理能力:通过结合外部知识库中的信息,RAG能够增强LLM的推理能力,使其能够回答更复杂、更具体的问题。
实时性:RAG方法允许我们在用户提出问题时实时检索相关信息,并立即生成答案。这使得我们能够为用户提供即时的、有针对性的帮助。
可扩展性:RAG方法不依赖于特定的LLM或嵌入模型,因此可以灵活地应用于各种场景和模型。此外,通过增加索引数据的数量和多样性,我们可以进一步提高RAG的性能和准确性。
在这个例子中,我们将引入一篇名为“Retrieval-Augmented Generation for Lange Language Models: A Survey”的论文。我们将使用该论文中包含的信息查询 LLM,以便它可以回答用户就论文内容相关的问题。我们将通过以下步骤,利用LangChain等工具和库,使得大型语言模型(LLM)能够回答用户关于该论文内容的问题。
首先,我们需要加载PDF文档并使用LangChain的PyPDF加载器来解析它,从而提取出其中的文本内容。
from langchain_community.document_loaders import PyPDFLoader
# 设置PDF文档的URL
document_url = "https://arxiv.org/pdf/2312.10997.pdf"
# 初始化加载器并加载PDF文档
loader = PyPDFLoader(document_url)
pages = loader.load()
由于PDF文档可能包含大量的文本,我们需要将其拆分为更小的文本块,以便后续进行索引和检索。这里,我们将使用LangChain提供的文本拆分器。
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 初始化文本拆分器,设置块大小和重叠字符数
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=400,# 每个文本块的大小
chunk_overlap=40,# 相邻文本块之间的重叠字符数
length_function=len,# 用于计算文本长度的函数
is_separator_regex=False# 是否使用正则表达式作为分隔符
)
# 拆分文档为文本块
chunks = text_splitter.split_documents(pages)
# 打印第一个文本块作为示例
print(chunks[0].page_content)# 注意这里可能需要访问正确的属性来获取文本内容
为了能够在后续步骤中高效地检索相关文本块,我们需要为它们计算向量表示。这里,我们将使用Hugging Face Hub上的开源嵌入模型BGE-small。它是一个开源的小型嵌入模型。
from langchain_community.embeddings import HuggingFaceBgeEmbeddings
# 设置嵌入模型的名称和参数
model_name = "BAAI/bge-small-en"
model_kwargs = {"device": "cpu"}
encode_kwargs = {"normalize_embeddings": True}
# 初始化嵌入模型
bge_embeddings = HuggingFaceBgeEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
# 提取文本块的内容并计算它们的向量表示
chunk_texts = list(map(lambda d: d.page_context, chunks))# 假设chunks中的每个元素都有page_content属性
embeddings = bge_embeddings.embed_documents(chunk_texts)
print(embeddings[0])
一旦我们计算了所有文本块的向量表示,下一步是将这些向量和对应的文本内容存储到向量数据库中。在这个示例中,我们将使用FAISS库来创建一个内存中的向量数据库。
from langchain_community.vectorstores import FAISS
# 假设embeddings是一个包含所有文本块嵌入的列表,chunk_texts是一个包含对应文本内容的列表
text_embedding_pairs = zip(chunk_texts, embeddings)
# 初始化FAISS向量数据库
db = FAISS.from_embeddings(text_embedding_pairs, bge_embeddings)
数据库已设置完毕。现在,我们可以接受用户的查询,并使用嵌入模型对其进行编码。然后,我们会在向量数据库中检索与查询最相似的文本块。
from langchain.embeddings import HuggingFaceEmbeddings
query = "which are the drawbacks of Naive RAG?"
# 初始化与之前相同的嵌入模型
query_embeddings = embeddings.embed_query(query)
# 在向量数据库中搜索与查询最相似的文本块
top_results = db.similarity_search(query_embeddings, k=5)
# 打印最相似文本块的内容
for result in top_results:
print(result.document.page_content)# 假设每个结果对象都有document属性,其中包含文本内容
在检索到与查询相关的文本块后,我们可以使用这些上下文来构建提示,并将其传递给大型语言模型(LLM)以获取答案。在这个例子中,我们将使用Anthropic的Claude模型。
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from google.colab import userdata
# 构建提示模板
prompt = ChatPromptTemplate.from_template(
"""
You are an expert on the topic of the retrieved document.
Here is the context from the document:
{context}
Human: {question}
"""
)
# 设置Anthropic的API密钥(这里仅为示例,请替换为实际API密钥)
anthropic_api_key = "YOUR_ANTHROPIC_API_KEY"
# 初始化Anthropic的Claude模型
llm = Anthropic(model_name="claude-3-haiku-20240307", api_key=anthropic_api_key)
# 创建ChatChain,它将提示和LLM组合在一起
chain = ChatChain.from_prompt(prompt, llm)
# 调用ChatChain以获取答案
response = chain.run(
{"context": '\n\n'.join(list(map(lambda r: r.document.page_content, top_results))), "question": query}
)
print(response)
尽管RAG(检索增强生成)解决方案为应用程序提供了利用LLM(大型语言模型)和现有数据的强大功能,但并非所有情况都适用。以下是RAG实现中可能遇到的一些常见问题及其潜在陷阱:
检索不相关的信息:当检索器从向量数据库中获取与查询不相关的数据时,LLM可能会受到误导,导致回答偏离问题主题。为避免这种情况,应优化检索机制,确保检索到的上下文与查询高度相关。
错过重要信息:如果数据库中没有包含回答问题所需的关键信息,或者检索器未能找到这些相关块,则RAG系统的性能将受到限制。因此,应定期更新和扩展数据库,同时优化检索算法以提高准确性。
生成上下文不支持的响应:即使检索到的上下文包含所需信息,但LLM如果没有正确使用这些信息,而是依赖于自己的预训练数据,那么RAG系统的优势将无法充分发挥。为确保LLM始终依赖上下文进行回答,可以开发一种机制来监测和纠正不恰当的响应。
对查询的不相关响应:LLM可能会使用所有提供的信息来生成响应,但这并不意味着它始终能够准确地回答用户的问题。因此,需要确保LLM能够聚焦于原始问题,避免在大量信息中迷失方向。
由类似上下文引起的冗余响应:当数据库中存在多个包含相似信息的文档时,检索器可能会返回多个几乎相同的信息块。这可能导致LLM在响应中多次重复相同的信息。为避免这种情况,可以在检索过程中实施去重策略,或在后处理阶段合并相似信息。
为了解决上述问题并优化RAG系统的性能,可以采取以下策略:
优化检索机制:使用更先进的检索算法和技术,如基于深度学习的语义相似度匹配,以提高检索的准确性和相关性。
定期更新和扩展数据库:确保数据库包含最新和全面的信息,以支持广泛的查询和问答任务。
开发响应监测机制:实施一种机制来检测LLM生成的响应是否与上下文相关,并纠正不恰当的响应。
引导LLM聚焦于原始问题:在提示中明确指示LLM关注原始问题,并在后处理阶段对响应进行校验以确保其相关性。
实施去重策略:在检索和后处理阶段实施去重策略,以减少冗余信息的出现。
要改进RAG应用程序,首先需要能够测量和评估其性能。通过制定明确的评估指标和测试集,可以量化RAG系统的准确性和相关性,从而指导进一步的优化工作。同时,评估结果也可能表明基本的RAG设置对于特定用例已经足够,无需过度复杂化。
在后续的文章中,我将更详细地探讨先进的RAG技术和策略,这些技术和策略将帮助我们避免常见问题,并将RAG应用程序提升到一个新的水平。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-05-28
2024-04-26
2024-04-11
2024-08-21
2024-07-09
2024-08-13
2024-07-18
2024-10-25
2024-07-01
2024-06-17