Bootstrap

玩转 LangChain:从文档加载到高效问答系统构建的全程实战

系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战



前言

在人工智能技术快速发展的背景下,大语言模型(LLM)的应用场景越来越广泛,例如自动化文本生成、智能问答系统、知识提取等。然而,如何高效地与这些大模型交互,成为开发者面临的一大挑战。LangChain正是为解决这一问题而生的。

LangChain 是一个开源框架,旨在帮助开发者更便捷地与大语言模型(如 OpenAI 的 GPT 系列、阿里云通义千问 Qwen)进行交互。通过 LangChain,开发者可以轻松整合模型调用、Prompt 模板管理、复杂任务链条设计以及输出解析等功能,大幅提升开发效率和应用的可靠性。

LangChain 的核心模块

LangChain 由以下核心模块组成,每个模块都有其特定的功能:

  1. Model(模型)

    • 提供与大语言模型交互的接口,例如 OpenAI、阿里云等的 LLM。
    • 开发者可以轻松配置 API 调用和模型参数。
  2. Prompt(提示词)

    • 提供动态提示词模板管理功能,支持变量插值、Prompt 优化。
    • 适用于创建灵活且高效的模型交互任务。
  3. Chains(链条)

    • 用于将多个任务步骤组合成一个链条,例如多次调用模型完成复杂的推理任务。
    • 支持模块化设计,便于维护和扩展。
  4. Memory(记忆)

    • 提供上下文记忆功能,可以让模型在多轮对话中记住用户的输入和历史对话内容。
    • 适合构建长时间、多轮交互的对话系统。
  5. Output Parsers(输出解析器)

    • 解析模型的返回结果,例如将文本解析为 JSON 结构,便于后续处理。
    • 特别适用于信息提取、分类任务等。
  6. Agents(代理)

    • 集成多种工具(如 API、数据库、文件系统)与模型交互,使模型能够动态调用外部资源完成复杂任务。
    • 适合构建更智能化的自动化工作流。

LangChain 的应用场景

LangChain 的灵活性和模块化设计使其广泛适用于以下场景:

  • 文本生成与翻译:例如生成新闻稿、调整语气风格、翻译专业文档。
  • 智能问答与知识库:构建基于文档、数据库的知识问答系统。
  • 信息提取与分析:从非结构化文本中提取关键信息,例如用户评论分析、商业报告解析。
  • 对话系统与聊天机器人:利用记忆模块支持上下文多轮对话,实现类似 ChatGPT 的应用。
  • 自动化工作流:通过 Agents 模块集成外部工具,完成复杂的任务链,例如自动处理订单或执行 API 查询。

本文主题

本文将重点介绍如何使用 LangChain 实现 基于文档的问答系统。通过实际的代码示例,您将学习如何使用 LangChain 加载文档、构建查询系统、使用向量搜索进行文档检索、以及通过模型对查询进行总结和处理。

通过阅读本文,你将学会:

  1. 如何加载文档并准备好查询数据。
  2. 如何使用 向量存储(Vectorstore)检索模型(Retriever) 进行高效的文档检索。
  3. 如何通过 LangChain 中的 QA ChainLLM 模型 生成详细的响应。
  4. 如何格式化和展示查询结果,提升用户体验。

一、LangChain 环境搭建与初始配置

在开始构建 LangChain 应用之前,需要完成基础环境的搭建和配置。

1.1 安装依赖

在项目环境中安装必要的 Python 包:

pip install langchain langchain-community langchain_openai python-dotenv openai docarray langchain-huggingface

这些依赖包含了 LangChain 框架、环境变量管理工具 python-dotenv 和与 OpenAI 模型交互的接口。

1.2 环境变量加载

为了保护敏感信息(如 API Key 和 API URL),建议将这些信息存储在项目根目录下的一个名为 .env 的文件中。这样可以避免将敏感信息直接暴露在代码中,同时方便环境的统一配置。

1.2.1 具体步骤

  1. 创建 .env 文件
    在项目根目录下创建一个名为 .env 的文件。注意,这个文件不需要任何扩展名。
    如果使用版本控制(如 Git),记得将 .env 文件添加到 .gitignore 中,避免敏感信息被提交到代码仓库。

  2. 编写 .env 文件内容
    .env 文件的内容采用键值对的形式,每行一个键值对,格式如下:

