引言
随着大语言模型(LLM)的普及,开发者不再满足于简单的对话补全,而是希望将 LLM 与外部数据、工具以及复杂业务逻辑相结合,构建真正落地的 AI 应用。LangChain 正是为此而生的开源框架,它提供了一系列标准化的组件和链式调用能力,帮助开发者高效地组合模型、数据检索、记忆、代理等模块。
本文将从实际需求出发,带你深入理解 LangChain 的核心概念,并通过一个完整的、可运行的代码示例,从零开始构建一个基于私有 PDF 文档的智能问答系统。读完本文,你将能够:
- 掌握 LangChain 中模型、提示、链、检索、记忆等核心抽象
- 学会使用 LangChain 加载并处理私有文档
- 实现一个带记忆的多轮对话问答机器人
- 了解常见问题与最佳实践
无论你是刚接触 LangChain 的新手,还是希望系统梳理概念的开发者,这篇实战指南都能为你提供清晰的路线图。
核心概念:LangChain 的六脉神剑
在动手写代码前,我们需要先理解 LangChain 的六大基础组件,它们是构建一切应用的基石。
1. 模型(Models)
LangChain 封装了两种主要模型:LLM(纯文本补全)和聊天模型(Chat Models,如gpt-3.5-turbo)。它们都通过统一的接口调用,方便替换。例如:
from langchain.chat_models import ChatOpenAI llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)2. 提示(Prompts)
提示管理是 LLM 应用的关键。LangChain 提供了PromptTemplate、ChatPromptTemplate等工具,支持变量插值、Few-shot 示例以及消息角色(system、human、ai)的组合。
from langchain.prompts import ChatPromptTemplate template = ChatPromptTemplate.from_messages([ ("system", "你是一个友好的AI助手"), ("human", "{user_input}") ])3. 链(Chains)
链是 LangChain 的核心思想,它将多个组件串联起来形成一个处理管道。比如最简单的LLMChain就是把提示模板和模型组合起来。更复杂的还有RetrievalQA、ConversationalRetrievalChain等,这些封装好的链大大降低了开发门槛。
4. 检索(Retrieval)
为了让 LLM 能够回答非公开知识,我们需要将文档切片、向量化并存入向量数据库。用户提问时,先检索相关文档片段,再与问题一起交给模型回答。这一流程常被称为RAG(Retrieval-Augmented Generation)。LangChain 提供了各种文档加载器、文本分割器、嵌入模型接口和向量数据库集成。
5. 记忆(Memory)
默认情况下,LLM 是无状态的。LangChain 的Memory组件可以保存对话历史,实现多轮对话能力。常见的记忆类型有ConversationBufferMemory(全量缓存)、ConversationSummaryMemory(摘要记忆)等。
6. 代理(Agents)
代理将 LLM 作为“大脑”,能够自主决定使用哪些工具、按什么顺序执行,最终完成复杂任务。本文以 RAG 为主,代理部分可视为进阶内容。
理清这些组件后,我们开始实战。
实战示例:私有 PDF 智能问答系统
我们以一个典型场景为例:公司内部有许多产品使用手册(PDF 格式),希望搭建一个智能问答机器人,员工可以用自然语言提问,机器人能基于手册内容给出精准回答,并支持多轮对话。
环境准备
确保已安装 Python 3.9+,并安装以下依赖:
pip install langchain openai chromadb pypdf tiktoken streamlit我们需要一个 OpenAI 的 API Key,请将其设置为环境变量OPENAI_API_KEY。
步骤一:加载并处理文档
首先,将 PDF 加载为文本,然后进行分块。分块的原因有两个:向量模型通常有 token 上限;小块文本检索时更精准。
# 1. 文档加载 from langchain.document_loaders import PyPDFLoader loader = PyPDFLoader("path/to/your/product_manual.pdf") documents = loader.load() # 每页作为一个 Document 对象 print(f"文档页数: {len(documents)}") # 2. 文本分割 from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, # 每块最大字符数 chunk_overlap=50, # 块之间重叠字符数,避免在语义边界切断 separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""] ) chunks = text_splitter.split_documents(documents) print(f"分割后的文本块数量: {len(chunks)}")注意:separators列表定义了分割优先级,先从两个换行(段落)开始,如果没有则用单个换行,再逐级下降。这能保留更好的语义结构。
步骤二:创建向量数据库
将文本块转化为向量,并存储到 Chroma(一个轻量级本地向量数据库)中。
# 3. 嵌入与向量存储 from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma embeddings = OpenAIEmbeddings() # 指定持久化目录,避免每次运行都重新计算嵌入 persist_directory = "./chroma_db" vectordb = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory=persist_directory ) vectordb.persist() # 持久化到磁盘 print("向量数据库创建完成")后续再次使用时,可以直接加载已有数据库:
vectordb = Chroma( persist_directory=persist_directory, embedding_function=embeddings )步骤三:构建对话型检索链
我们使用ConversationalRetrievalChain一步到位:它内部整合了问题改写(基于对话历史生成独立问题)、文档检索、上下文整合和答案生成。
from langchain.chat_models import ChatOpenAI from langchain.chains import ConversationalRetrievalChain from langchain.memory import ConversationBufferMemory # 初始化语言模型 llm = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0.1, # 降低随机性,保证回答精确 max_tokens=512 ) # 创建记忆组件,用于存储对话历史 memory = ConversationBufferMemory( memory_key="chat_history", # 链内部会使用这个key从记忆读取历史 return_messages=True, # 以消息对象列表形式返回 output_key="answer" # 指定输出存储的键 ) # 构建对话检索链 qa_chain = ConversationalRetrievalChain.from_llm( llm=llm, retriever=vectordb.as_retriever(search_kwargs={"k": 4}), # 每次检索返回4个相关块 memory=memory, verbose=False # 设为True可查看内部运行细节 ) print("对话检索链创建成功,开始对话吧!")步骤四:测试对话
# 模拟用户提问 print("第一轮:") response = qa_chain({"question": "如何重置设备的网络设置?"}) print(f"AI: {response['answer']}\n") print("第二轮(跟随上下文):") response = qa_chain({"question": "对刚才提到的步骤有没有更详细说明?"}) print(f"AI: {response['answer']}\n") print("第三轮:") response = qa_chain({"question": "如果重置后仍无法连接怎么办?"}) print(f"AI: {response['answer']}\n")运行以上代码,你将看到一个能够记住“刚才提到的步骤”的问答系统。记忆组件会自动将对话历史加入提示,使得模型可以正确理解上下文。
完整可运行代码
下面将所有步骤整合为一个完整的 Python 脚本qa_bot.py,你只需修改 PDF 路径即可运行:
import os from langchain.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma from langchain.chat_models import ChatOpenAI from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationalRetrievalChain # 1. 加载文档 pdf_path = "./user_manual.pdf" # 请替换为你的PDF路径 loader = PyPDFLoader(pdf_path) docs = loader.load() # 2. 分割文本 splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""] ) chunks = splitter.split_documents(docs) # 3. 向量化并存储 embeddings = OpenAIEmbeddings() vectordb = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_manual_db" ) vectordb.persist() # 4. 初始化 LLM 和记忆 llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.1) memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True, output_key="answer" ) # 5. 构建对话检索链 qa = ConversationalRetrievalChain.from_llm( llm=llm, retriever=vectordb.as_retriever(search_kwargs={"k": 4}), memory=memory ) # 6. 命令行交互循环 print("智能问答机器人已启动,输入'退出'结束对话。") while True: user_input = input("\n用户: ") if user_input.lower() in ["退出", "exit", "quit"]: print("再见!") break try: result = qa({"question": user_input}) print(f"AI: {result['answer']}") except Exception as e: print(f"发生错误: {e}")运行后即可在命令行进行多轮对话。
常见问题 & 注意事项
1. Token 超限怎么办?
当对话历史越来越长,加上检索到的文档块,提示可能会超出模型的上下文长度。解决方案:
- 限制检索返回数量(
k值调小) - 使用
ConversationSummaryMemory替代全量缓存,自动生成摘要 - 启用 token 计数,对历史进行截断
- 选择更大上下文窗口的模型(如 gpt-3.5-turbo-16k)
2. 回答质量不理想
常见原因及优化方向:
- 文档切割不合理:块太大检索不准,块太小丢失上下文。建议
chunk_size=500~1000,overlap=50~100为初始值,根据实际文档粒度调整。 - 检索结果相关性低:可以在检索时增加相似度阈值过滤,或使用带有元数据的筛选条件。
- 提示模板可定制:通过
from_llm的condense_question_prompt和combine_docs_chain_kwargs传入自定义提示,引导模型更好地利用上下文。
3. 持久化与成本
- 嵌入计算会消耗 API 费用,如果文档不变,应持久化向量数据库,避免重复计算。
- Chroma 默认使用内存模式,指定
persist_directory后才会存盘。 - 在本地频繁测试时,可以先用较小的文档集验证流程。
4. 向量数据库选择
Chroma 适合原型和小规模应用。生产环境可考虑:
- FAISS(本地,速度快)
- Pinecone、Weaviate、Qdrant等云原生向量数据库
LangChain 对它们都有良好支持。
5. 安全性
- 切勿将 API Key 硬编码在代码中,使用环境变量或密钥管理服务。
- 对于面向最终用户的产品,注意控制数据访问权限,防止检索到未授权文档。
总结
本文从零开始,详细拆解了 LangChain 构建 AI 应用的全过程。我们从核心概念入手,理解了模型、提示、链、检索和记忆的职责;随后通过一个完整的 PDF 问答项目,展示了从文档加载、分割、向量化到构建带记忆检索链的每个环节。所有代码均可直接运行,希望能为你开启 LangChain 实战之旅提供坚实基础。
LangChain 的威力远不止于此,它还支持代理、工具调用、回调系统等高级特性。但万变不离其宗,理解这六脉神剑的组合方式,你就能轻松驾驭更复杂的场景。有什么问题欢迎在评论区交流,一起探索大语言模型应用的无限可能。