☰
LangChain速成课:用Python搭建带记忆的LLM应用实战
2026/10/26 20:34:27 网站建设 项目流程

如果你最近在翻 GitHub 或者逛技术社区,大概率已经被 LangChain 这个词刷屏了。它是 Python 生态里搭建 LLM 应用最常见的框架之一。我用 LangChain 折腾过不少项目,从最傻的问答机器人到带工具的 Agent 都玩过,踩过不少坑,所以这个“速成课”系列我打算用实战视角来写:今天第一课,先用 Python 把基于 OpenAI LLM 的最小可用应用跑起来,同时把 LangChain 的核心组件拆开讲明白。如果你刚接触 LLM 应用开发,或者正打算把大模型能力接进自己的系统,这篇内容能帮你少走弯路。

先说个总览性的判断:LangChain 不负责让模型变聪明,它解决的是工程化问题。直接调 OpenAI API 十几行代码就能出结果,但一旦你开始想“怎么让多轮对话记住上下文”“怎么统一管理 Prompt”“怎么把工具调用流程稳定地跑起来”,裸调代码就变得又臭又难维护。LangChain 做的就是把这类高频需求抽象成标准组件,让你别重复造轮子。这篇文章我会从“为什么需要 LangChain”讲起,然后搭环境、写第一个调用、用 Prompt 模板和链(Chain)重构成工程化结构,最后加上多轮记忆。整个过程会给出可以直接跑的 Python 代码,并且把每个关键环节背后“为什么这么设计”也讲清楚。

1. 为什么第一课先讲 LangChain,而不是直接调 OpenAI

1.1 LLM 应用开发的真正难点

很多人第一次写完openai.ChatCompletion.create()之后很兴奋,觉得 LLM 应用不过如此。但你真把一个对话机器人接进业务里试试,马上会遇到几个问题:第一,API 的调用参数各家不一样,换个模型就要改一批代码;第二,业务逻辑和 Prompt 文案搅拌在一起,产品想改一句人话你就得翻代码;第三,多轮对话的上下文要自己拿数组拼,拼到第几十轮时 token 爆了还不知道怎么回事;第四,想让模型调用外部工具,你得自己处理模型输出的特殊格式,然后写各种 if/else 分派。这些问题才是 LLM 应用开发的常态。

我把这些归纳成一句话:LLM 应用开发的难点不在“调用模型”,而在“把模型稳定地嵌进业务系统”。LangChain 走的路线就是我给你一套通用抽象,你负责切换模型、拼 Prompt、管历史、调工具,业务代码只要描述“我要什么”,不用管“底层怎么折腾”。这一点弄清楚,你学 LangChain 的时候就不会被各种新概念带偏方向。

1.2 LangChain 的组件地图

LangChain 的组件设计其实很直白:

  • Model:封装不同大模型的调用,OpenAI、Anthropic、本地模型等,对外提供统一的接口。
  • Prompt Template:把 Prompt 字符串变成可复用、可填参数的模板。
  • Chain(链):把多个步骤串起来,最典型的是“模板取数据 → 模型生成 → 输出整理”。
  • Memory(记忆):负责多轮对话中的上下文存取。
  • Agent(代理)与 Tool(工具):让模型能调用外部函数,这是进阶话题,后面单独说。

这些组件并不是硬性捆绑,你可以只用 Model 或者只用 Prompt Template,完全自由组合。有个实操建议:第一课先别碰 Agent,很多人一上来就被 Agent 花哨的能力吸引,结果被格式解析、循环调用、死循环这些问题折腾到怀疑人生。先把 Model、Prompt、Chain、Memory 这四个基本功练熟,Agent 后面学起来快得多。

1.3 这套速成课我打算怎么安排

既然标题写了“速成课(一)”,这条系列线我会按这个顺序推进:这一课解决“跑通最小可用应用”,也就是从零到能跟模型流畅对话;第二课接着讲输出解析和结构化输出,解决“让模型输出 JSON”这个高频需求;第三课再讲 Agent 和工具调用。每一步都把代码和原理绑在一起讲,而不是贴一段让你自己悟。