阿里云通义千问(Qwen)API 配置
ALIYUN_API_KEY=你的阿里云API密钥
ALIYUN_API_URL=你的阿里云API地址,例如:https://dashscope.aliyuncs.com/compatible-mode/v1

DeepSeek API 配置
DEEPSEEK_API_KEY=你的DeepSeek API密钥
DEEPSEEK_API_URL=你的DeepSeek API地址,例如:https://api.deepseek.com

OpenAI API 配置
OPENAI_API_KEY=你的OpenAI API密钥
OPENAI_API_URL=https://api.openai.com/v1

  • 键名ALIYUN_API_KEYALIYUN_API_URL 是阿里云 API 的密钥和访问地址;DEEPSEEK_API_KEYDEEPSEEK_API_URL 是DeepSeek API 的密钥和地址;OPENAI_API_KEYOPENAI_API_URL 是OpenAI API 的密钥和地址。
  • :具体的密钥和 URL 需要根据实际情况替换为你自己的值。
  1. 在代码中加载 .env 文件
    使用 python-dotenv 模块加载 .env 文件中的内容到 Python 程序中。示例如下:
   import os
   from dotenv import load_dotenv

   # 加载 .env 文件中的环境变量
   load_dotenv()

   # 获取环境变量的值
   api_key = os.getenv("ALIYUN_API_KEY")
   base_url = os.getenv("ALIYUN_API_URL")

1.2.2 注意事项

  • .env 文件应放在项目根目录下,与主代码文件(如 main.py)处于同一级目录。这样 load_dotenv() 可以自动找到 .env 文件。
  • 在使用其他环境变量(如 DEEPSEEK_API_KEYDEEPSEEK_API_URL)时,直接通过 os.getenv("<变量名>") 访问即可。
  • 确保 .env 文件已正确加载。如果程序中获取不到变量值,请检查文件路径和格式是否正确。

通过这种方式,可以在保护敏感信息的同时,方便多环境配置和管理。

1.3 初始化模型客户端

使用 LangChain 提供的 ChatOpenAI,连接阿里云通义千问模型(Qwen):

from langchain_openai import ChatOpenAI # type: ignore

llm = ChatOpenAI(
    openai_api_key=api_key,
    model_name="qwen-plus",
    base_url=base_url
)

至此,环境已经完成初始化,可以开始与模型交互。


二、创建 Q&A 系统

在本节中,将展示如何使用 LangChain 创建一个基于文档的问答系统。通过此系统,用户可以输入查询,系统能够从预先加载的文档中提取相关信息,并根据需求生成回答。利用 LangChain 中的核心模块,如文档加载器、向量存储、检索器和 LLM 模型,逐步构建完整的问答系统。

OutdoorClothingCatalog_1000.csv

2.1 准备文档数据

首先,需要准备文档数据。在这个例子中,我们使用一个产品目录的 CSV 文件作为数据源,该文件包含了多个产品的描述和信息。通过 LangChain 的 CSVLoader 模块,我们可以方便地加载文档并将其转换为可查询的格式。

2.1.1 加载文档

在实际应用中,文档通常存储在 CSV、JSON 或其他格式中。在这里,我们使用 CSV 文件格式。以下代码展示了如何加载 CSV 文件:

from langchain.document_loaders import CSVLoader

file = 'OutdoorClothingCatalog_1000.csv'
loader = CSVLoader(file_path=file, encoding='utf-8')

我们通过 CSVLoader 来加载文件,并指定文件的路径和编码方式。loader 对象会读取文件内容并转换为 LangChain 可以处理的文档对象。

2.1.2 查看加载的文档

加载文档后,我们可以查看文档的部分内容,确保数据加载正确:

docs = loader.load()
print(docs[0])

输出会显示第一条文档的内容,包括产品的名称、描述以及元数据。例如:

page_content=': 0
name: Women's Campside Oxfords
description: This ultracomfortable lace-to-toe Oxford boasts a super-soft canvas, thick cushioning, and quality construction for a broken-in feel from the first time you put them on. 
...
metadata={'source': 'OutdoorClothingCatalog_1000.csv', 'row': 0}

2.2 创建向量索引

接下来,我们需要使用 向量索引(Vectorstore) 来为文档数据创建索引。这使得我们可以对文档进行高效的相似性搜索。LangChain 提供了 DocArrayInMemorySearch 作为内存中的向量存储方案。我们将使用 VectorstoreIndexCreator 来创建索引,并通过加载的文档数据进行初始化。

