☰
LangChain实战入门:从提示词模板到文档问答的完整链路
2026/10/16 8:21:54 网站建设 项目流程

说实话,在接触LangChain之前,我有一段时间处于“天天接大模型API,但代码越写越乱”的状态。每个模型一套调用方式,提示词得自己拼字符串,聊个天要手动把历史记录攒成列表,想做个文档问答还得自己实现切片和向量检索——这些重复劳动完全可以抽象出来。于是当我第一次用LangChain把整条流程串起来的时候,最大的感受是:终于能把精力放在业务逻辑本身了。

这篇属于系列教程的第02章,定位很明确:不聊源码,不讲底层原理,而是用最快的速度带你跑通LangChain的基础链路。你要会一点Python,理解HTTP和JSON的基本概念就够了。学完之后,你能独立搭建一个带提示词模板、连上大模型、支持多轮对话记忆的小应用,同时具备把一份PDF文档变成“可对话知识库”的能力。这些正是后续学习Agent、工作流编排、复杂应用开发的根基。

1. 为什么LangChain能成为大型语言模型应用开发的首选框架

1.1 没有LangChain之前,接大模型到底有多麻烦

我们先把时间拨回还没接触LangChain的时候。假设你要做一个智能客服机器人,第一步就是调用某个大模型接口。单这一步就藏着很多坑:不同厂商的接口风格不一样,有的传messages数组,有的传prompt字符串,有的要求system角色单独传,有的必须带user前缀。代码里往往要写一堆ifelse来适配不同厂商,换一个模型就要改一层封装。

更麻烦的是多轮对话。大模型本身是没有记忆的,你这次问它“我姓王”,下次再问“我叫什么”,它完全不记得。你必须把之前的对话历史全部拼到请求参数里,每次还得琢磨历史太长会不会超过token上限,要不要截断,截断哪些内容。这些逻辑看起来简单,但实际做起来充满了边界情况。

再来是数据接入。想让AI回答私有文档里的内容,你得先把PDF或者Word拆成片段,做向量化,存到向量数据库,然后再根据用户问题检索相关片段,组装进提示词。这一整套流程,每个环节都得自己找库、自己调参数、自己处理异常。写一次还好,一旦要复用或者换数据源,基本就是重写一遍。

这个痛点不是某个人遇到,而是“大模型应用开发早期”的普遍困境。LangChain正是针对这些重复劳动给出了一套统一的抽象。

1.2 LangChain的核心价值:抽象、复用、生态

LangChain本质上做了一件事:把大模型应用开发中的“常变部分”和“不变部分”分离。它提供了几个非常关键的抽象层。

第一层是模型接口的统一封装。通过一个ChatOpenAI类,你可以用几乎一致的代码去调用不同的模型服务。切换模型的成本从“重写调用层”降到“改一个类名加一个环境变量”。这个价值在项目维护期特别明显,尤其是你想对比几个模型的效果时,不用写多份测试代码了。

第二层是提示词模板化。PromptTemplate可以把提示词从“散落在业务代码里的字符串拼接”变成“可维护的模板对象”。模板里留出变量位置,运行时填充用户问句或者数据片段。这让提示词的调试、复用、版本管理都变得更规整。

第三层是链(Chain)。这是LangChain最核心的思维:把“提示词生成—模型调用—输出解析”这个固定流程包装成一个可组合的单元。你可以把单个链拼成更复杂的链,也可以在整个流程中插入记忆或者工具调用。这就像把一堆散落的零件装进标准化模块,再通过统一的接口拼接成流水线。

第四层是丰富的生态。文档加载器、向量库集成、Agent工具、各类Memory策略,LangChain都有对应的组件。意味着你不需要从零去写一个PDF解析器或者接一个向量数据库的客户端,大多数时候只需选择合适的组件就能完成工作。

1.3 什么人适合读这篇,需要什么前置基础

这篇第02章面向的是“对大模型API有基本概念,但还没系统化开发过应用”的读者。你不一定写过完整的提示词工程,也不一定了解向量数据库的原理,但应该知道“发给模型的内容是什么格式”“返回的内容又是什么格式”。如果连API调用都没接触过,强烈建议先花半小时写一个最基础的大模型请求脚本,哪怕只是让它输出一句“你好”,再来上手LangChain会轻松很多。

