基于LangChain框架搭建AI问答系统(附源码)
AI问答系统
- 1. 背景知识
- 2. 问答系统流程
- 3. 知识问答系统相关组件
- 3.1 文档加载器
- 3.2 文档切割器
- 3.3 嵌入模型包装器
- 3.4 向量存储库
- 3.5 模型包装器
- 3.6 链组件
- 4. 问答系统演示
- 4.1 问答程序
- 4.2 演示大模型回答效果
- 5.问答系统代码
1. 背景知识
在人工智能技术飞速发展的今天,知识库问答系统已成为企业、教育、医疗等领域实现高效信息检索与智能交互的核心工具。传统问答系统往往依赖人工编写的规则或固定模板,难以应对复杂多变的业务场景和海量数据。而基于大语言模型(LLM)的智能问答系统,通过自然语言处理(NLP)技术,能够自动理解用户问题并从知识库中精准检索答案,显著提升了信息处理的效率与用户体验。
LangChain 作为当前最流行的框架之一,为开发者提供了构建端到端AI应用的强大工具链。它通过模块化设计,将大语言模型(如GPT、Llama等)、外部数据源(文档、数据库、API等)和智能代理(Agent)无缝集成,支持快速实现知识库问答、对话生成、自动化任务等复杂功能。相较于从零开发,LangChain大幅降低了技术门槛,使开发者能够专注于业务逻辑而非底层实现。
问答系统:是一种基于人工智能和自然语言处理技术的智能工具,它允许用户通过自然语言提问,并从文档中提取相关信息来生成准确的回答。
无论是希望快速搭建原型的技术爱好者,还是需要为企业定制智能客服的开发者,本项目均能提供从理论到实践的完整指导。接下来,我们将从环境配置开始,逐步实现一个功能完备的AI知识库问答系统。
2. 问答系统流程
本项目旨在基于 Python 和 LangChain框架,构建一个可扩展的知识库问答系统,其核心目标包括:
- 支持多格式外部数据加载:从PDF、Word、CSV、excel等非结构化数据中提取知识,构建统一的知识库。
- 结合大语言模型的语义理解能力:通过向量检索(Embedding)和上下文增强技术,实现精准答案生成。
通过本项目,读者将掌握:
- LangChain框架的核心组件(Document Loaders、Embeddings、Vector Stores、Chains)的使用方法;
- 如何将本地或在线文档转化为可查询的知识库(向量库);
- 如何结合LLM实现语义搜索与答案生成;
本项目模式
支持:支持加载多种类型数据(pdf、word、excel、md、json、yaml、csv、html等)
支持:多种大模型(deepseek、千帆、智普等)
流程图
基于上述流程图涉及到的功能点如下
功能点 | 说明 |
---|---|
文档加载器 | 知识库多种格式数据加载 |
文档切割 | 将大块文本切割为多个小文本,便于后续处理 |
模型嵌入包装器 | 将文本转换为向量 |
向量库 | 存储模型嵌入包装器文本转换的向量,同时支持文本相关度检索 |
大模型 | Deepseek、qianfan等大模型框架 |
chain链 | 支持将向量库命中的文档传递给大模型的文档链 |
3. 知识问答系统相关组件
3.1 文档加载器
在 LangChain 和 大语言模型(LLM)应用 中,文档加载器(Loader 的作用是 从 PDF、CSV、txt、excel等 文件中提取文本内容,并将其转换为 LLM 可处理的格式(如字符串、列表或结构化数据),以便后续进行 文本分割(Text Splitting)、嵌入(Embedding)、问答(QA)或总结(Summarization) 等任务。
工具名称 | 使用场景 |
---|---|
PdfReader | PyPDF2中操作pdf工具 |
TextLoader | 加载txt文本文件 |
CSVLoader | 加载CSV格式的文件 |
UnstructuredExcelLoader | 加载excel格式文件 |
JSONLoader | 加载json格式文件 |
UnstructuredMarkdownLoader | 加载MD格式文件 |
我们的文档系统支持加载多种数据格式,比如pdf、txt、csv、excel, 这里加载文本后获取的数据有两种格式
- 一种是返回原始的文本列表 list[Document]
- 一种是统一转换为文本格式(个人发现:使用非结构化的文本数据,向量库的检索的准确率更高,模型回答效果更好)
下面列举上述文本加载的python代码(代码很多但是超级简单)
- 加载并转换为文档
import osfrom PyPDF2 import PdfReader
from langchain_community.document_loaders import TextLoader, CSVLoader, UnstructuredExcelLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document# 文本转换为 list[Document]:对于pdf、txt非结构化数据处理
def textSplitForDocs(content: str) -> list[Document]:"""将文本内容进行切割,返回切割后的文本列表使用 langchain_text_splitters 库进行切割Args:content (str): 待切割的文本内容Returns:list[str]: 切割后的文本列表"""# 初始化递归字符分割器 (每个文档库100字、每个文档块可以允许重复10个字)# 重叠字符数:为了分割文字同时不破化其语义完整性splitter = RecursiveCharacterTextSplitter(chunk_size=100, # 每个文档块的最大字符数chunk_overlap=10 # 相邻文档块之间的重叠字符数)# 将文本分割成多个 Document 对象docs = splitter.create_documents([content])return docs #PdfReader 加载器 加载pdf文件
def loadPyPdf2Loader(pdf_path: str) -> list[Document]:"""从本地路径加载 PyPDF2 并返回 Document 列表使用 PyPDF2 库进行加载Args:pdf_path (str): PDF 的本地路径(如 "path/to/your/pdf.pdf")Returns:list[Document]: 包含文本内容和元数据的 Document 列表"""#入参 pdf文档路径 加载完成padReader = PdfReader(pdf_path)raw_text = ''# 遍历 PDF 的每一页for page in padReader.pages:#提取文本内存(目前不考虑图片相关资源)text = page.extract_text()#拼接成长文本if text:raw_text += text#长文本分割并转换为Document列表 return textSplitForDocs(raw_text)#TextLoader 加载器 加载txt文件
def loadTextLoader(text_path: str) -> list[Document]:"""从本地路径加载txt文件:param text_path: 文件路径:return: 文本信息"""#校验文件路径if not os.path.exists(text_path):raise FileNotFoundError(f"输入文件不存在: {text_path}")#文件路径、编码方式(中文需要指定,否则会乱码) 创建加载器 load = TextLoader(text_path, encoding="utf-8")# 加载文本文件 直接会返回document列表 文件内容按自然段落或换行符分割成文档列表# 不完全符合我们要求,故此处还是拼接文本,在调用方式按照我们要求分隔文本docs = load.load()raw_text = ''for doc in docs:text = doc.page_contentif text:raw_text += text#长文本分割并转换为Document列表 return textSplitForDocs(raw_text)#CSVLoader 加载器 加载csv文件(结构化数据直接load 即可获取Document列表)
def loadCSVLoader(cvs_path: str) -> list[Document]:"""加载CSV格式文件:param cvs_path: csv文件路径:return: 获取到的文本信息"""csvLoader = CSVLoader(cvs_path, encoding="utf-8")docs = csvLoader.load()return docs#UnstructuredExcelLoader 加载器 加载excel文件(结构化数据直接load 即可获取Document列表)
def loadExclLoader(excl_path: str) -> list[Document]:"""加载excel文件:param excl_path: 文件路径:return: excel中文本信息"""loader = UnstructuredExcelLoader(excl_path, encoding="utf-8")docs = loader.load()return docs##### 其他格式文档加载待补充
- 加载并转换为纯文本格式
#逻辑与上述加载文本转换为文档列表大体一致,主要添加了将数据提取为长文本并分割
import osfrom PyPDF2 import PdfReader
from langchain_community.document_loaders import TextLoader, CSVLoader, UnstructuredExcelLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Documentdef loadPyPdf2Loader(pdf_path: str) -> list[str]:"""从本地路径加载 PyPDF2 并返回 字符串结婚 列表使用 PyPDF2 库进行加载Args:pdf_path (str): PDF 的本地路径(如 "path/to/your/pdf.pdf")Returns:list[str]: 包含文本内容列表"""padReader = PdfReader(pdf_path)raw_text = ''for page in padReader.pages:text = page.extract_text()if text:raw_text += textreturn textSplitter(raw_text)def loadTextLoader(text_path: str) -> list[str]:"""从本地路径加载txt文件:param text_path: 文件路径:return: 文本信息"""#校验文件路径if not os.path.exists(text_path):raise FileNotFoundError(f"输入文件不存在: {text_path}")load = TextLoader(text_path, encoding="utf-8")docs = load.load()raw_text = ''for doc in docs:text = doc.page_contentif text:raw_text += textreturn textSplitter(raw_text)def loadCSVLoader(cvs_path: str) -> list[str]:"""加载CSV格式文件:param cvs_path: csv文件路径:return: 获取到的文本信息"""csvLoader = CSVLoader(cvs_path, encoding="utf-8")docs = csvLoader.load()raw_text = ''for doc in docs:text = doc.page_contentif text:raw_text += textreturn textSplitter(raw_text)def loadExclLoader(excl_path: str) -> list[str]:"""加载excel文件:param excl_path: 文件路径:return: excel中文本信息"""try:loader = UnstructuredExcelLoader(excl_path, encoding="utf-8")docs = loader.load() # 在这里卡住说明是加载器内部问题except Exception as e:print(f"加载失败: {str(e)}")raiseraw_text = ''for doc in docs:text = doc.page_contentif text:raw_text += textreturn textSplitter(raw_text)
3.2 文档切割器
在大语言模型(LLM)应用 中,文档切割器(Text Splitter) 的核心作用是 将长文本拆分成更小的、符合模型输入限制的片段(chunks),同时尽可能保留语义完整性。它是连接 原始文档 和 LLM 处理单元 的关键工具,尤其在处理长文档(如 PDF、书籍、报告)时必不可少。
类名 | 功能描述 | 核心参数 | 适用场景 |
---|---|---|---|
CharacterTextSplitter | 按固定字符数或分隔符(如换行符、空格)切割文本。 | - chunk_size : 最大块大小(字符数) - chunk_overlap : 块间重叠字符数 - separator : 分隔符(默认 \n\n ) | 简单文本切割,无需语义结构(如日志、纯文本)。 |
RecursiveCharacterTextSplitter | 递归尝试多种分隔符(如段落、句子、空格),优先保留语义边界。 | - chunk_size : 最大块大小 - chunk_overlap : 块间重叠 - separators : 分隔符优先级列表(默认 ["\n\n", "\n", " ", ""] ) | 通用文本切割(如文章、书籍、网页内容)。 |
MarkdownHeaderTextSplitter | 按 Markdown 标题层级(如 # 、## )切割文本,保留章节结构。 | - headers_to_split_on : 标题元组列表(如 [("#", "Header 1"), ("##", "Header 2")] ) | Markdown 文档(如技术文档、笔记)。 |
HTMLSectionSplitter | 按 HTML 标签(如 <h1> 、<p> )切割文本,保留 HTML 结构。 | - tag_names : 标签名列表(如 ["h1", "p"] ) - attributes_to_keep : 保留的属性(如 ["class"] ) | HTML 文档(如网页、报告)。 |
TokenTextSplitter | 按 Token 数量(而非字符数)切割文本,适合需要精确控制 Token 的场景(如 GPT 模型)。 | - chunk_size : 最大 Token 数 - model_name : 用于分词的模型名(如 "gpt2" ) | 需要与 LLM Token 限制对齐的场景(如 OpenAI API)。 |
LanguageSplitter | 按 自然语言边界(如句子、段落)切割,支持多语言(需指定语言模型)。 | - chunk_size : 最大块大小 - language : 语言代码(如 "en" 、"zh" ) - model_name : 分词模型名 | 多语言文本切割(如中文、英文混合文档)。 |
如果上述返回的是纯文本,则需要长文本转换为小的满负荷模型输入限制的片段,文档分割器就派上用场了。
from langchain_text_splitters import RecursiveCharacterTextSplitterdef textSplitter(content: str) -> list[str]:"""将文本内容进行切割,返回切割后的文本列表使用 langchain_text_splitters 库进行切割Args:content (str): 待切割的文本内容Returns:list[str]: 切割后的文本列表"""splitter = RecursiveCharacterTextSplitter(chunk_size=100, # 每个文档块的最大字符数chunk_overlap=10 # 相邻文档块之间的重叠字符数)# chunk_size:每个文档块的最大字符数 chunk_overlap:相邻文档块之间的重叠字符数splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=10)# 文档分割texts = splitter.split_text(content)return texts
3.3 嵌入模型包装器
嵌入模型包装器是自然语言处理(NLP)框架中的核心组件,其核心作用是将 文本数据转换为浮点数值向量(Embedding),从而可以分析文本的相似度。后续将其存储在向量库中,便于后续的搜索使用等。
类名 | 功能描述 | 核心参数 |
---|---|---|
OpenAIEmbeddings | 调用 OpenAI 或 Azure OpenAI 的嵌入模型(如 text-embedding-ada-002 ),将文本转换为高维向量。 | - model : 模型名称(如 text-embedding-ada-002 ) - api_key : OpenAI API 密钥 - inputs : 输入文本(字符串或字符串列表) |
QianfanEmbeddingsEndpoint | 百度千帆平台的嵌入模型包装器,支持多种嵌入模型,提供稳定的 API 接口。 | - endpoint : 千帆平台 API 地址 - qianfan_ak : 千帆平台 Access Key - qianfan_sk : 千帆平台 Secret Key - model : 模型名称(如 qianfan-embedding-model ) |
ZhipuAIEmbeddings | 智谱 AI 的嵌入模型包装器,用于生成文本的嵌入向量,支持参数配置和错误处理。 | - model : 模型名称(如 zhipuai-embedding-model ) - batch_size : 批量处理大小 - max_retries : API 调用最大重试次数 |
BaichuanTextEmbeddings | 百川智能开发的中文文本嵌入模型,专注中文语义理解,生成高维度向量。 | - model : 模型名称(如 baichuan-text-embedding ) - max_tokens : 最大处理 token 数(如 512) - dimension : 输出向量维度(如 1024) |
如下是我整合了千帆、智普、百川三个平台的模型嵌入包装器(为啥没有openAI 因为需要🪜)其他国产大模型的嵌入模型包装器我没有找到对应的demo ,参考文档:langchain嵌入模型包装器
import os
from pathlib import Pathfrom dotenv import load_dotenv
from langchain_community.embeddings import BaichuanTextEmbeddings, QianfanEmbeddingsEndpoint, ZhipuAIEmbeddings
from langchain_core.embeddings import Embeddings
from langchain_openai import OpenAIEmbeddings#支持百川大模型、千帆大模型、智普AI模型
def getEmbeeding(modelName: str) -> Embeddings:"""获取嵌入模型包装器Args:modelName (str): 嵌入模型名称Returns:Embeddings: 嵌入模型对象"""#加载配置信息 (安全) #大模型相关key存储在resource/.env BASE_DIR = Path(__file__).parent.parentload_dotenv(dotenv_path=BASE_DIR / "resource" / ".env")if modelName == "baichuan":# 百川大模型#1.获取对应的apikeybai_chuan_api_key = os.getenv("BAI_CHUAN_API_KEY")#2.创建百川嵌入模型包装器return BaichuanTextEmbeddings(baichuan_api_key=bai_chuan_api_key)elif modelName == "qianfan":# 千帆大模型(文心一言)#1.获取千帆ak、skqianfan_ak = os.getenv("QIANFAN_AK")qianfan_sk = os.getenv("QIANFAN_SK")#2.创建千帆的嵌入模型包装器return QianfanEmbeddingsEndpoint(qianfan_ak=qianfan_ak,qianfan_sk=qianfan_sk)elif modelName == "zhipu":# 智普AI模型#1. 获取智普apikeyzhipu_api_key = os.getenv("zhipu-api-key")#2. 创建智普的嵌入模型包装器return ZhipuAIEmbeddings(model="embedding-3",api_key=zhipu_api_key)elif modelName == "openai":# openai大模型 TODO 没有open_ai 的keyreturn OpenAIEmbeddings()else:return None
资源添加resource/.env
#百川大模型 apiKey(注册申请)
BAI_CHUAN_API_KEY=sk-XXXXX#智普AI的api-key
zhipu-api-key=fxxx#千帆大模型
QIANFAN_AK=xxx
QIANFAN_SK=xxxx#openai
OPEN_AI_KEY=xxxxx
3.4 向量存储库
向量存储库(Vector Store)是专门用于存储、索引和检索高维向量数据的数据库系统,支持存储向量。从而支持相似性搜索、语义理解能力、多模态支持等。
- 高效存储高维向量:支持海量高维向量的压缩存储,例如文本、图像、音频等非结构化数据经嵌入模型转换后的向量。
- 相似性搜索:通过近似最近邻(ANN)算法(如HNSW、IVF-PQ、LSH)快速查找与查询向量最相似的向量集合,而非传统数据库的精确匹配。
- 语义理解能力:基于向量间的距离度量(如余弦相似度、欧氏距离),实现基于语义的搜索,而非关键词匹配。
- 多模态支持:可处理文本、图像、音频等多种数据类型的向量表示,支持跨模态检索。
- 可扩展性:设计用于处理大规模数据,支持分布式部署和水平扩展。
类名 | 功能描述 | 核心参数 |
---|---|---|
Milvus | 开源的分布式向量数据库,支持多种索引类型(如HNSW、IVF_PQ),适用于大规模向量相似性搜索。 | - dim :向量维度 - index_type :索引类型(如IVF_FLAT 、HNSW ) - metric_type :距离度量方式(如L2 、IP ) |
FAISS | Facebook AI Research开发的开源库,针对相似性搜索优化,支持GPU加速。 | - dim :向量维度 - M (HNSW参数):每个节点的最大连接数 - efConstruction (HNSW参数):构建索引时的搜索范围 |
Pinecone | 全托管的向量数据库服务,提供高性能的向量搜索,支持实时更新和水平扩展。 | - dimension :向量维度 - metric :距离度量方式(如cosine 、euclidean ) - pod_type :计算资源类型(如s1.x1 ) |
Weaviate | 开源的向量数据库,结合语义搜索与图形数据库特性,支持自动schema推断和丰富的GraphQL API。 | - vectorizer :嵌入模型配置(如text2vec-contextionary ) - distance :距离度量方式(如cosine ) |
Chroma | 开源的、AI本地的嵌入式向量数据库,支持查询、过滤、密度估计等多种功能。 | - 支持多种嵌入模型(如BERT 、CLIP ) - 提供Python API,易于集成到AI应用中 |
Qdrant | 开源的向量相似性搜索引擎和数据库,提供生产就绪的服务和易于使用的API。 | - dim :向量维度 - distance :距离度量方式(如cosine 、euclidean ) - hnsw_m :HNSW参数,每个节点的最大连接数 |
Elasticsearch + pgvector | 传统数据库添加的向量搜索功能,支持SQL查询和向量检索的混合搜索。 | - dim :向量维度 - distance :距离度量方式(如l2 、inner_product ) |
Annoy | 轻量级开源库,适合大型数据集的近似最近邻搜索,特点是构建索引速度快且占用空间小。 | - f :向量维度 - metric :距离度量方式(如angular 、euclidean ) - n_trees :树的数量 |
HNSWlib | 实现HNSW算法的库,支持高效的近似最近邻搜索。 | - dim :向量维度 - space :距离度量方式(如cosine 、l2 ) - M :每个节点的最大连接数 |
这里我使用的是FAISS向量库,存储嵌入模型包装器(该向量库对于数字类型查询不太理想,准确度较低,对于文字相关搜索精确度挺高,后续看演示吧)
保存向量库数据
def saveFAISSVector(indexPath: str) -> None:"""加载外部数据并将其存储到向量库中:param indexPath: 向量库路径"""# 加载pdf文件pdf_texts = loadPyPdf2Loader("doc_data/member.pdf")# 加载文本文件txt_texts = loadTextLoader("doc_data/crm_info.txt")# 加载CSV格式文件csv_texts = loadCSVLoader("doc_data/cust_info.csv")# 加载Excel格式文件excel_texts = loadExclLoader("doc_data/dms.xlsx")# 3. 合并 PDF 和 TXT 的文档列表texts = pdf_texts + txt_texts + csv_texts + excel_texts# 3.使用嵌入模型包装器,将文档转换嵌入向量embeddings = getEmbeeding(embeeding_model)# vectors = embeddings.embed_documents(texts)# 4. 创建向量数据库并与嵌入模型连接,# 将文本转换为向量,并保存到向量数据库中docSearch = FAISS.from_texts(texts, embeddings)# 持久化向量库docSearch.save_local(indexPath)print("向量库保存成功!")
加载向量库 用于查询使用
def loadFAISSVector(indexPath: str) -> FAISS:"""加载 FAISS 向量库:param index: 向量库路径:return: FAISS 对象"""return FAISS.load_local(indexPath, getEmbeeding(embeeding_model),allow_dangerous_deserialization=True)
文档检索
我们从向量库中加载相关向量后,需要从向量库中进行检索,并将检索出来的数据(document 列表)作为背景信息传递给大模型
# 5.查询数据从向量库# query是自然语言查询条件docs = docSearch.similarity_search(query, k=6)print("命中的文档数:", len(docs))
3.5 模型包装器
模型包装器(Model Wrappers)是 LangChain 的核心组件之一,其作用是为大语言模型提供一层抽象接口,封装模型的调用、输入输出处理以及配置管理等功能。
类名 | 作用 | 功能用法 |
---|---|---|
ChatOpenAI | LangChain 中用于与 OpenAI 聊天模型交互的核心类,提供多种方法来调用和管理对话 | - 核心方法: - invoke() :同步调用模型,获取完整响应。 - stream() :流式响应,适合逐步显示结果的场景。 - generate() :批量生成,同时处理多个用户输入,返回包含元数据的完整响应对象。 - bind_tools() :绑定工具调用,将函数调用能力绑定到当前模型。 - with_structured_output() :结构化输出,强制模型返回结构化数据。 - with_fallbacks() :失败回退,设置模型调用失败时的备用模型。 - 配置相关方法: - 模型参数设置,如 model (模型名称)、temperature (随机性控制)、max_tokens (最大输出长度)等。 - 异步方法: - 所有核心方法都有对应的异步版本,如 ainvoke() 、astream() 等。 - 使用示例: - 同步调用:response = chat.invoke("你好!") - 流式处理:for chunk in chat.stream("请写一首关于春天的诗"): print(chunk.content, end="", flush=True) - 批量生成:result = chat.generate([[HumanMessage(content="2+2等于多少?")]]) |
QianfanChatEndpoint | 百度千帆平台提供的聊天模型端点,支持多轮对话和历史消息记录 | - 多轮对话:维护对话上下文,支持连续交互。 - 历史消息记录:记录对话历史,便于追溯和复用。 - 应用场景:聊天机器人、客服系统、智能问答等需要对话交互的场景。 - 使用方式:在千帆平台中配置模型参数,通过API调用实现对话功能。 |
ChatDeepSeek(假设类名,基于DeepSeek模型) | 封装DeepSeek大语言模型的调用接口,提供文本生成、语义理解、代码生成等功能 | - 文本生成:支持文章、故事、诗歌创作,营销文案生成等。 - 自然语言理解:语义解析、情感分析、意图识别、实体提取。 - 编程辅助:代码生成、补全、调试建议,API文档生成。 - 数据可视化:生成流程图、折线图、柱状图等图表。 - 使用方式:通过官方网站或API调用,输入提示语(Prompt)获取输出。 |
#目前支持 deepseek、qianfan、zhipu
def getLLM(modelName: str) -> BaseChatModel:"""获取大模型对象Args:modelName (str): 模型名称Returns:BaseChatModel: 模型对象"""#加载配置信息 (安全)BASE_DIR = Path(__file__).parent.parent.parentload_dotenv(dotenv_path=BASE_DIR / "resource" / ".env")if modelName == "deepseek":# deepseek大模型api_key = os.getenv("DEEPSEEK_API_KEY")return ChatDeepSeek(model="deepseek-chat",temperature=0,max_tokens=None,timeout=None,max_retries=2,openai_api_key=api_key,)elif modelName == "qianfan":# 千帆大模型app_id = os.getenv("QIANFAN_APPID")access_token = os.getenv("QIANFAN_TOKEN")return QianfanChatEndpoint(model="ERNIE-Tiny-8K",temperature=0.2,timeout=30,app_id=app_id,access_token=access_token)elif modelName == "zhipu":# 智普AI模型api_key = os.getenv("zhipu-api-key")# 初始化一个 ChatOpenAI 对象(即一个 LLM 模型接口)return ChatOpenAI(temperature=0.95, # 控制生成文本的随机性(0=确定性强,1=随机性强)model="glm-4-air-250414", # 指定模型名称(这里是智谱 GLM-4 的某个版本)openai_api_key=api_key, # 你的 API 密钥(需提前定义变量 api_key)openai_api_base="https://open.bigmodel.cn/api/paas/v4/" # 智谱 AI 提供的 API 基础地址)elif modelName == "openai":# openai大模型 TODO 没有open_ai 的keyreturn ChatOpenAI()else:return None
3.6 链组件
在 LangChain 中,链(Chain) 是核心抽象概念之一,它代表一个将多个组件(如大语言模型LLM、提示词模板prompt、工具调用toool、其他链等)按特定逻辑组合起来的可执行流程。链的设计使得复杂任务可以分解为可复用的模块,并通过组合这些模块实现灵活的功能扩展。下面列举几个常用链
- 基础链(Basic Chains)
简单任务的基础组件。
链类型 | 核心作用 | 典型适用场景 |
---|---|---|
LLMChain | 直接调用 LLM 完成单步文本生成或问答。 | 单轮问答、提示词测试、简单文本生成。 |
SequentialChain | 按顺序执行多个链(如 Chain1 → Chain2 ),支持线性流程。 | 多步骤任务(如数据清洗 → 分析 → 报告生成)。 |
SimpleSequentialChain | 简化版的顺序链,固定输入输出结构。 | 标准化线性流程(如固定格式的文档处理)。 |
- 2. RAG 链(Retrieval-Augmented Generation Chains)
通过检索实时更新的外部知识库(如新闻、数据库、API),将最新信息作为上下文输入大模型,生成准确回答。
链类型 | 核心作用 | 典型适用场景 |
---|---|---|
Stuff Documents Chain | 合并输入:将多个文档拼接成一个长文本,直接传给 LLM 处理。 | 短文档问答(文档总长度 < LLM max_tokens )、简单信息提取。 |
Map-Reduce Documents Chain | 分治处理: 1. Map:对每个文档单独处理(如生成摘要、提取关键信息); 2. Reduce:合并所有处理结果后传给 LLM 生成最终输出。 | 长文档总结、多文档信息聚合(如新闻汇总、产品评论分析)。 |
Refine Documents Chain | 迭代优化: 1. 生成初步回答; 2. 基于所有文档逐步优化回答(多次交互)。 | 复杂问题优化(如法律条文分析、科研文献综述)、需要多轮修正的高精度任务。 |
Map-Rerank Chain | 生成-排序: 1. Map:批量生成多个候选结果(如答案、摘要、选项); 2. Rerank:按评分标准(如相关性、准确性)对候选结果排序,选择最优解。 | 问答系统(多答案选择)、搜索结果优化、文本摘要质量提升、推荐系统候选筛选。 |
- 3. 代理链(Agent Chains)
通过工具扩展 LLM 能力,实现自动化。
链类型 | 核心作用 | 典型适用场景 |
---|---|---|
Zero-Shot Agent | 通过自然语言描述工具,自主选择工具完成任务。 | 简单自动化任务(如“查询天气”“计算数学题”)。 |
ReAct Agent | 结合推理(Reasoning)和行动(Action),支持多步决策。 | 复杂逻辑任务(如“规划旅行路线”“诊断系统故障”)。 |
Conversational Agent | 支持对话历史记忆,实现多轮上下文交互。 | 聊天机器人、客服系统、个人助手。 |
- **4. 高级推理链(Advanced Reasoning Chains) **
针对特定场景优化(如知识图谱、辩论)。
链类型 | 核心作用 | 典型适用场景 |
---|---|---|
GraphQA Chain | 基于知识图谱进行关系推理和问答。 | 结构化知识查询(如“XX 和 XX 的关系是什么?”)。 |
Debate Chain | 通过多 LLM 角色辩论优化回答质量。 | 高质量内容生成(如论文写作、创意策划)。 |
- 多模态链(Multimodal Chains)
支持非文本数据(图像、音频)与 LLM 交互。
链类型 | 核心作用 | 典型适用场景 |
---|---|---|
Vision-Language Chain | 处理图像和文本混合输入(如 OCR + LLM 描述图像)。 | 图像分析、图表解读、视觉问答。 |
Audio-Language Chain | 处理音频和文本混合输入(如语音转文字 + LLM 总结)。 | 语音助手、会议纪要生成、音频内容分析。 |
我们的问答系统,需要将向量库使用RAG代理增强,对文本进行代理增强。实现了stuff链(文档问答链)代码如下
stuff链(文档问答链)
def qa_stuff(docs: list[Document], query: str):"""创建一个基于 Stuff 策略的问答链,将多个文档合并后传给 LLM 回答用户问题。Args:docs (list[Document]): 文档列表,每个文档包含文本内容和元数据。query (str): 用户提出的查询问题。Returns:str: LLM 生成的回答结果。"""# 1. 定义系统提示模板(System Prompt)# - 指导模型如何利用提供的文档回答问题# - 强调“不知道就说明不知道”,避免编造答案system_template = "使用以下背景信息来回答用户的问题。如果你不知道答案,只需说明不知道,不要编造答案。{context}"# 2. 构建完整的提示模板(Prompt Template)# - 包含系统消息(System Message)和用户消息(Human Message)messages = [# 系统消息:设置模型行为规则SystemMessagePromptTemplate.from_template(system_template),# 用户消息:包含实际查询问题HumanMessagePromptTemplate.from_template("{question}"),]# 3. 创建聊天提示模板(ChatPromptTemplate)# - 将消息列表转换为 LangChain 可识别的提示格式chat_prompt = ChatPromptTemplate.from_messages(messages)# 4. 创建 Stuff 文档链# - 使用自定义的聊天提示模板# - 指定 LLM 模型(这里调用 getLLM 函数获取 "deepseek" 模型)chain = create_stuff_documents_chain(llm=getLLM("deepseek"), # 获取 LLM 模型实例prompt=chat_prompt # 使用上面定义的聊天提示模板)# 5. 执行链式调用# - 输入参数:# - context: 要合并的文档列表(会自动拼接)# - question: 用户查询问题result = chain.invoke({"context": docs, # 文档上下文(会自动转换为字符串)"question": query # 用户问题})# 6. 返回结果return result
至此,langchain的所有核心组件讲完,同时我们问答系统的所有流程也编写完成!下面让我们进入演示环节。
4. 问答系统演示
4.1 问答程序
调用我们上面章节的相关代码,我们便编写完了一个基于知识库的AI智能问答系统。流程如下
def qa_system(query: str) -> None:"""基于向量检索和LLM的问答系统主流程:1. 将数据保存到FAISS向量库(若不存在则创建)2. 从本地加载向量库3. 执行相似度检索获取相关文档4. 使用Stuff链将文档和问题传给LLM生成答案Args:query (str): 用户输入的自然语言查询问题Returns:None (结果直接打印,如需返回可修改为return result)"""# 1. 保存/更新数据到FAISS向量库# - 如果向量库已存在则直接使用,不存在则创建新索引# - faiss_index_name 是全局常量,指定索引存储路径saveFAISSVector(faiss_index_name)# 2. 从本地加载FAISS向量库# - 返回的docSearch是已初始化的向量检索器# - 内部包含嵌入模型(embedding model)和FAISS索引docSearch = loadFAISSVector(faiss_index_name)# 3. 执行相似度检索# - 参数说明:# - query: 用户查询文本# - k=6: 返回最相似的6个文档(可根据需求调整)# - 返回的docs是Document对象列表,包含文本内容和元数据docs = docSearch.similarity_search(query, k=6)print("命中的文档数:", len(docs)) # 调试信息:实际检索到的文档数量# 4. 调用Stuff链处理文档并生成答案# - qa_stuff函数实现:# 1) 将所有文档内容合并为单个字符串# 2) 添加系统提示词指导LLM回答# 3) 调用LLM生成最终答案result = qa_stuff(docs, query)# 5. 输出最终结果print("最终答案:", result)#启动程序
if __name__ == "__main__":#用户输入提问query = input("你想要问些什么?")#问答qa_system(query)
4.2 演示大模型回答效果
知识库和向量库
图中是我的建立的知识库以及向量库。
pdf_qa_system.py 是将所有数据转换为文档存入向量 (针对数字类型的问答不准确)
pdf_qa_system_text.py 是将所有数据转换为长文本分割成多个文档存入向量 (各个维度查询准确度都很高)
命中知识库的回答:
首先在【3.1 文档加载器】加载了不同类型的文档知识,例如加载了:csv格式的客户数据:
客户id | 客户姓名 | 客户类型 | 营业执照 | 地址 | 锁定状态 |
---|---|---|---|---|---|
1000002007 | 塔德伊·波加查 | 经销商 | 91588888MAAK96L889 | 北京市石景山区八角游乐园 | 锁定 |
可以针对上述数据任何维度进行问答。问答效果如下:
不命中的效果
5.问答系统代码
链接: 代码原文件地址
项目目录结构
lang_chain_qa/
└── doc_data/ 知识库资源 (目前是空自行补充)
├── faiss_index/ 向量库持久化文件(文档类型)
├── faiss_text_index/ 向量库持久化文件(文本类型)
├── crm_info.txt 资源
├── cust_info.csv 资源
├── dms.xlsx 资源
├── mathPix.md 资源
├── member.pdf 资源
├── pdf_qa_system.py 文档类型数据知识问答
├── pdf_qa_system_text.py 文本类型数据知识问答
└── rerank_prompt.py
至此,我们一步一步的手写了一份知识库问答系统,如有问题欢迎指正。原创不易,对你有帮助还请一键三连。