2.2.1 创建向量存储索引

在创建索引之前,我们需要先为文档生成向量表示。LangChain 提供了 HuggingFaceEmbeddings 模块,可以通过预训练模型来生成文档和查询的嵌入向量。以下是生成嵌入向量的代码:

from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")

然后,我们使用 VectorstoreIndexCreator 创建索引:

from langchain.indexes import VectorstoreIndexCreator
from langchain.vectorstores import DocArrayInMemorySearch

index = VectorstoreIndexCreator(
    vectorstore_cls=DocArrayInMemorySearch,
    embedding=embeddings
).from_loaders([loader])

这段代码通过 VectorstoreIndexCreator 从加载的文档中生成向量存储索引 index,这将使我们能够在后续的查询中进行高效的相似性检索。

2.3 查询和检索文档

通过构建好的索引,我们可以对查询进行相似性搜索,找到与查询最相关的文档。在这个例子中,我们使用以下查询:

query = "Please suggest a shirt with sunblocking"
# 通过 index.vectorstore 访问底层的向量存储对象
docs = index.vectorstore.similarity_search(query)

该查询将从文档中检索所有与“防晒”相关的产品,返回最相关的结果。similarity_search 方法会返回一个文档列表,其中每个文档与查询的相关性按降序排列。

2.3.1 输出检索结果

我们可以查看检索结果,了解返回的文档内容:

for doc in docs:
    print(doc.page_content)
    print("-----")

返回的结果将展示与查询最相关的文档,包含产品名称、描述等信息。示例输出如下:

: 255
name: Sun Shield Shirt by
description: "Block the sun, not the fun – our high-performance sun shirt is guaranteed to protect from harmful UV rays. 
...
Sun Protection That Won't Wear Off
Our high-performance fabric provides SPF 50+ sun protection, blocking 98% of the sun's harmful rays.
-----
: 679
name: Women's Tropical Tee, Sleeveless
description: Our five-star sleeveless button-up shirt has a fit to flatter and SunSmart™ protection to block the sun’s harmful UV rays. 
...
Sun Protection That Won't Wear Off: Our high-performance fabric provides SPF 50+ sun protection, blocking 98% of the sun's harmful rays.
-----
...

2.4 创建问答链

接下来,我们将使用 LangChain 的 RetrievalQA 来结合检索到的文档和 LLM 模型生成回答。

2.4.1 初始化 LLM 模型

首先,我们需要初始化一个 LLM 模型,这里我们使用 ChatOpenAI 作为我们的语言模型,并设置温度参数为 0,确保回答的准确性和一致性:

from langchain_openai import ChatOpenAI
import os
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

api_key = os.getenv("ALIYUN_API_KEY")
base_url = os.getenv("ALIYUN_API_URL")

# 初始化 ChatOpenAI 客户端
llm = ChatOpenAI(
    openai_api_key=api_key,  # 必须明确设置 api_key
    model_name="qwen-plus",  # 或者根据支持的模型使用 qwen-plus
    base_url=base_url,  # 设置 Base URL
)

2.4.2 创建问答系统

接下来,我们将使用 RetrievalQA 来创建一个问答系统,将文档检索器和 LLM 模型结合在一起:

from langchain.chains import RetrievalQA

# 通过 index.vectorstore 访问底层的向量存储对象,并调用 as_retriever 方法
qa_stuff = RetrievalQA.from_chain_type(
    llm=llm, 
    chain_type="stuff", 
    retriever=index.vectorstore.as_retriever(),  # 修改这里
    verbose=True
)

response = qa_stuff.run(query)

2.4.3 执行查询并生成回答

现在,我们可以通过问答系统对用户的查询进行处理,生成最终的回答:

response = qa_stuff.run(query)

2.4.4 显示回答

最终,我们可以使用 Markdown 格式将回答展示给用户:

from IPython.display import display, Markdown

display(Markdown(response))

系统将生成一个 Markdown 格式的表格,展示符合条件的防晒衬衫,并对每个衬衫进行简要总结。


2.5 完整代码示例

以下是一个完整的示例,展示如何将所有步骤结合起来创建一个 Q&A 系统:

# 1. 加载文档数据
from langchain.document_loaders import CSVLoader

file = 'OutdoorClothingCatalog_1000.csv'
loader = CSVLoader(file_path=file, encoding='utf-8')

# 加载文档并查看第一条内容
docs = loader.load()
print(docs[0])