至于数学基础和机器学习知识,这里暂时不需要。LangChain的价值恰恰在于把很多工程细节藏到了抽象之下,你可以先用起来,再慢慢理解内部机制。

2. 环境准备与5分钟快速上手

2.1 环境搭建与依赖安装

我建议直接使用Python 3.10或3.11版本,这两个版本对LangChain全家桶的兼容性最稳。老项目的3.8、3.9也能跑,但有些新版本组件已经放弃了对旧版的支持,没必要折腾。

安装方式推荐用虚拟环境隔离,避免污染全局环境。命令行里依次执行:

mkdir langchain-demo cd langchain-demo python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install langchain langchain-community langchain-openai

这里的三个包分工要说明一下:langchain是核心框架,包含链、提示词、记忆等基础抽象;langchain-community是社区维护的各种集成,导入了大量文档加载器和向量库适配器;langchain-openai是官方针对OpenAI兼容接口的模型封装。新版LangChain把“模型实现”和“核心框架”拆开了,这是很多人一开始容易搞混的地方。

另外建议顺手装一个python-dotenv,用来管理环境变量密钥:

pip install python-dotenv

LangChain的API Key配置方式通常是读取名为OPENAI_API_KEY的环境变量。你可以在项目根目录创建一个.env文件,把密钥放进去:

OPENAI_API_KEY=你的密钥

然后在代码中加载:

from dotenv import load_dotenv load_dotenv()

这样可以避免把密钥硬编码在脚本里,也方便在不上传.env的情况下用Git管理代码。

2.2 模型接入与统一接口机制

LangChain对模型的封装逻辑是这样的:不管底层是哪个厂商的模型,只要它提供OpenAI兼容的HTTP接口,你就都可以通过ChatOpenAI这个类来接入。这样做的好处非常直接——业务代码完全不用关心底层模型是GPT系列、通义系列还是其他兼容服务,只需要在初始化时指定模型名称和接口地址。

代码里最简单的接入方式如下:

from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="gpt-3.5-turbo", temperature=0.7, timeout=30, max_retries=2 )

这里model是模型名,temperature控制输出随机性,timeout和max_retries分别限制了请求超时时间和重试次数。

如果你的模型服务地址不是默认地址,而是在其它端点,可以通过base_url参数指定:

llm = ChatOpenAI( model="你的模型名", base_url="http://你的服务地址/v1", api_key="你的密钥" )

这个base_url参数非常实用。国内使用各种兼容代理服务时,几乎都要用到。另外要注意,如果你的服务商要求使用非OpenAI的鉴权方式,需要查一下是否在langchain_community里有对应集成,或者自己写一个回调函数。现在很多国内模型都提供了OpenAI兼容端点,所以ChatOpenAI基本能覆盖大多数场景。

2.3 第一个LangChain应用:欢迎语生成器

环境搭好后,我们写一个最小可用程序,感受一下LangChain的完整调用链。这个例子的目标是让模型帮我们生成一段给新用户的欢迎语。

from langchain_openai import ChatOpenAI from langchain.prompts import PromptTemplate from langchain.chains import LLMChain # 1. 初始化模型 llm = ChatOpenAI( model="gpt-3.5-turbo", temperature=0.7 ) # 2. 定义提示词模板 prompt = PromptTemplate( input_variables=["app_name", "user_name"], template=""" 你是一家互联网产品的运营助手。 请为新用户 {user_name} 生成一段关于产品 {app_name} 的欢迎语。 要求:语气亲切,长度不超过50字。 """ ) # 3. 将提示词和模型组装成链 chain = LLMChain(llm=llm, prompt=prompt) # 4. 执行 result = chain.run(app_name="极简待办", user_name="小王") print(result)

运行这段代码,得到的可能是一段类似“小王你好呀,欢迎来到极简待办,愿你用最轻的方式完成每一件事”的输出。这个例子虽然简单,但已经把LangChain最基本的“模板—模型—链”三层结构跑通了。你可以在后续代码中反复修改template字符串来测试不同效果,也可以把input_variables换成你的业务字段,比如订单号、用户积分、商品名称等。

这一段跑通之后,恭喜你,LangChain的基本链路你已经握在手里了。

3. 从零搭起核心四件套:模板、链、记忆、输出

3.1 PromptTemplate:告别手抖拼字符串