这一节课结束的时候,你应该能独立写出一个带记忆的多轮对话脚本,并且理解 Chain 为什么是 LangChain 最核心的抽象。后面所有复杂功能,本质上都是在这个基础能力上叠加。

2. 环境准备与最小工程骨架

2.1 Python 环境与工作目录

LangChain 对 Python 版本要求比较宽松,官方推荐 Python 3.9 以上,我自己用的是 3.11,跑得很稳。如果你还没装 Python,去官网下载当前稳定版就行,安装时记得勾选“Add Python to PATH”,这一步经常有人漏掉,导致命令行里敲python没反应。

建议新建一个独立的虚拟环境,别把依赖直接装进全局环境,否则过两个月依赖冲突能把人逼疯。我是这样创建的:

mkdir langchain-quickstart && cd langchain-quickstart python -m venv .venv source .venv/bin/activate # Windows 下是 .venv\Scripts\activate

虚拟环境激活后,终端提示符前面会出现(.venv),后面所有安装和运行都在这个环境里,互不污染。这一步花不了两分钟,换来的清爽能持续很久。如果你之前已经装过 LangChain 但版本比较乱,最省事的办法就是新建一个干净虚拟环境重新装。

2.2 安装 LangChain 核心依赖

我推荐的最小安装命令如下:

pip install langchain langchain-openai python-dotenv

逐个说下为什么是这三个包:

  • langchain:框架主包,提供 Chain、Memory、Agent 这些核心抽象。
  • langchain-openai:OpenAI 模型的标准集成包。早期 LangChain 把模型集成全放在主包里,后来拆成了独立子包,目的在于让用户少装用不到的依赖,也让各家的集成可以各自独立发版。
  • python-dotenv:用来读取.env文件,把你的 API Key 从代码里隔离出来,别写死在 Python 文件里。

安装完成后可以快速验证一下版本:

python -c "import langchain; print(langchain.__version__)" python -c "from langchain_openai import ChatOpenAI; print('ok')"

如果第二行报ModuleNotFoundError,大概率是 pip 装到了别的 Python 环境里。检查一下你当前虚拟环境是否激活,用which python(Windows 用where python)看路径是不是在你的项目目录下。这种环境问题占了新手报错的很大比例,不要急着怀疑代码。

2.3 API Key 配置与第一个防护习惯

这一步最容易出问题,也最容易埋下安全祸根。别在代码里写死 API Key,更别把带 Key 的文件直接推到 Git 仓库。我见过太多人把 Key 提交到公开仓库,然后被扫描工具拉走,账单蹭蹭涨。

正确做法是在项目根目录创建一个.env文件:

OPENAI_API_KEY=sk-你的密钥

然后新建main.py,只要两行代码就能让 Key 生效:

import os from dotenv import load_dotenv load_dotenv() api_key = os.getenv("OPENAI_API_KEY")

注意.env不能提交到 Git,所以在.gitignore里加上.env这一行。这个习惯建议一上来就养成,后面不管做什么项目都受用。有两点提醒:一是.env文件里的值不要加引号,sk-xxx直接写;二是不要使用从第三方渠道弄来的“共享 Key”,那种账号不受你控制,说跑路就跑路,还会白白消耗你的额度。自己申请自己的 Key,安全又省心。

3. 第一个 LLM 应用:从裸调用到 Chain

3.1 模型加载与最朴素的调用

先把最小可运行代码写出来。在main.py里:

from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="gpt-4o-mini", temperature=0.7, ) resp = llm.invoke("用一句话解释什么是 LangChain") print(resp.content)

然后运行:

python main.py

正常情况下你会看到终端打印出一句模型生成的回答。这里ChatOpenAI是 LangChain 对 OpenAI 聊天类模型的统一封装,invoke是 LangChain 统一后的调用方法,输入是一个字符串,输出是一个AIMessage对象,它既有content(文本正文),也有metadata(token 数、模型名等信息)。模型名可以根据你的实际情况换成别的,gpt-4o-mini是我现在测试时常用的,速度和成本平衡得不错。

我把temperature设成了 0.7,让回答带一点随机性。如果是做分类、抽取这类需要稳定输出的任务,把temperature调到 0 甚至 0.1,能显著减少“信口开河”的问题。这个参数在工程上是真的值得花心思的,不是随便填的。

