在这里插入图片描述

文章目录


课程导读 & 学习目标

欢迎来到《LangChain从入门到精通》的第一课。在正式进入开发之前,我们需要先建立起对LangChain这座“大模型应用框架”的整体认知。很多初学者一上来就急着写代码,结果被各种概念(Chain、Agent、Memory、Retriever)搞得晕头转向,然后遇到版本不兼容、API变动、环境报错,最终放弃。本课就是要帮你从一开始就站在正确的起点上。

学完本节课,你将达到以下目标:

  1. 理解LangChain是什么,能解决什么问题 —— 不再停留在“听说是做大模型应用的”这种模糊认知,能够清晰说出LangChain的4大核心能力组件。
  2. 掌握LangChain的整体架构分层 —— 从底层基础抽象到上层应用模组,心中有架构图,知道每个概念属于哪一层。
  3. 了解版本演进历史,做出正确的版本选择 —— 避开0.1.x到0.3.x的坑,选择最适合学习和生产落地的版本。
  4. 零基础搭建完整的Python开发环境 —— 包括虚拟环境、LangChain安装、OpenAI兼容的API配置(即使你没有OpenAI官方密钥也能用替代方案)。
  5. 运行第一个完整的LangChain程序,并理解每一行代码的作用 —— 建立“写-跑-改”的正反馈循环。

本课是后续29节课的基石,请不要跳步。我们会用大量类比(比如把LangChain比作“乐高积木”)和原理图逻辑(但我会用文字描述出图的样子)来降低理解门槛。


前置知识与环境准备

1.1 你需要具备的硬性前提

  • Python 3.10 或更高版本(推荐3.10,3.11/3.12均可,注意避免3.9以下)
  • 基础的Python语法:知道变量、函数、类、列表推导、装饰器(不要求精通,见到能理解即可)
  • 简单的HTTP请求概念:知道什么是API、JSON、POST请求(不懂也没关系,会同步解释)
  • 一个代码编辑器:VSCode、PyCharm、Cursor都行,能写.py文件即可
  • 能联网:因为LangChain需要调用大模型API(可以是免费代理或本地模型)

💡 如果你是纯小白,连Python都没装好,请在课前花30分钟安装Python,并确保在终端输入python --version能看到版本号。

1.2 没有OpenAI官方Key怎么学?

很多教程上来就要20美元的OpenAI API Key,直接劝退。本专栏全程提供两种兼容方案

  • 方案A(推荐):使用国内大模型代理服务,如智谱AI(GLM-4)阿里百炼(qwen-turbo)DeepSeek等,它们提供兼容OpenAI接口的API,甚至免费额度够学完整专栏。
  • 方案B:使用Ollama + 本地模型(如llama3.2,qwen2.5),完全免费,无需联网。本课会给出两种方案的代码示例。

本课实战部分默认使用方案A(以智谱AI为例,注册送免费额度),同时提供切换成本地模型的代码注释。

1.3 你的第一份环境依赖清单

我们将使用Python虚拟环境(强烈推荐,避免污染全局包)。打开终端(Windows用PowerShell或CMD,Mac/Linux用Terminal):

# 创建项目目录
mkdir langchain_course && cd langchain_course

# 创建虚拟环境(Python 3.10+)
python -m venv venv

# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Mac/Linux:
source venv/bin/activate

# 升级pip
python -m pip install --upgrade pip

接着创建requirements.txt文件,内容如下:

langchain==0.3.7
langchain-core==0.3.21
langchain-community==0.3.7
langchain-openai==0.2.8
python-dotenv==1.0.1

然后执行:

pip install -r requirements.txt

⚠️ 版本特别说明:截至2025年5月,LangChain稳定版为0.3.x(2024年底发布)。0.3.x相比0.2.x和0.1.x有较大破坏性变更,但更规范、类型提示完备,是本专栏的教学版本。如果你看到网上旧教程使用langchain.llmsfrom langchain.chains import LLMChain,那都是过时的,本课会教你最新的写法。


核心概念深度拆解

要学好LangChain,首先丢掉一个错误印象:“LangChain是一个封装好的SDK,我调两个函数就能做一个聊天机器人”。实际上,LangChain是一个编排框架,它不做模型本身,而是帮你把大模型、外部数据、工具、记忆等组件像拼乐高一样组合起来完成复杂任务。

2.1 LangChain解决的三个核心痛点