很多没接触过LangChain的人会问:提示词不就是字符串拼接吗?用f-string不也行?当然行,但在实际项目里,prompt往往不只是简单替换一两个变量,而是需要根据业务场景动态组织内容。比如同时传入用户资料、历史订单、当前问题,还需要根据条件插入不同指令,这时候用f-string就会变成一大坨难以阅读的字符串逻辑。

LangChain的PromptTemplate把提示词结构化和变量注入分开,代码会更清晰。核心用法如下:

from langchain.prompts import PromptTemplate template = """ 你是一位资深前端工程师。 请针对下面的技术问题给出解决方案,要求使用Vue3和TypeScript。 问题主题:{topic} 技术难点:{difficulty} 用户的代码片段:{code_snippet} """ prompt = PromptTemplate( input_variables=["topic", "difficulty", "code_snippet"], template=template ) filled_prompt = prompt.format( topic="实现虚拟滚动列表", difficulty="需要处理动态高度", code_snippet="<div v-for='item in list'>..." ) print(filled_prompt)

输出就是完整填充的提示词。你可以随时把这个prompt打印出来查看它真实发送给模型的内容,调试体验比直接拼接字符串好得多。

新版LangChain推荐使用的是ChatPromptTemplate,这是专门为Chat模型设计的模板,支持按“system、user、assistant”的角色分别定义内容。建议后面空闲时了解它,但第02章先把PromptTemplate用熟就够应付接下来的实战了。

3.2 Chain:把组件串成流水线

讲Chain之前,先想一个问题:如果每次调用模型都要先format提示词、再传参、再做异常处理,那业务代码里就全是样板代码了。Chain要解决的就是“把固定步骤变成封装的单元”。

最简单的链是LLMChain,它接收一个提示词模板和一个模型对象,调用run方法直接输出结果。上面欢迎语生成器就是例子。

链更大的价值在于组合。比如我想让AI先生成一个营销文案的主题,再基于这个主题扩写一段完整的广告文案,用两个链接力完成:

from langchain.prompts import PromptTemplate from langchain.chains import LLMChain from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) first_prompt = PromptTemplate( input_variables=["product"], template="请为产品 {product} 想一个吸引人的营销主题,只输出主题本身。" ) first_chain = LLMChain(llm=llm, prompt=first_prompt) second_prompt = PromptTemplate( input_variables=["product", "theme"], template="围绕主题 {theme},为产品 {product} 写一段50字的营销文案。" ) second_chain = LLMChain(llm=llm, prompt=second_prompt) theme = first_chain.run(product="智能保温杯") copy = second_chain.run(product="智能保温杯", theme=theme.strip()) print("主题:", theme) print("文案:", copy)

这种串联方式只是Chain组合的入门形态,但你已经能看到“工作流”的影子了。在实际生产项目里,还可以实现自定义Chain类,把更复杂的逻辑比如条件判断、结果校验、外部API调用都编排进去。

3.3 Memory:让对话有记忆

大模型本身是无状态的,但对话类应用天然需要记住上下文。最原始的做法是自己维护一个消息列表,每次请求带上全部历史消息。这个小项目里用到的简洁封装是ConversationBufferMemory——它把每次的提问和回答自动存下来,并在下一次请求时拼接进去。

具体代码是:

from langchain_openai import ChatOpenAI from langchain.chains import ConversationChain from langchain.memory import ConversationBufferMemory llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) memory = ConversationBufferMemory() conversation = ConversationChain( llm=llm, memory=memory, verbose=True ) print(conversation.run("我叫小明,今年25岁。")) print(conversation.run("你还记得我叫什么吗?"))

运行后会看到verbose模式打印出来的内部构造。第二次调用时,LangChain自动把第一轮问答拼进了prompt,所以模型能准确回答出“你刚才说你叫小明”。

这里有一个需要留意的地方:ConversationBufferMemory会把所有历史都存下来,一旦对话轮次变多,token消耗会快速上涨。生产环境一般会用ConversationSummaryMemory或者ConversationWindowMemory这类裁剪策略。第02章先不用深入,知道有这回事,后续章节我们专门展开。

3.4 为什么第02章先不要急着学Agent

Agent是LangChain里最招人眼球的模块,看网上演示视频,AI自动调用计算器、搜索网页、操作数据库,确实酷。但我的建议是,入门阶段先把它放一放。