3.2 裸调 AI 的三个痛点

跑通之后,别急着高兴。我们来看这个最简单的代码,实际上藏了三个问题:

第一,入参和出参都是裸的。你输入的是一串毫无结构的字符串,如果后面要加一个“角色设定”,只能在字符串里拼接,改起来极易出错。

第二,业务逻辑跟模型耦合太紧。你的代码里直接出现了ChatOpenAI实例,一旦要换模型,所有用到它的地方都要改。

第三,没有应对复杂对话结构的能力。真实场景往往不是一句话进一句话出,而是“系统提示词 + 历史上下文 + 用户提问 + 可能的工具返回结果”,这些都得有一条清晰的组装路径。裸写当然也可以,但每个项目都重写一遍同样的组装逻辑,就非常浪费了。

所以 LangChain 给出的答案是:用Prompt Template管理输入结构,用Chain管理调用流程。

3.3 用 ChatPromptTemplate 把 Prompt 工程化

LangChain 里最常用的模板类是ChatPromptTemplate,它天然区分消息角色,不只是拼字符串。看这段代码:

from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "你是一位资深技术讲解员。回答风格要求:逻辑清晰、表达通俗、不超过三句话。"), ("human", "{question}"), ]) chain = prompt | llm result = chain.invoke({"question": "什么是向量数据库?"}) print(result.content)

这里的核心变化在于:模板把“系统提示词”和“用户问题”分开管理,{question}是一个占位符,调用时通过字典传入真实值。以后产品想改人设,只动模板这一行,业务代码一行都不用碰。

注意到prompt | llm这个写法了吗?这是 LangChain 新版本主推的LCEL(LangChain Expression Language),用管道符把组件串起来,prompt的输出自动变成llm的输入。后面的chain.invoke接受的参数就是模板里所有占位符的集合。这个表达方式非常直观:数据流是“提示词模板 → 模型”,管道符就像 Unix 管道一样把一个个处理单元接起来。

我见过很多老教程还让用LLMChain(prompt=..., llm=...)这种旧语法,写起来特别笨重。如果你的依赖是按上面命令装的,直接用 LCEL 就行,这是 LangChain 新版的推荐做法。

3.4 Chain 设计带来的实际好处

链的好处要真正写了才体会得到。第一,可插拔:你想换模型,只需要替换llm这一个变量;想调整 Prompt,只改模板。第二,可组合:后续如果要做“先提取关键词再回答”这类复杂流程,就是chain1 = prompt | llm | 解析器,一个管道接着往下接。第三,可观测:LangChain 提供了 tracing 工具,能把一次调用的完整数据流记录下来,排查问题非常方便。

实际上,我自己的项目里,八成代码都是在定义prompt和组合chain。模型本身只负责“生成”,业务上的结构化处理全靠链来编排。理解了这一点,你再去看各种 LangChain 项目,大概率不会迷路。

4. 给模型装上记忆:多轮对话的实现

4.1 为什么模型“记不住”话

先做一个实验:用上面的chain连续问两轮话。

print(chain.invoke({"question": "你好,我叫小王"}).content) print(chain.invoke({"question": "我叫什么名字?"}).content)

第二轮的输出大概率是“抱歉,我没有上下文信息”。原因是 OpenAI 的 Chat API 本身是无状态的,每一次invoke都是独立请求,模型不记得之前发生过什么。所谓“记住”,其实是你把历史对话内容作为消息一起发给模型,让它看“过去的聊天记录”再作答。这就是 Memory 存在的意义:替你把历史保存好、拼装好。

4.2 用 InMemoryChatMessageHistory 管理会话历史

LangChain 新版提供了InMemoryChatMessageHistory这个轻量组件来存储消息记录,用法如下:

from langchain_core.chat_history import InMemoryChatMessageHistory from langchain_core.runnables.history import RunnableWithMessageHistory history_store = {} def get_session_history(session_id: str) -> InMemoryChatMessageHistory: if session_id not in history_store: history_store[session_id] = InMemoryChatMessageHistory() return history_store[session_id] with_history_chain = RunnableWithMessageHistory( chain, get_session_history, input_messages_key="question", ) result1 = with_history_chain.invoke( {"question": "你好,我叫小王"}, config={"configurable": {"session_id": "user-001"}}, ) result2 = with_history_chain.invoke( {"question": "我叫什么名字?"}, config={"configurable": {"session_id": "user-001"}}, ) print(result2.content)