![LangChain痛点图示](文字描述)
想象你想做一个“能够查询公司内部文档并回答问题的智能助手”,如果不使用框架,你需要自己:

  • 写代码把文档切碎、存储到向量数据库
  • 处理大模型API的超时、重试、流式输出
  • 手动拼接Prompt模板,处理对话历史
  • 决定何时调用外部搜索工具
  • ……

LangChain把这些通用模式封装成标准化组件,让你专注于业务逻辑。

痛点1:与大模型交互的重复底层工作
每次调用都要处理API密钥、请求格式、错误重试、输出解析,LangChain提供了统一的ChatModel接口。

痛点2:大模型本身没有记忆和外部知识
每次调用都是无状态的,LangChain提供Memory组件管理对话历史,Retriever组件从外部文档检索相关信息。

痛点3:单一模型无法完成复杂任务
比如“帮我写报告,并且搜索最新的行业数据”,需要模型自己决定调用搜索工具。LangChain的Agent可以实现这种“思考-行动-观察”循环。

2.2 LangChain核心组件一览(类比餐厅点餐)

为了让你秒懂,我们把整个框架类比为一家智能餐厅

  • Model(大模型) = 厨师长,负责烹饪(生成回答),但他不会自己买菜、不会记住你说过喜欢辣。
  • Prompt Template(提示模板) = 标准点菜单,每次点餐都填:菜品名、辣度、忌口。不必每次都重写“请做一道菜”这种废话。
  • Output Parser(输出解析器) = 服务员,把厨师做出的菜(模型输出字符串)摆盘成结构化格式,比如JSON、列表。
  • Memory(记忆) = 餐厅的顾客档案,记录你上次点了什么、爱吃什么,下次不用再说一遍。
  • Retriever(检索器) = 食材采购员,当你需要特殊食材(外部知识)时,他去仓库(向量数据库)找出来给厨师。
  • Chain(链) = 一条完整的业务流,比如“从记忆取偏好 → 组装提示 → 调用模型 → 解析输出 → 存记忆”,每一步串起来。
  • Agent(智能代理) = 餐厅经理,面对复杂需求(比如“做一顿生日宴,要蛋糕、要热闹、要优惠券”),他会拆解任务,决定找谁(厨师、采购员、优惠券系统)。

有了这个类比,你以后看到任何LangChain代码,都可以问自己:这一段是在扮演餐厅里的哪个角色?

2.3 LangChain整体架构分层

LangChain官方将框架分为三层(从低到高):

层级 名称 作用 对应类比
第1层 基础抽象层 (Base Abstractions) 定义标准接口,比如BaseChatModelBaseRetrieverBaseMemory。所有第三方集成必须实现这些接口。 餐厅的设备接口标准(所有煤气灶必须能点火,所有冰箱必须能制冷)
第2层 实现层 (Concrete Implementations) 具体实现,比如ChatOpenAIChromaRetrieverConversationBufferMemory 具体品牌煤气灶、海尔冰箱
第3层 编排层 (Orchestration Layer) 组装组件成链、Agent、运行流程。 餐厅的管理系统,指挥所有人协作

作为初学者,你大部分时间在第3层调用组件,偶尔需要自己扩展第2层(比如自定义一个Retriever)。

2.4 版本选择深度解析(决定你未来3个月不踩坑)

LangChain版本迭代非常快,这里给出版本时间线:

  • 0.1.x(2023年初-2024年初):早期版本,混乱,很多API设计不合理,大量LLMChainload_qa_chain等函数式写法。不要学,教程已过时。
  • 0.2.x(2024年中):过渡版本,引入langchain-core,开始推崇@chain装饰器和Runnable接口。但兼容性混杂。
  • 0.3.x(2024年底 - 当前最新)推荐版本。完全基于Runnable接口(LCEL,LangChain Expression Language),类型提示完整,废弃了大量旧API。本专栏基于0.3.7。

为什么0.3.x是教学首选?

  • 未来所有新功能都基于LCEL,学了0.3.x就等于掌握了未来1-2年的写法。
  • 0.3.x的ChatModel统一了同步/异步/流式接口,写一次代码能无缝切换模型(GPT-4、Claude、GLM等)。
  • 错误信息更友好,调试更容易。

版本兼容注意
如果你在GitHub或Stack Overflow上看到类似from langchain.llms import OpenAI,那是0.1.x的写法,0.3.x请使用from langchain_openai import ChatOpenAI。本课会全部使用新写法。


