首页 > 教程攻略 > ai资讯 >LangChain实战 | 实现一个检索增强生成系统(RAG)

LangChain实战 | 实现一个检索增强生成系统(RAG)

来源:互联网 时间:2026-07-20 13:06:46

在人工智能技术快速迭代的当下,大语言模型(比如GPT-4)展现出的语言生成能力确实令人惊叹。但话说回来,这类模型也有其固有的短板:它们无法动态更新自己的知识库,对特定领域的深度信息掌握有限,搞不好还会生成一些“听起来头头是道,但实际上是错的”答案。为了破解这些难题,RAG(检索增强生成)技术就这么诞生了。

RAG最大的魅力在于,它不只是生成一个答案,还会把支撑答案的上下文文本一并提供给你。这个能力,说白了,就是给答案加了“注释”和“引用”,显著提升了系统的

可信度

可解释性

。尤其是在法律、医学、学术研究这些对信息来源极其敏感的场景里,这个能力几乎是不可或缺的。

在这篇文章里,我们会拆解如何构建一个既能动态整合外部知识库,又能提供引用文本的RAG系统。废话不多说,直接进入正题。

1. 业务需求描述

一个生产环境下的知识库,文档总是会不断变化的——可能有新文件加入,也可能有旧文件被修改。系统需要具备“感知”这种变化的能力,并自动完成以下任务:

  • 精准识别

    :通过计算文件的哈希值,识别出哪些是新增或更新过的文件。
  • 自动更新

    :对新文件或变更文件生成向量嵌入,并更新到向量数据库中。
  • 避免重复劳动

    :对于没有变化的文件,不进行重复处理,以提升整体效率。

更进一步,系统不仅要生成符合自然语言习惯的答案,还必须明确告诉用户这个答案是从哪来的,给出可以交叉验证的引用文本。这才是提升可信度和可解释性的关键所在。

  • 用户提出问题后,系统第一时间检索出最相关的文档片段,作为上下文喂给生成模型。
  • 在每个返回的文本片段里,都附带文件名、来源路径等元信息,方便溯源。

2. 系统的工作流程

一个完整的RAG系统,它的运行流程大致可以分为几个关键步骤。下面的流程图展示了它的全貌:

可以看到,在生成答案的同时,系统会原封不动地把引用的文档内容展示出来。这和那种只给一个“结论”的传统生成式模型相比,差异一目了然。

3. 关键技术要点

3.1 文件更新与动态管理

为了保证知识库的实时性和准确性,核心手段就是计算文件的哈希值。一旦系统检测到哈希值有变化,就会自动触发新嵌入的生成并更新向量数据库,整个过程不需要人工干预。

3.2 提供引用文本的重要性

在实际应用中,这个“提供引用”的能力,直接解决了三个关键问题:

  1. 增强可信度

    :用户不再需要“盲信”模型的输出,而是可以亲自去核实引用的原文。
  2. 提升可解释性

    :面对复杂问题时,引用上下文能让用户明白答案的推理依据和背景信息。
  3. 支持领域审查

    :在法律、医学等需要严格审查的领域,没有引用文本的答案,基本是不合格的。

4. LangChain组件概述

要实现这么一套系统,LangChain是一个相当趁手的工具框架。它凭借模块化的设计,把从数据加载到问答生成的整个流程大大简化了。我们在这个系统里用到的核心模块包括:

  1. 数据加载器(Loader)

    :能轻松搞定文本、PDF等多种格式的数据加载。
  2. 文本分割器(Text Splitter)

    :负责把长篇大论切成适合检索的短片段。
  3. 嵌入与向量存储

    :将文本内容“翻译”成高维向量,并存起来做快速检索用。
  4. Prompt管理

    :灵活设计送入生成模型的提示词。
  5. 问答链(Chain)

    :把“检索”和“生成”这两个环节串联起来,形成从问题到答案的完整闭环。

接下来,我们就基于这些组件,实现一个支持引用文本的RAG系统,看看核心思路到底是什么。

5. 核心实现思路

5.1. 文档加载与预处理

第一步,当然是把文档加载进来。LangChain里的 DirectoryLoader 非常强大,可以一次性把一个文件夹里的所有文本文件都加载好。加载进来之后,还得进行预处理,比如清理无关字符,以及最重要的——文本分割。

loader = DirectoryLoader(
dir_path,
glob="**/*.txt",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"}
)

分割文本时,我们把每个小片段的长度控制在500个字符左右,并且设置了50个字符的区间重叠。这么做的目的很明确:确保上下文的完整性。即便一个答案横跨了两个段落,在检索时也不会因为切割而丢失语义。

text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
texts = text_splitter.split_documents(data)

5.2. 嵌入与存储