这里有个关键概念:session_id。一个会话对应一份历史记录。你把它理解成“聊天窗口 ID”就行,不同用户传不同 ID,历史就自动隔离了。

RunnableWithMessageHistory的职责是:在每次调用前,从get_session_history里读取对应会话的历史,把它们作为历史消息插入到模板消息列表中,再把当前{"question": "..."}作为最新的用户消息追加进去,最后整体交给模型。它内部处理好了消息排序,你不用手写数组拼接。这是新版 LangChain 推荐的记忆方案,比老的ConversationBufferMemory更贴合 LCEL 的链式结构。

4.3 记忆选型的一个避坑点

旧版 LangChain 里非常流行ConversationBufferMemory,但在我用的新版下,它和 LCEL 链配合起来很别扭,很多教程里的写法跑起来会报兼容性错误。不是说它不能用了,而是它更适合旧版LLMChain那种结构。我的建议很直接:新项目直接用RunnableWithMessageHistory + InMemoryChatMessageHistory这套组合,少踩一堆版本坑。

再提醒一个隐藏问题:历史消息是无限增长的。用户聊个一百轮,历史消息里的 token 数很可能撑爆模型的上下文窗口,报context_length_exceeded错误。这个问题现在先记在心里,进阶部分我们再讲怎么用窗口截断和摘要压缩把历史“瘦身”。第一课能跑通记忆、理解原理,就已经达到目的了。

4.4 一个完整的带记忆 Demo 汇总

把前面内容拼起来,一个带记忆的多轮对话助手长这样:

import os from dotenv import load_dotenv from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.chat_history import InMemoryChatMessageHistory from langchain_core.runnables.history import RunnableWithMessageHistory load_dotenv() llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一位耐心的智能助手。"), ("human", "{question}"), ]) chat_chain = prompt | llm history_store = {} def get_session_history(session_id: str): if session_id not in history_store: history_store[session_id] = InMemoryChatMessageHistory() return history_store[session_id] with_history_chain = RunnableWithMessageHistory( chat_chain, get_session_history, input_messages_key="question", ) session_id = "demo-user" while True: user_input = input("你:") if user_input.strip().lower() in ["exit", "quit"]: break response = with_history_chain.invoke( {"question": user_input}, config={"configurable": {"session_id": session_id}}, ) print("助手:", response.content)

里面我用了while True循环,让用户可以在终端持续对话。运行后你问“我叫小王”,下一轮问“我叫什么”,它就能正确回答了。满足感很强,拿来当演示 Demo 也够用。

注意config={"configurable": {...}}这层嵌套不能省略,session_id必须放在configurable里,这是RunnableWithMessageHistory约定的参数传递方式。我第一次写的时候漏了configurable,直接报错,翻文档才发现。这种细节在框架集成里很常见,多踩一次坑就记住了。

5. 常见问题与排查技巧实录

5.1 API Key 与鉴权类报错

几乎所有初学者都会碰见下面这个错误:

AuthenticationError: Error code: 401 - {'error': {'message': 'Incorrect API key provided...'}}

提示401,说明 API Key 没被正确读取。先检查这几处:.env文件是不是放在项目根目录、load_dotenv()是不是在读取 Key 之前调用、.env里变量名是不是跟getenv("OPENAI_API_KEY")完全一致。我自己最容易犯的低级错误是在.env变量值两边加了引号,导致 Key 里混入引号字符,排查了十分钟。

还有一类是404 model_not_found,提示找不到模型。多半是模型名拼写错误,或者你的账号没开通该模型权限。去官方模型列表核对名字,gpt-4o-mini这种带连字符的千万别漏。

5.2 限流与 Token 超限

调用比较频繁时会遇到429或RateLimitError。这表示请求太密集,达到账号限制。我的经验:先看是不是自己代码里有循环并发调用,如果是,给请求之间加个time.sleep或者在调用函数里做简单的退避重试。项目里一般建议用tenacity这类库做指数退避,但第一课先用time.sleep(0.5)也能明显改善。