底层运行原理剖析

在使用LangChain之前,你需要理解它在背后做了些什么。不需要深究源码,但要知道大致流程。

3.1 一次简单的ChatModel调用背后发生了啥?

当你写下:

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo")
response = llm.invoke("你好,请介绍自己")
print(response.content)

LangChain内部大致做了这些事:

  1. 参数校验:检查API密钥、model名称、温度参数是否合法。
  2. 构建请求体:将用户消息按照OpenAI API格式打包成JSON,例如{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "你好,请介绍自己"}]}
  3. 发送HTTP请求:使用httpxrequests发送POST到https://api.openai.com/v1/chat/completions,带重试和超时控制。
  4. 解析响应:收到返回的JSON,提取choices[0].message.content字符串。
  5. 封装为AIMessage对象:将字符串和额外元数据(token消耗、finish_reason)打包成AIMessage实例返回。
  6. 异常映射:如果网络错误或API返回429(限流),LangChain会抛出自定义异常,如RateLimitError

整个过程对用户透明。你只需调用.invoke(),同步拿到结果。

3.2 LangChain Expression Language (LCEL) 的由来

在0.1.x时代,组合Chain需要写很多样板代码,比如先初始化PromptTemplate,再LLMChain,再SequentialChain,代码难以阅读和修改。到了0.3.x,官方推出了LCEL,使用管道符|来组合组件。

示例对比:

旧写法(0.1.x,不要用)

from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

template = PromptTemplate(input_variables=["topic"], template="写一首关于{topic}的诗")
chain = LLMChain(llm=llm, prompt=template)
chain.run(topic="春天")

新写法(0.3.x,LCEL)

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template("写一首关于{topic}的诗")
output_parser = StrOutputParser()
chain = prompt | llm | output_parser
result = chain.invoke({"topic": "春天"})

LCEL的原理是:每个组件都实现了Runnable接口,该接口定义了.invoke().stream().batch()等方法。管道符|实际上是RunnableSequence的语法糖,它会将左边的输出作为右边的输入。这种设计让链的组合异常清晰,并且天然支持流式、批处理和异步。

3.3 流式输出的底层机制

调用llm.stream()时,LangChain会设置HTTP请求头Accept: text/event-stream(Server-Sent Events)。大模型API会分块返回token,每次返回一个数据块,LangChain解析后yield出来。这样用户就能实现打字机效果,而不是等待全部生成完。


核心API/组件源码解读

本节不要求你读完整个LangChain源码(几十万行),但我会带你剖析几个最关键的基类和常用组件的核心方法签名,这能帮助你理解为什么代码要那么写,以及遇到错误时如何快速定位。

4.1 BaseChatModel 抽象基类(源码片段)

LangChain中所有聊天模型都继承自BaseChatModel,其核心方法:

# 来自 langchain_core/language_models/chat_models.py
class BaseChatModel(BaseLanguageModel, ABC):
    @abstractmethod
    def _generate(
        self,
        messages: List[BaseMessage],
        stop: Optional[List[str]] = None,
        run_manager: Optional[CallbackManagerForLLMRun] = None,
        **kwargs,
    ) -> ChatResult:
        """同步生成方法,子类必须实现,返回ChatResult包含生成的message和元数据"""
        
    def invoke(self, input: LanguageModelInput, config: Optional[RunnableConfig] = None) -> BaseMessage:
        """对外暴露的调用入口,内部处理缓存、事件、重试等,最终调用_generate"""
        
    async def ainvoke(self, ...) -> BaseMessage:
        """异步版本"""
        
    def stream(self, input: LanguageModelInput, config: Optional[RunnableConfig] = None) -> Iterator[BaseMessageChunk]:
        """流式输出,默认实现是将完整结果拆成chunk,但具体模型可覆盖实现真正的流式"""

关键理解:你从不直接调用_generate,而是调用.invoke().invoke()内部会触发回调管理器、处理重试逻辑,最后调用你传入模型的具体实现(比如ChatOpenAI_generate会发HTTP请求)。

4.2 ChatPromptTemplateformat 原理

ChatPromptTemplate负责将用户输入的变量转换成模型可接受的消息列表。它的核心方法是format_prompt(**kwargs)