原因很现实:Agent本质上是“链的进阶版”,它的核心机制是让模型自己决定调哪个工具、按什么顺序调、如何根据观察结果制定下一步。这里面隐含着对提示词质量、工具设计、容错机制的较高要求。如果连基础Chain的组装还没理清楚就直接上Agent,一旦出现问题,连问题出在哪个环节都定位不了。

就像学开车,先熟练踩离合换挡,再上城市道路。第02章把基础打牢,后续学Agent会觉得所有概念都能对号入座,而不是一脸懵。

4. 知识库问答实战:让AI基于你的文档回答问题

4.1 思路:为什么需要给大模型“外挂”知识库

先共识一个大前提:大模型的知识在训练完成那一刻就固定了,它不知道你公司内部的规章制度,不知道你刚写的最新方案,也不知道某个小众领域的最新动态。要让AI回答这类“私有知识”问题,有两种主流方案:微调(Fine-tuning)和检索增强生成(RAG,Retrieval-Augmented Generation)。

微调要准备训练数据、上传到平台、跑训练任务,成本高且更新知识不灵活。RAG的思路则完全不同:把知识文档提前切分成块,生成向量索引,用户提问时先检索出与问题最相关的几个片段,然后把这些片段和问题一起交给大模型,让它基于提供的资料作答。

就好比考试允许开卷,AI考试时先翻书找到相关段落,再照着书上的内容答题。这样知识库可以随时更新,替换文档只需要重新走一遍切分和索引流程,不用动模型本身。这就是RAG近两年火遍大模型应用圈的根本原因。

4.2 实操步骤:文档加载、切分、向量化、组装检索问答链

下面我以一个纯本地运行的案例演示整个过程,用的是PDF文档作为示例(换成txt或md文件也同理)。先装依赖:

pip install pypdf faiss-cpu langchain-openai

完整代码如下:

from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS from langchain.chains import RetrievalQA from langchain_openai import ChatOpenAI # 1. 加载文档 loader = PyPDFLoader("产品手册.pdf") documents = loader.load() print(f"加载了 {len(documents)} 页文档") # 2. 文档切分 text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) texts = text_splitter.split_documents(documents) print(f"切分成 {len(texts)} 个文本块") # 3. 向量化并存储 embeddings = OpenAIEmbeddings(model="text-embedding-ada-002") vectorstore = FAISS.from_documents(texts, embeddings) # 4. 构建检索问答链 qa_chain = RetrievalQA.from_chain_type( llm=ChatOpenAI(model="gpt-3.5-turbo", temperature=0.2), retriever=vectorstore.as_retriever(search_kwargs={"k": 4}), chain_type="stuff", return_source_documents=True ) # 5. 提问 query = "这款产品最长续航是多少?" result = qa_chain.invoke({"query": query}) print("回答:", result["result"]) print("参考来源:") for doc in result["source_documents"]: print(doc.page_content[:100])

把这个流程拆开解释一下。

文档加载这一步,PyPDFLoader会按页返回Document对象,每个Document包含page_content文本和metadata元数据(比如页码、来源文件名)。这里的metadata可用于后续追踪答案来源,非常实用。

切分是整个RAG流程中最影响回答质量的一步。chunk_size设为500,表示每个文本块大约500个字符;chunk_overlap设为50,表示相邻块之间保留50个字符的重叠。这么做的好处是,当某句话恰好在切分边界时,它不会完全丢失上下文,检索时更容易命中完整语义。separators这个参数也很关键,它指定了优先按段落、换行、句号等自然边界切分,而不是硬生生按字符数砍断。

向量化使用的是OpenAIEmbeddings,它会把每个文本块转换成一个高维向量,语义相近的文本向量距离也近。FAISS是一个本地的向量检索库,不需要额外部署服务,非常适合学习阶段使用。

最后通过RetrievalQA组装一条检索问答链。search_kwargs中的k表示返回4个最相关的文本块,chain_type="stuff"表示把所有检索结果一次性塞进提示词。当知识量很大时,stuff方式可能导致提示词过长,后续章节我们会聊map_reduce和refine等替代方案。return_source_documents=True是为了把检索到的原文一并返回,方便验证AI的回答是否真的有依据。

4.3 参数调优心得:切分大小和检索深度如何取舍

把流程跑通只是第一步,想让回答质量稳定,还得理解几个关键参数的取舍。