# 2. 创建向量索引
from langchain_huggingface import HuggingFaceEmbeddings

# 使用 Hugging Face 的嵌入模型
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")

from langchain.indexes import VectorstoreIndexCreator
from langchain.vectorstores import DocArrayInMemorySearch

# 创建向量存储索引
index = VectorstoreIndexCreator(
    vectorstore_cls=DocArrayInMemorySearch,
    embedding=embeddings
).from_loaders([loader])

# 3. 查询和检索文档
query = "Please suggest a shirt with sunblocking"

# 通过 index.vectorstore 访问底层的向量存储对象,进行相似性搜索
docs = index.vectorstore.similarity_search(query)

# 打印检索结果
for doc in docs:
    print(doc.page_content)
    print("-----")

# 打印第一条文档的完整内容
print(docs[0])

# 4. 初始化 LLM 模型
from langchain_openai import ChatOpenAI
import os
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()

api_key = os.getenv("ALIYUN_API_KEY")
base_url = os.getenv("ALIYUN_API_URL")

# 初始化 ChatOpenAI 客户端
llm = ChatOpenAI(
    openai_api_key=api_key,  # 必须明确设置 api_key
    model_name="qwen-plus",  # 或者根据支持的模型使用 qwen-plus
    base_url=base_url,  # 设置 Base URL
)

# 5. 创建问答系统
from langchain.chains import RetrievalQA

# 通过 index.vectorstore 访问底层的向量存储对象,并调用 as_retriever 方法
qa_stuff = RetrievalQA.from_chain_type(
    llm=llm, 
    chain_type="stuff", 
    retriever=index.vectorstore.as_retriever(),  # 修改这里
    verbose=True
)

# 运行问答系统
response = qa_stuff.run(query)

# 6. 显示问答结果
from IPython.display import display, Markdown

display(Markdown(response))

三、深入理解 LangChain:问答系统中的向量存储与检索

在本节中,我们将深入探讨 LangChain 中的 向量存储检索器 如何协同工作,提升文档检索的效率。通过理解这两个模块,您将掌握如何在实际应用中构建高效的查询和信息检索系统。

3.1 向量存储概述

向量存储是自然语言处理(NLP)和信息检索领域中的一个关键概念。它通过将文本或文档转换为高维向量(embedding)的形式,帮助模型和系统高效地进行相似性检索。在 LangChain 中,向量存储用于存储经过处理的文档向量,使得我们能够通过 相似性搜索 快速找到与查询内容最相关的文档。

3.1.1 向量嵌入(Embedding)

在 LangChain 中,嵌入是通过不同的预训练模型(如 HuggingFaceEmbeddingsOpenAIEmbeddings)生成的。嵌入将每个文档或查询转化为一个高维向量,捕捉文本的语义信息。这些向量可以在向量空间中进行比较,以找到最相关的内容。

例如,我们可以通过 HuggingFace 的 sentence-transformers/all-mpnet-base-v2 模型生成嵌入:

from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")

3.1.2 向量存储

在 LangChain 中,向量存储用于存储这些高维向量,支持高效的相似性检索。LangChain 提供了多种向量存储实现,如 DocArrayInMemorySearch,它存储嵌入向量并允许快速查询。

from langchain.vectorstores import DocArrayInMemorySearch

index = VectorstoreIndexCreator(
    vectorstore_cls=DocArrayInMemorySearch,
    embedding=embeddings
).from_loaders([loader])

此代码片段展示了如何通过 VectorstoreIndexCreator 将文档加载器(CSVLoader)与向量存储相结合,创建一个可以进行相似性搜索的向量索引。

3.1.3 相似性搜索

一旦文档存储在向量存储中,我们可以通过 相似性搜索 来查找与给定查询最相关的文档。相似性搜索基于文档的嵌入向量来进行匹配,返回与查询语义最接近的文档。

query = "Please suggest a shirt with sunblocking"
docs = index.vectorstore.similarity_search(query)

该查询会从存储的文档中找到所有与“防晒”相关的内容,并按相关性排序返回结果。

3.2 构建检索器

在 LangChain 中,检索器(Retriever) 是连接文档和模型的桥梁,它负责从文档中检索出最相关的部分,以供后续的生成模型(如 LLM)进行进一步处理。

3.2.1 创建检索器

LangChain 提供了简洁的 API 来将向量存储转化为可用于问答任务的检索器。通过 as_retriever() 方法,向量存储可以被转化为检索器对象,用于从中提取最相关的文档。