# 简化的源码逻辑
class ChatPromptTemplate(BaseChatPromptTemplate):
    def format_prompt(self, **kwargs) -> StringPromptValue:
        messages = []
        for message_template in self.messages:
            # 每个message_template可能是"human", "ai", "system"类型
            # 调用其format方法,替换变量
            formatted = message_template.format(**kwargs)
            messages.append(formatted)
        return PromptValue(messages=messages)

例如你的模板是:

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是{role}助手"),
    ("user", "{input}")
])

调用prompt.format_prompt(role="专业", input="你好")会生成:

[SystemMessage(content="你是专业助手"), HumanMessage(content="你好")]

4.3 StrOutputParser 源码解析

输出解析器的职责是把模型输出的AIMessage对象转换成字符串。StrOutputParser的实现极其简单:

class StrOutputParser(BaseOutputParser[str]):
    def parse(self, text: str) -> str:
        return text
    
    def parse_result(self, result: List[Generation], *, partial: bool = False) -> str:
        return result[0].text

所以当你chain = prompt | llm | StrOutputParser()时,最终invoke返回的就是纯字符串,而不是AIMessage对象。

4.4 为什么LCEL中的组件要保证输入输出类型匹配?

LCEL正常工作的前提是前一个组件的输出类型能作为后一个组件的输入类型。例如:

  • prompt:输入dict(比如{"topic":"春天"}),输出PromptValue(可转化为字符串或消息列表)
  • | llmllm的输入要求是BaseMessage列表或字符串,而PromptValue实现了to_messages()方法,LCEL会自动调用它转换,所以没问题。
  • | output_parseroutput_parser的输入要求是BaseMessage,因为llm的输出正是BaseMessage,所以也没问题。

这种类型契约让链式调用非常安全。如果你自定义一个组件,需要实现Runnable接口并明确输入输出类型。


手把手项目实战教学

理论讲够,现在开始动手!我们将创建三个渐进式的实战程序:

  1. 单次调用大模型:感受最基础的invoke
  2. 带提示模板和输出解析器的链:学会LCEL基本用法。
  3. 添加记忆的对话机器人:为后续Agent课铺垫。

实战一:你的第一个LangChain程序 —— 让大模型介绍LangChain

目标:调用智谱AI的GLM-4模型(兼容OpenAI接口),问“什么是LangChain?”,打印回答。

步骤1:配置API密钥

注册智谱AI开放平台(送500万tokens免费额度),创建API Key。为了安全,不要将密钥写在代码里。我们在项目根目录创建.env文件:

ZHIPU_API_KEY=你的实际密钥
BASE_URL=https://open.bigmodel.cn/api/paas/v4/
步骤2:编写第一个脚本 01_basic_invoke.py
# 加载环境变量
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI  # 注意:智谱兼容OpenAI接口,所以用这个
from langchain_core.messages import HumanMessage

load_dotenv()  # 读取.env文件

# 从环境变量获取密钥
api_key = os.getenv("ZHIPU_API_KEY")
base_url = os.getenv("BASE_URL")

# 初始化模型(通过base_url指向智谱)
llm = ChatOpenAI(
    model="glm-4",           # 智谱的模型名
    openai_api_key=api_key,
    openai_api_base=base_url,
    temperature=0.7,         # 控制随机性,0-1,越高越有创意
    timeout=60               # 请求超时秒数
)

# 方式一:直接使用invoke传入消息列表
messages = [HumanMessage(content="请用一句话介绍什么是LangChain?")]
response = llm.invoke(messages)

print("回答:", response.content)
print("消耗token:", response.response_metadata.get('token_usage', {}))

运行:

python 01_basic_invoke.py

预期输出:类似“LangChain是一个用于构建大语言模型应用的开源框架,它通过可组合的组件简化了复杂AI工作流的开发。” 同时会打印token用量。

代码解读

  • HumanMessage表示用户消息,还有SystemMessage(系统设置)、AIMessage(模型回复)。
  • invoke是最常用同步调用,返回AIMessage对象,.content取文本。
  • response_metadata包含了模型返回的额外信息,如token消耗、延迟等。

实战二:使用LCEL构建提示模板链

场景:我们希望模型以严格的JSON格式输出,包含“名字”和“解释”字段。

文件02_prompt_template_chain.py

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
import json

load_dotenv()

llm = ChatOpenAI(
    model="glm-4",
    openai_api_key=os.getenv("ZHIPU_API_KEY"),
    openai_api_base=os.getenv("BASE_URL"),
    temperature=0
)