chunk_size直接决定了每次给模型的上下文颗粒度。块太大,检索出来的内容可能夹杂大量与问题无关的段落,浪费token还稀释注意力;块太小,单独的块可能缺少上下文,比如一句话的主语在上一块里,模型拿到片段根本看不明白。从我的经验看,500到800字符对于大多数中文文档是一个比较合适的区间,但这也不是绝对的,技术文档可以小一点,叙事性材料可以大一点。

chunk_overlap的作用是“补缝”。想象一篇文章是一匹布,切分就像剪刀裁剪,重叠区就像布边特意留出的缝份,确保边缘信息不丢。50到100字符的重叠对绝大多数情况都够用。

检索k值则控制每次取几个块。k太小,可能漏掉关键信息;k太大,无关噪音明显增多。默认的4对于短文档够用,文档内容差异大时,可以适当调大到5到6,但不要盲目贪多。

最后提醒一句:本地向量库FAISS在内存足够时非常顺畅,但如果你的文档量很大,或者需要跨多个项目共用索引,建议尽早切换到Chroma或者更专业的向量数据库。学习阶段先用FAISS把RAG链路打通,比一上来折腾运维配置要更值得。

5. 新手高频踩坑与排查清单

5.1 导入错误:from langchain.llms import OpenAI 为什么报错

很多老教程里还会出现这样一行:

from langchain.llms import OpenAI

在新版LangChain中,这行代码已经不行了。主要原因在于官方在架构上做了调整,LLM实现部分被拆分到独立的子包中。正确的写法是:

from langchain_openai import ChatOpenAI

如果你在社区里看到有人用from langchain.chains import LLMChain没问题,但from langchain.llms报错,那基本就是新旧API混用导致的。

排查思路很简单:遇到导入报错时,优先去官方文档查对应类的正确导入路径,不要凭旧记忆硬写。用pip show langchain langchain-openai查看版本,确认是否装全了需要的子包。

5.2 中文乱码与文件编码问题

Windows环境下的终端经常会出现中文乱码,尤其当打印包含中文的提示词模板或者回答时,控制台输出一堆乱码。这通常是代码文件编码和终端编码不一致导致的。

做法很直接:项目中的所有Python文件都用UTF-8保存,并在文件开头加上声明:

# -*- coding: utf-8 -*-

如果要把结果写入文件,最好也显式指定编码,比如:

with open("output.txt", "w", encoding="utf-8") as f: f.write(result)

5.3 faiss-cpu 安装失败怎么办

faiss是一个比较特殊的库,在某些环境上通过pip安装并不顺利,尤其是缺少预编译轮子的平台。一种常见报错是:ERROR: No matching distribution found for faiss-cpu。

解决办法是用conda安装:

conda install -c conda-forge faiss-cpu

如果连conda也没有,也可以换成Chroma向量库,本地运行也够用。替换方案只需要改两行代码:

from langchain_community.vectorstores import Chroma vectorstore = Chroma.from_documents( documents=texts, embedding=embeddings, persist_directory="./chroma_db" )

Chroma默认支持持久化,学习期的体验比FAISS更友好,还自带可视化界面,适合观察索引情况。

5.4 大模型接口超时与限流

调用大模型接口时最常见的错误是TimeoutError和RateLimitError。前者是请求超时,后者是请求频率超过限制。弱网环境下,大模型一次生成几百字内容可能需要几十秒,默认的请求超时时间很容易不够。

推荐在初始化ChatOpenAI时设置timeout和max_retries这两个关键参数:

ChatOpenAI( model="gpt-3.5-turbo", timeout=60, max_retries=3 )

timeout设成60秒基本能覆盖绝大多数场景,max_retries会在临时网络波动时自动重试。如果做批量并发请求,建议在代码层加一个简单的限流器,比如每请求间隔200毫秒,或者用线程池限制并发数。很多限流报错不是模型的问题,而是你自己请求频率太高。

这一章的实战到这里就该告一段落了。从环境搭建到跑通LLMChain,从记忆实现到知识库问答,每一步都是后面所有LangChain进阶内容的基石。我个人在实际操作中体会最深的一点是:不要试图一开始就理解所有组件,而是先跑通一个闭环,再逐步拆解内部机制。很多概念只有在你亲手踩过坑之后,回头看文档才会真正理解它为什么那样设计。建议你把文中的三个示例都自己敲一遍,改改模板、换换文档、调调参数,比单纯阅读要有用得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询