context_length_exceeded是另一个高频报错,意味着你发送的消息内容长度超出了模型支持的上下文窗口。原因大多是历史消息越攒越多。最简单粗暴的解法是限制历史条数,只保留最近 N 轮对话;复杂一点的做法是让模型对旧对话生成摘要。这两种思路后续课程会展开。

5.3 输出格式相关烦恼

模型的文本输出不稳定,经常多讲废话或者自己加编号,你要是直接把它当程序输入,很容易解析失败。这也是为什么 LangChain 有专门的输出解析器(Parser)体系。第一课先记住方法论:不要在业务代码里 try/catch 猜格式,应该让解析器标准化输出。比如StructuredOutputParser能强制模型按 JSON Schema 输出,后面做数据处理时极其好用。这一课先有个印象,第二课我们专门讲。

5.4 接口返回内容和预期不符

模型“答非所问”多半不是程序 bug,而是 Prompt 设计问题。你问的是 A,它理解偏了。建议排查顺序:先看系统提示词有没有设定边界,再看temperature是否过高,最后看历史消息里有没有串味。很多“幻觉”问题可以通过把任务描述得更加具体来大幅缓解。记住一条:LLM 应用里,上限是模型决定的,下限是 Prompt 决定的。

5.5 常见问题速查表

症状大概率原因处理建议
401 鉴权失败API Key 未正确加载或无效检查 .env、变量名、是否加引号
404 模型不存在模型名错误或未开通核对模型名,换成通用模型
429 限流请求过于频繁增加间隔,增加重试退避
context_length_exceeded历史消息过长截断历史、做摘要压缩
多轮对话“失忆”未接历史或 session_id 冲突检查 RunnableWithMessageHistory 配置
输出 JSON 解析失败模型自由输出接输出解析器强制结构化

这张表我打算继续维护,后面课程每次讲新增组件就补一行,慢慢就是一份很实用的排错手册。

6. 从第一课到实战,我的几点心得

6.1 先跑通再优化,别沉迷原理

这个建议可能和很多教程相反,但我真心这么想。LangChain 的源码和文档都很庞大,如果一开始就追求把所有概念弄懂,很容易陷入“文档刷了一个月,代码一行没跑”的状态。我的经验是:先把最小 Demo 跑起来,再去回头理解 Chain 的数据流动和 Memory 的组装方式。带着实际问题看源码,效率高得多。

6.2 用版本锁定规避玄学问题

LangChain 更新速度很快,接口变动频繁,网上的教程很多是旧版的,照抄会报各种兼容性错误。我踩过LLMChain变成非推荐、ConversationBufferMemory与 LCEL 不兼容这类坑。如果你是照着本文装的依赖,基本没问题;但如果是自己新建项目,建议把版本写在requirements.txt里,例如:

langchain==0.3.* langchain-openai==0.3.* python-dotenv==1.*

版本固定之后,就算过半年再回来看项目,也仍然跑得起来。这一点对生产项目尤其重要,依赖漂移是上线后最隐蔽的灾难来源。

6.3 最小闭环验证假设

第一课我只引进了四个组件,但已经足够完成“一个带记忆的聊天机器人”这个完整场景。在实际项目中,我往往也会让团队先用这四个组件把业务验证跑通,确认价值后再逐步加 Agent、向量检索、多模态等高级能力。这样每次引入新技术都有明确的目的性,而不是为了“酷”而上框架。

我个人在跑这个 Demo 时最大的感受是:LangChain 的学习曲线看起来吓人,但只要抓住“Prompt Template 负责组装输入、Chain 负责流程、Memory 负责状态”这条主线,后面所有复杂功能都不过是往这条主线上挂新的处理单元。第一个能记住上下文的聊天程序跑起来的那一瞬间,你会觉得这套框架的抽象是真的值。

下一课我会接着写输出解析:怎么让模型稳定输出 JSON、怎么把返回结果直接转成 Python 对象接业务逻辑。掌握了这个,你就能把模型从“聊天玩具”升级成“可靠的数据处理管线”。这次先到这里,跑通代码的同学可以在评论区分享你遇到的报错,我看到了会回。

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

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

立即咨询