# 定义提示模板,包含一个变量 concept
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个AI概念解释专家。请用JSON格式输出,包含两个字段:name(概念名称)和explanation(一句话解释)。"),
    ("user", "请解释概念:{concept}")
])

# 输出解析器:先转换成字符串,但我们额外希望解析JSON
chain = prompt | llm | StrOutputParser()

# 执行链
concept = "RAG (Retrieval-Augmented Generation)"
result = chain.invoke({"concept": concept})

print("原始模型输出:", result)

# 尝试解析JSON(有些模型可能不严格遵守格式,这里做容错)
try:
    # 提取可能被markdown包裹的JSON
    if "```json" in result:
        result = result.split("```json")[1].split("```")[0]
    parsed = json.loads(result)
    print("解析后:", parsed)
except:
    print("模型未返回合法JSON,原始内容:", result)

运行效果:模型会输出类似{"name": "RAG", "explanation": "检索增强生成,结合信息检索与语言生成来提高回答准确性。"}

重要技巧StrOutputParser只做最简单的字符串提取,如果你需要结构化的JSON解析,LangChain提供了JsonOutputParser,它可以更可靠地处理。我们将在第5课详细讲解。

实战三:带记忆的对话机器人(为后续铺垫)

目标:实现一个能记住上下文的简单聊天机器人,使用ConversationBufferMemory

文件03_chat_with_memory.py

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

load_dotenv()

llm = ChatOpenAI(
    model="glm-4",
    openai_api_key=os.getenv("ZHIPU_API_KEY"),
    openai_api_base=os.getenv("BASE_URL")
)

# 提示模板:包含占位符 {history} 用于插入历史消息
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的助手,请用中文回答。"),
    MessagesPlaceholder(variable_name="history"),  # 历史消息会动态插入这里
    ("human", "{input}")
])

# 创建基础链
chain = prompt | llm

# 存储不同会话的历史(简单用字典)
store = {}

def get_session_history(session_id: str):
    """根据session_id返回该会话的消息历史对象"""
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# 包装成带记忆的链
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="input",      # 输入中哪个字段是用户消息
    history_messages_key="history"   # 提示模板中的历史占位符名称
)

# 模拟多轮对话
config = {"configurable": {"session_id": "user-001"}}

response1 = chain_with_history.invoke(
    {"input": "我叫小明,我喜欢打篮球"},
    config=config
)
print("AI:", response1.content)

response2 = chain_with_history.invoke(
    {"input": "我最喜欢的运动是什么?"},
    config=config
)
print("AI:", response2.content)  # 应该回答篮球

运行结果:第二轮问答中,模型能从历史中提取出“小明喜欢打篮球”的信息。这里用到了RunnableWithMessageHistory,它会自动在每次调用前从store加载历史消息,填充到MessagesPlaceholder,调用后把新消息追加到历史中。

🎉 恭喜!你已经完成了LangChain核心组件的初步实践。以上三个实战基本覆盖了日常开发的80%场景。


完整可运行Python代码(带逐行详细注释)

本节将三个实战的代码整合成一个完整脚本,并附带更详尽的注释,方便你复制粘贴到一个文件中体会整体流程。你可以创建一个full_demo.py文件。

"""
LangChain第1课完整演示脚本
涵盖:基础调用、提示模板链、带记忆对话
确保已执行 pip install langchain langchain-core langchain-community langchain-openai python-dotenv
并配置好.env文件
"""

import os
import json
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# ========== 1. 加载配置 ==========
load_dotenv()  # 从当前目录的.env文件读取环境变量
API_KEY = os.getenv("ZHIPU_API_KEY")
BASE_URL = os.getenv("BASE_URL")

if not API_KEY:
    raise Exception("请在.env文件中设置ZHIPU_API_KEY")

# ========== 2. 初始化模型(兼容OpenAI接口) ==========
# 注意:这里的ChatOpenAI并非只能连接OpenAI官方,通过base_url可指向任何兼容接口
llm = ChatOpenAI(
    model="glm-4",               # 模型名称,智谱使用glm-4,如果使用OpenAI官方则写gpt-3.5-turbo
    openai_api_key=API_KEY,
    openai_api_base=BASE_URL,    # 例如:https://open.bigmodel.cn/api/paas/v4/
    temperature=0.7,             # 控制创造性,0为确定性,1为随机
    timeout=60,                  # 请求超时秒数
    max_retries=2                # 失败重试次数
)