retriever = index.vectorstore.as_retriever()

3.2.2 使用检索器

检索器可以与 问答链(QA Chain) 配合使用,帮助模型获取最相关的文档部分并基于此生成准确的答案。例如,结合 RetrievalQA 模块,我们可以创建一个基于检索器和 LLM 的问答系统:

from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    openai_api_key=api_key,  # 必须明确设置 api_key
    model_name="qwen-plus",  # 或者根据支持的模型使用 qwen-plus
    base_url=base_url,  # 设置 Base URL
)

qa_stuff = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    verbose=True
)

response = qa_stuff.run(query)

在这个例子中,qa_stuff.run(query) 将首先通过检索器获取相关文档,然后传递给语言模型(LLM)生成回答。

3.2.3 检索器优化

为了提升检索效果,我们可以对检索器的行为进行优化。LangChain 提供了 top_k 参数,允许我们控制每次检索返回的文档数量。例如,如果我们设置 top_k=5,检索器将返回与查询最相关的前 5 条文档。

retriever = index.vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 5})

3.3 问答链的实现

在 LangChain 中,问答链(QA Chain) 是将多个模块组合在一起,实现端到端问答功能的工具。它通过结合 检索器生成模型(LLM),实现根据查询从文档中提取信息并生成回答。

3.3.1 使用问答链

通过 RetrievalQA,我们可以创建一个完整的问答系统,系统会首先检索相关文档,并通过 LLM 来生成答案:

qa_stuff = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    verbose=True
)
response = qa_stuff.run(query)

在这个过程中,retriever 提供了与查询最相关的文档,llm 模型生成最终的回答。

3.3.2 输出格式化

为了便于用户查看,回答可以通过 Markdown 格式进行输出,特别是在需要展示表格或列表时。LangChain 可以直接将生成的文本转化为 Markdown 格式,确保展示内容的可读性。

from IPython.display import display, Markdown

display(Markdown(response))

这样,我们就能够以清晰、结构化的方式呈现答案,增强用户体验。

3.4 常见问题与解决方案

3.4.1 文档检索不准确怎么办?

  1. 优化嵌入模型:尝试使用不同的嵌入模型(如 OpenAI 的 text-embedding-ada-002,HuggingFace 的 all-mpnet-base-v2)进行对比,选择最适合的模型。
  2. 调整 k:在检索时,适当调整 top_k 参数,增加或减少返回的文档数量,帮助提高检索精度。
  3. 细化查询:如果查询过于模糊,可以通过增加更多的上下文或关键词来精细化查询,以获得更准确的检索结果。

3.4.2 如何处理大型文档集?

对于大型文档集,建议采用分布式向量存储方案,如使用数据库(如 Pinecone、Weaviate)进行向量存储。这可以有效减少内存负担,并提高检索速度。


四、总结

  1. LangChain 核心功能与模块化设计

    • LangChain 提供了丰富的模块和灵活的配置,帮助开发者高效与大语言模型(如 GPT)进行交互,核心模块包括 Model(模型接口)、Prompt(动态提示词)、Chains(任务链条)、Memory(记忆管理)、Output Parsers(输出解析器)等。
    • 模块化的设计让开发者能够根据不同应用需求,组合和扩展各模块功能,构建从简单文本生成到复杂自动化工作流的多种应用场景。
  2. 基于文档的问答系统构建

    • 本文详细介绍了如何构建基于文档的问答系统,涵盖文档加载、向量索引创建、相似性搜索、以及结合 RetrievalQALLM 模型 实现智能问答功能的全过程。
    • 使用 CSVLoaderHuggingFaceEmbeddings 实现文档数据加载与嵌入生成,进而通过向量存储和检索机制高效实现查询匹配。
  3. 实战案例与优化建议

    • 通过完整的代码示例,展示了如何将文档检索与语言模型结合,自动化回答生成。同时,针对检索效率和问答质量,提供了优化方向,如调整检索的 top_k 参数、选择合适的嵌入模型等。
    • 讨论了如何处理大型文档集和优化文档检索,通过使用分布式存储或更高效的检索算法来提升系统的可扩展性和性能。
  4. LangChain 的广泛应用前景

    • LangChain 提供的强大功能,使得它在多种实际场景中表现出色,尤其是在智能问答系统、信息提取、数据分析、自动化工作流等领域具有巨大的应用潜力。
;