分割好的文本片段,要用嵌入模型(比如 OpenAI Embeddings)转化成向量。嵌入模型的工作是把文本映射到一个高维空间,在这个空间里,语义相近的文本,它们的向量距离也更近。生成的向量会存到专门的向量数据库里,比如Chroma。

在存储向量时,一个关键操作是:把文档的元信息也一并存进去。这些元信息包括:

  • 文档来源(文件名、路径)。
  • 文件的哈希值(用来检测后来有没有被修改过)。

这个存储元信息的步骤至关重要,它是后续能提供引用文本的基础。有了它,我们才能在检索阶段精准地返回每个片段的来源和具体内容。

embeddings = OpenAIEmbeddings(model="text-embedding-3-large", base_url="https://api.openai-hk.com/v1")
vectorstore = Chroma.from_documents(
documents=texts,
embedding=embeddings,
collection_name="book_demo",
persist_directory="./chroma_db"
)

5.3. 问题检索与答案生成

当用户提交一个问题时,系统会先做检索。这个阶段的目标很纯粹:找到能够支持答案的相关文本片段,而不是试图直接生成答案。在LangChain里,检索是通过计算向量相似度(比如余弦相似度)来完成的。

检索到的上下文片段,会和用户问题一起,打包成一个精心设计的Prompt,然后送入生成模型。这个Prompt的设计非常关键,它会引导模型“只看”提供的上下文,而不是去调用自己的“固有知识”瞎猜。

上下文信息如下:
---------------------
{context}
---------------------
请根据以上提供的上下文信息,不依赖其他外部知识,回答以下问题:
问题: {input}
答案:

这里的 {context} 是系统找到的相关文档片段,{input} 是用户提问。生成模型会严格基于这个上下文来输出答案。

def create_rag_chain(vectorstore, top_k=3):
"""创建RAG问答链

Args:
vectorstore: 向量存储实例
top_k (int): 检索的文档数量

Returns:
RetrievalChain: RAG链实例
"""
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
base_url="https://api.openai-hk.com/v1"
)

retriever = vectorstore.as_retriever(search_kwargs={"k": top_k})

PROMPT = PromptTemplate.from_template("""
上下文信息如下
---------------------
{context}
---------------------
请根据以上提供的上下文信息,不依赖其他外部知识,回答以下问题
问题: {input}
答案:
""")

question_answer_chain = create_stuff_documents_chain(llm, PROMPT)
return create_retrieval_chain(retriever, question_answer_chain)

5.4. 提供引用文本

最后,也是最有价值的一步:除了最终答案,系统会把检索到的上下文片段作为“引用文档”一并展示给用户。来看个例子,当用户问“菩提祖师身边有多少个人?”时,系统的返回结果会是这样的:

问题:

菩提祖师身边有多少个人?

答案:

菩提祖师身边有三十个小仙侍立台下。

引用文档:

  1. Source 1:

    text : 这猴王整衣端肃,随童子径入洞天深处观看:一层层深阁琼楼,一进进珠宫贝阙,说不尽那静室幽居,直至瑶台之下。见那菩提祖师端坐在台上,两边有三十个小仙侍立台下。果然是: 大觉金仙没垢姿,西方妙相祖菩提; 不生不灭三三行,全气全神万万慈。 空寂自然随变化,真如本性任为之...

    file_hash : 2545120aa977ff5dddc91f4cdabc808e

    source : ../data/xiyouji/西游记第一回.txt

这种展示形式,让用户不仅知道答案是从哪段文字里“提炼”出来的,还能直接沿着线索去阅读原文,验证答案的准确性。如果对答案有疑问,直接点开源文档一看便知。

    for i, doc in enumerate(response["context"]):
print(f"nSource {i+1}:")
print(f" text: {clip_text(doc.page_content, threshold=350)}")
for key in doc.metadata:
if key != "pk":
val = doc.metadata.get(key)
clipped_val = clip_text(val) if isinstance(val, str) else val
print(f" {key}: {clipped_val}")

6. 应用场景

这种支持引用文本的RAG系统,应用前景相当广阔:

  1. 企业知识管理

    :员工能从海量内部文档中快速找到答案,并且能看到答案的出处,方便核实。
  2. 法律咨询

    :给出法律建议时,同步附上相关的法规条文或案例原文,让建议更有说服力。
  3. 学术研究

    :研究人员可以检索到论文的具体片段,回答问题时还能标明来源,对学术规范很有帮助。
  4. 医疗问答

    :在提供医学知识的同时,附加权威文献或诊疗指南的内容,提升建议的可靠性。

总的来说,实现一个能提供引用文本的RAG系统,不仅能动态整合外部知识,还能通过“有理有据”的方式,让答案的可信度和可解释性提升一个档次。这种能力,让RAG在知识密集型领域里大有可为。未来,这个系统还可以继续扩展,比如加入对图片、表格等多模态数据的支持,或者实时抓取网络信息,为用户提供更全面的智能服务。