# ========== 3. 实战一:基础调用 ==========
def basic_invoke():
    """最简单的单次问答,不涉及模板和记忆"""
    from langchain_core.messages import HumanMessage
    messages = [HumanMessage(content="LangChain是什么?用50字以内回答")]
    response = llm.invoke(messages)
    print("[基础调用] 回答:", response.content)
    print("[基础调用] 元数据:", response.response_metadata)

# ========== 4. 实战二:LCEL链 + 输出解析 ==========
def template_chain_demo():
    """使用提示模板,强制模型输出JSON格式"""
    # 构造聊天提示模板,支持变量插值
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个AI专家。请输出一个JSON对象,包含{concept}的定义。格式:{{\"concept\": \"{concept}\", \"definition\": \"...\"}}"),
        ("human", "请定义:{concept}")
    ])
    # 使用管道符创建链:输入变量 -> 提示模板 -> 模型 -> 字符串解析器
    chain = prompt | llm | StrOutputParser()
    result = chain.invoke({"concept": "LangChain"})
    print("[模板链] 原始输出:", result)
    # 尝试解析JSON(处理模型可能返回markdown包裹的情况)
    cleaned = result.strip()
    if cleaned.startswith("```json"):
        cleaned = cleaned.split("```json")[1].split("```")[0]
    try:
        data = json.loads(cleaned)
        print("[模板链] 解析后的JSON:", data)
    except:
        print("[模板链] 未能解析为合法JSON,请检查模型输出")

# ========== 5. 实战三:带记忆的多轮对话 ==========
def memory_chat_demo():
    """展示如何让模型记住对话历史"""
    # 提示模板中包含一个插槽用于动态插入历史消息
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个乐于助人的助手,请记住用户之前提到的信息。"),
        MessagesPlaceholder(variable_name="history"),  # 历史消息将出现在这里
        ("human", "{input}")
    ])
    # 基础链(没有记忆)
    chain = prompt | llm
    # 用于存储不同会话的历史记录(生产环境应使用Redis或数据库)
    session_storage = {}
    
    def get_session_history(session_id: str):
        if session_id not in session_storage:
            session_storage[session_id] = ChatMessageHistory()
        return session_storage[session_id]
    
    # 包装链,使其自动注入和保存历史
    chain_with_history = RunnableWithMessageHistory(
        chain,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history"
    )
    
    config = {"configurable": {"session_id": "test_user"}}
    
    # 第一轮:告诉模型名字
    resp1 = chain_with_history.invoke({"input": "我叫王小明,我喜欢编程和象棋"}, config)
    print("[记忆对话] 第1轮AI:", resp1.content)
    
    # 第二轮:测试模型是否记得
    resp2 = chain_with_history.invoke({"input": "我叫什么名字?我喜欢什么?"}, config)
    print("[记忆对话] 第2轮AI:", resp2.content)
    # 期望回答:王小明,编程和象棋
    
    # 第三轮:换一个新会话,应该没有记忆
    config2 = {"configurable": {"session_id": "another_user"}}
    resp3 = chain_with_history.invoke({"input": "我叫什么名字?"}, config2)
    print("[记忆对话] 新会话AI:", resp3.content)
    # 期望:不知道,因为没有历史

# ========== 6. 主入口 ==========
if __name__ == "__main__":
    print("========== 实战一 ==========")
    basic_invoke()
    print("\n========== 实战二 ==========")
    template_chain_demo()
    print("\n========== 实战三 ==========")
    memory_chat_demo()

环境依赖安装命令(可直接复制)

# 创建并激活虚拟环境(推荐)
python -m venv langchain_env
# 激活:Windows -> langchain_env\Scripts\activate   Mac/Linux -> source langchain_env/bin/activate

# 安装核心依赖(国内用户可使用镜像加速,如 -i https://pypi.tuna.tsinghua.edu.cn/simple)
pip install langchain==0.3.7 langchain-core==0.3.21 langchain-community==0.3.7 langchain-openai==0.2.8 python-dotenv==1.0.1

# 如果使用本地Ollama方案,额外安装:
# pip install ollama langchain-ollama

# 验证安装
python -c "import langchain; print(langchain.__version__)"  # 应输出0.3.7

.env文件模板:

# 智谱AI配置(或换成你的OpenAI兼容服务)
ZHIPU_API_KEY=your_key_here
BASE_URL=https://open.bigmodel.cn/api/paas/v4/

# 如果使用OpenAI官方,注释掉上面,改用:
# OPENAI_API_KEY=sk-xxx
# BASE_URL=https://api.openai.com/v1

常见报错坑点与避坑方案

作为零基础学员,你在运行上述代码时很可能遇到以下问题。我已为你整理了解决步骤。

坑1:ModuleNotFoundError: No module named 'langchain_openai'

原因:没有安装langchain-openai包,或者安装的是旧版langchain(0.1.x)不包含该子包。

解决:确保执行了pip install langchain-openai==0.2.8。同时注意不要使用pip install langchain不带版本号,因为最新版可能不兼容(截至2025年5月,最新版就是0.3.x,但仍建议指定版本)。

坑2:AuthenticationError: Incorrect API key provided

原因:API密钥错误或base_url不正确。

解决

  • 检查.env文件中的ZHIPU_API_KEY是否复制完整(没有多余空格)。
  • 确认智谱AI的BASE_URL应为https://open.bigmodel.cn/api/paas/v4/(末尾斜杠可有可无)。
  • 若使用OpenAI官方,请确保OPENAI_API_KEY有效,并且BASE_URL注释掉或设为https://api.openai.com/v1

坑3:openai.NotFoundError: 404 model not found

原因:模型名称错误。智谱AI的模型名是glm-4glm-4-plus等,不是gpt-3.5-turbo

解决:查阅服务商的文档,填入正确的模型名。如果是智谱,使用glm-4

坑4:ImportError: cannot import name 'ChatMessageHistory' from 'langchain.memory'

原因:你在0.3.x中尝试使用旧的导入路径。ChatMessageHistory已经从langchain_community.chat_message_histories导入。

解决:使用from langchain_community.chat_message_histories import ChatMessageHistory,而不是from langchain.memory import ChatMessageHistory

坑5:运行带记忆的代码时,提示TypeError: __init__() got an unexpected keyword argument 'session_id'

原因RunnableWithMessageHistoryconfig参数需要包含configurable键,且get_session_history函数必须接收一个字符串参数。

解决:参考实战三的写法,确保config = {"configurable": {"session_id": "xxx"}}

坑6:模型返回的内容被截断或超时

原因:默认超时时间太短,或者模型输出过长。

解决:在初始化ChatOpenAI时增加timeout=120,并设置max_tokens=1000(根据模型参数)。另外,智谱免费版可能有频率限制,可适当增加max_retries

坑7:虚拟环境激活后,pip仍然安装到全局

原因:终端未正确激活虚拟环境,或使用了系统自带的pip。

解决:激活后执行where python(Windows)或which python(Mac/Linux),确认路径在虚拟环境目录内。如果不对,重新激活。


本节核心知识点总结

📌 LangChain本质:一个编排框架,用于组合大模型、外部数据、工具等构建复杂AI应用。它不提供模型本身,而是提供标准化接口。

📌 四大核心组件:Model(模型)、Prompt Template(提示模板)、Memory(记忆)、Retriever(检索器)。类比餐厅角色,帮助记忆。

📌 架构分层:基础抽象层(接口定义)→ 实现层(具体类)→ 编排层(链/Agent)。初学者主要接触编排层。

📌 版本选择:强烈推荐LangChain 0.3.x(本专栏基准),摒弃0.1.x旧API,掌握LCEL管道式写法。

📌 LCEL核心:使用|符号组合Runnable对象,每个对象的输出自动作为下一个的输入。例如:prompt | llm | output_parser

📌 环境搭建要点

  • Python 3.10+,虚拟环境隔离依赖
  • 依赖包:langchain, langchain-core, langchain-community, langchain-openai
  • 使用.env管理API密钥,勿硬编码

📌 三个实战覆盖基础

  1. 直接invoke调用模型
  2. 带提示模板和输出解析的LCEL链
  3. 使用RunnableWithMessageHistory实现会话记忆

📌 常见报错解法:导入路径变更、模型名称不匹配、API密钥错误、虚拟环境问题。


课后练习题(含原题+标准答案+详细解析)

选择题

1. 下列哪个不是LangChain 0.3.x中推荐的导入方式?
A. from langchain_openai import ChatOpenAI
B. from langchain_core.prompts import ChatPromptTemplate
C. from langchain.chains import LLMChain
D. from langchain_community.chat_message_histories import ChatMessageHistory

2. 在使用LCEL构建链时,prompt | llm | output_parser 中要求 prompt 的输出能被 llm 的输入接收。llminvoke 方法期望的输入类型是?
A. str
B. Dict[str, Any]
C. List[BaseMessage]PromptValue
D. BaseMessage

3. 以下哪种方法可以在LangChain中实现对话记忆功能?
A. 每次调用时将完整历史拼接到用户消息中
B. 使用 ConversationBufferMemory 配合 RunnableWithMessageHistory
C. 在模型参数中开启remember=True
D. 设置全局变量存储消息

4. 关于LangChain 0.3.x相比于0.1.x的变化,说法错误的是?
A. 废弃了LLMChain,推荐使用LCEL
B. 统一了同步/异步/流式接口
C. 不再支持OpenAI模型的调用
D. 引入了langchain-core包作为核心抽象

答案及解析:

  1. C。在0.3.x中,from langchain.chains import LLMChain是旧版写法,已被废弃。新版应使用LCEL方式构建链。

  2. CChatOpenAIinvoke方法接受LanguageModelInput类型,通常是List[BaseMessage]或可以转换为消息列表的PromptValue对象。提示模板输出就是PromptValue,因此能直接传入。

  3. BRunnableWithMessageHistory是官方推荐的记忆管理方式,它自动处理历史注入和保存。A选项手工拼接容易出错且不安全;C选项模型本身没有记忆参数;D选项全局变量在多会话时无法隔离。

  4. C。0.3.x完全支持OpenAI模型,只是通过langchain-openai包导入,并未废弃。其他选项正确。

简答题

5. 请简述LangChain中Runnable接口的作用,并举例说明LCEL如何利用该接口实现灵活组合。

参考答案
Runnable是LangChain 0.3.x中所有可调用组件(模型、提示模板、解析器、检索器等)的抽象基类。它定义了.invoke(), .stream(), .batch()等标准方法,使得不同组件可以通过|管道符链接,只要上一个组件的输出类型匹配下一个组件的输入类型。LCEL利用这一特性,例如prompt | llmprompt是一个Runnable,它的invoke返回PromptValue对象;llm也是一个Runnable,它的invoke能够接收PromptValue并自动转换为消息列表,最终返回AIMessage。这种设计让代码可读性极高,且天然支持流式和批处理。

6. 在实战三的带记忆代码中,为什么需要MessagesPlaceholder(variable_name="history")?如果不使用,如何手动实现记忆效果?

参考答案
MessagesPlaceholder是一个占位符,它告诉LangChain在运行时将存储的历史消息列表动态插入到该位置。如果不使用它,你需要自己在每次调用前将历史消息手动拼接到提示模板中,比如:"System: ... \n 历史: {history_str} \n Human: {input}",但这样非常繁琐且容易破坏消息角色的结构(系统、人类、AI混合)。手动实现需要额外写格式化函数,且流式输出时历史更新逻辑复杂。使用MessagesPlaceholder是官方推荐的标准做法,能保证消息角色的正确顺序。

实践题

7. 修改实战二的代码,使用JsonOutputParser代替手动解析JSON,要求生成的JSON包含namedescription两个字段。写出代码及运行结果。

参考答案

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field

load_dotenv()

llm = ChatOpenAI(
    model="glm-4",
    openai_api_key=os.getenv("ZHIPU_API_KEY"),
    openai_api_base=os.getenv("BASE_URL"),
    temperature=0
)

# 定义期望的JSON结构(Pydantic模型)
class Concept(BaseModel):
    name: str = Field(description="概念的名称")
    description: str = Field(description="概念的一句话描述")

# 创建解析器
parser = JsonOutputParser(pydantic_object=Concept)

# 构建提示模板,包含格式说明
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个AI专家。请输出一个符合以下格式的JSON对象,不要包含其他文本。\n{format_instructions}"),
    ("human", "解释概念:{concept}")
])

# 将格式指令注入到提示中
chain = prompt.partial(format_instructions=parser.get_format_instructions()) | llm | parser

result = chain.invoke({"concept": "LangChain"})
print(result)  # 输出类似 {'name': 'LangChain', 'description': '一个用于构建LLM应用的框架'}

解析JsonOutputParser结合Pydantic模式可以自动生成格式指令(如示例输出应该长什么样),并解析模型返回的JSON为字典。比手动解析更健壮。


🔗《30节课 LangChain 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