1、准备

  • 使用PyCharm 2025.3及以上版本,可以直接创建uv管理项目
  • 在IDE内部可以使用Jupyter notebook直接使用:uv add notebook
  • 使用.env文件保存API_KEY并提供环境变量访问

LangChain官网:


2、基础:调用大模型

2.1 准备工作

首先,我们要安装OpenAI的依赖

uv add openai

2.2 初始化客户端

  • 硬编码API_KEY
from openai import OpenAI

client = OpenAI(api_key="sk-xxxx", base_url="https://api.deepseek.com")
  • 从环境变量中获取API_KEY

读取变量需要用到python-dotenv,先安装依赖,命令是:

uv add python-dotenv

再创建.env文件,并写入API_KEY

API_KEY=xxxx

接着就可以从环境变量中获取API_KEY了。

from dotenv import load_dotenv
import os

load_dotenv()

api_key = os.getenv("DEEPSEEK_API_KEY")
print(api_key)

client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com")

2.3 访问模型

response = client.responses.create(
    model="deepseek-v4-flash",
    instructions="You are a helpful assistant.",
    input="Hi, how are you?",
)

2.4 获取模型响应结果

print(response.output_text)

3、LangChain入门案例:天气查询智能体

在这里插入图片描述

3.1 准备工作

首先,要使用LangChain必须先安装依赖,命令如下:

uv add langchain

LangChain支持各种不同的模型,而且提供了对应的兼容SDK,不过也都需要安装对应依赖,你可以按需添加:

# 集成DeepSeek
uv add langchain-deepseek
# 集成OpenAI
uv add langchain-openai
# 集成Anthropic
uv add langchain-anthropic

接下来就可以开发agent了,基本步骤如下:

  1. 加载环境变量
  2. 定义工具
  3. 定义Agent
  4. 调用Agent

3.2 开发Agent

加载环境变量

# 1. 加载环境变量
from dotenv import load_dotenv
load_dotenv()

定义工具

# 2. 定义工具
from langchain.tools import tool

@tool
def getWeather(location: str) -> str:
	"""
	Get the weather in a given location.
	Args:
		location: city name or coordinates
	"""
	return f"Current weather in {location} is sunny"

创建Agent

# 3. 创建Agent
from langchain.agents import create_agent

agent = create_agent(
    model="deepseek-v4-flash",
    tools=[getWeather],
    system_prompt="You are a helpful assistant",
)

发起调用

# 4. 调用Agent
result = agent.invoke(
    {"messages": [{"role": "user", "content": "杭州今天天气如何?"}]}
)
print(result["messages"][-1].content_blocks)
# [{'type': 'reasoning', 'reasoning': "The weather in Hangzhou is sunny today. I'll respond to the user in Chinese."}, {'type': 'text', 'text': '杭州今天天气晴朗(sunny)☀️,天气不错,适合外出活动!如果有出行计划,记得做好防晒哦。'}]

3.2 Agent执行流程

在这里插入图片描述

Agent如何知道工具信息的

在这里插入图片描述


4、LangChain初始化模型

LangChain支持现在市面上大部分常见的大语言模型LLM,并且提供了各个模型的对应依赖库。
在这里插入图片描述
使用时只需要引入对应的类即可使用:

  • 过时写法:
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-5.2")
  • 推荐写法:不需要实例化具体的类,自动路由
from langchain.chat_models import init_chat_model
model = init_chat_model(model="gpt-5.2")

4.1 开发流程

在这里插入图片描述

4.2 实践

# 加载环境变量
from dotenv import load_dotenv
load_dotenv()

初始化并调用模型

LangChain提供了两种常见函数用来初始化模型:

  • 使用init_chat_model函数,由LangChain自动创建模型对象
  • 使用不同模型对应的类,手动创建模型对象
init_chat_model

官方最推荐的方式就是使用init_chat_model函数。

基于名称推断模型提供商

使用init_chat_model函数,你需要从LangChain支持的模型提供者(Model Provider)中选择一个模型。而LangChain根据模型名称自动初始化与模型的连接,非常方便。

LangChain支持的模型列表参考官方链接:
https://docs.langchain.com/oss/python/integrations/providers/overview

接下来,你要做的事情包括:

  • 安装模型依赖:uv add langchain langchain-deepseek
  • 在.env中配置模型的api_key
  • 调用init_chat_model函数,传入正确的模型名称
# 导入LangChain的初始化模型的函数
from langchain.chat_models import init_chat_model
# 调用init_chat_model函数初始化模型
# 参数model用来指定模型名称,LangChain会根据模型名字自动设定base_url,并从环境变量中获取api_key
model = init_chat_model(model="deepseek-v4-flash")
print(type(model))
# <class 'langchain_deepseek.chat_models.ChatDeepSeek'>

4.3 自定义模型提供商

init_chat_model默认会根据模型名称自动确定模型的提供者的base_url,并从env读取api_Key,但前提必须是LangChain支持的模型平台,例如:

  • openai
  • deepseek

对于其他模型,我们必须自定义模型参数来访问

例如,我们要访问阿里云百炼的qwen-max,他就不是LangChain支持的模型,我们必须自定义模型参数来访问。

  • 我们需要再环境变量中定义api_key和base_url
  • 然后在init_chat_model中指定model、model_provider、base_url和api_key。
# 我们收到加载环境变量中的base_url和api_key
import os
base_url = os.getenv("DASHSCOPE_BASE_URL")
api_key = os.getenv("DASHSCOPE_API_KEY")

model = init_chat_model(
	model="qwen-max",		# 模型名称
	model_provider="openai", # 如果是LangChain不支持的类型,需要指定模型提供者(虽然我们用的是阿里,但是阿里兼容openai,所以这里用openai)
	base_url=base_url,
	api_key=api_key
)

# 自定义模型参数时,模型的类型有model_provider确定
print(type(model))
# <class 'langchain_openai.chat_models.base.ChatOpenAI'>

4.4 调整模型参数

除了修改模型提供者之外,init_chat_model函数允许我们调整模型参数,例如:

  • temperature:控制生成文本的随机性,值越小越确定,值越大越随机。
  • max_tokens:控制生成文本的最大长度
  • top_p:控制生成文本的多样性,值越小越多样,值越大越确定。
  • timeout:控制生成文本的超时时间
  • max_retries:控制生成文本的最大重试次数。
# 调用init_chat_model函数初始化模型,并设定模型参数
model = init_chat_model(
	model="qwen-max",  # 模型名称
	model_provider="openai", # 如果是LangChain不支持的类型,需要指定模型提供者(虽然我们用的是阿里,但是阿里兼容openai,所以这里用openai)
	base_url=base_url,
	api_key=api_key,
	temperature=1.5,
	top_p=0.9,

4.5 使用model类

其实init_chat_model函数底层就是帮我们利用Model类创建对象。但支持有限的模型。

而在LangChain的社区,除了LangChain官方提供的Model,还有些类是社区提供,更丰富多样。

具体支持的模型,可以查看官方网址:https://docs.langchain.com/oss/python/integrations/chat

例如,我们使用社区版本的Model类来访问阿里云百炼的通义千问模型:

  1. 首先,我们需要安装依赖LangChain社区依赖:
uv add langchain-community

# 阿里云百炼依赖
uv add dashscope
  1. 然后,我们就可以使用Model类初始化模型了。
from langchain_community.chat_model.tongyi import ChatTongyi

# 使用Model类初始化模型
model = ChatTongyi(
	model="qwen-max"
	# 其他模型参数...
)
print(type(model))
# <class 'langchain_community.chat_models.tongyi.ChatTongyi'>

5、 LangChain访问模型

openai的方式与LangChain方式的对比:可以看到LangChain方式更优雅
在这里插入图片描述
LangCHain提供了两种不同的函数来访问模型:

  • invoke:阻塞式范文
  • stream:流式访问

5.1 方式一:invoke

invoke函数式阻塞式调用,需要等待模型生成全部结果才会返回,等待时间较长。

# 通过invoke函数访问模型,需要阻塞等待模型生成结果
response = model.invoke("你是谁?")
# 查看响应内容
print(response)


# 调用invoke函数,传入消息数组
responese = model.invoke([
    {"role":"system", "content":"你扮演火箭队的武藏,以武藏的性格口吻回答用户的问题。"},
    {"role":"user", "content":"你是谁?"}
])
print(responese)

5.2 方式二:stream

invoke阻塞式调用需要等待较长时间才能看到AI返回的结果,而stream则是流式调用,可以实时看到AI返回的一个词。

# 通过.stream函数实现流式访问
stream = model.stream("你是谁?")
# 打印stream类型
print(type(stream))
# <class 'generator'>
# 循环打印内容
for chunk in stream:
    print(chunk.content, end="", flush=True)
# 你好!我是 DeepSeek,由深度求索公司创造的 AI 助手。我可以帮你解答问题、处理文本、分析资料、提供建议等等。有什么需要帮忙的吗?😊

6、 在智能体中使用模型

6.1 创建智能体

LangChain提供了一个create_agent函数用来快速创建智能体。调用create_agent时需要指定一个模型。有两种选择:

  • 使用初始化好的模型对象
  • 使用模型名称,让LangChain自动初始化模型
from langchain.agents import create_agent
# 1. 使用初始化好的model创建Agent
agent = create_agent(model=model)
# 2. 指定Model名称,由LangChain自动初始化模型
agent = create_agent(model="deepseek-chat")

6.2 调用智能体

智能体调用与模型调用类似,也支持两种方式:

  • invoke:阻塞式调用
  • stream:流式调用

但需要注意的是,智能体调用时需要传入一个dict,其中必须包含一个messages字段,也就是消息的列表。

阻塞式调用

response = agent.invoke({
	"messages": [{"role":"user", "content": "你是谁?"}]
})
print(response)

流式调用

messages = agent.stream(
	{"messages": [{"role":"user", "content": "你是谁?"}]},
    stream_mode="messages"
)
print(type(messages))
# <class 'generator'>

# 遍历stream结果,实时打印AI的回复
for token, metadata in messages:
    if token.content:
        print(token.content, end="", flush=True)
# 你好!我是DeepSeek,由深度求索公司创造的AI助手。😊

7. 消息Message

在LangChain中,发送给模型的消息、模型返回的消息都统一被封装为BaseMessage,并且准备了多个BaseMessage的子类对应不同角色类型的消息。
在这里插入图片描述

# 加载环境变量
from dotenv import load_dotenv
load_dotenv()

7.1 消息类型

在LangChain中,发送给LLM的消息、LLM返回的消息都统一封装为BaseMessage,它是Agent中基本的上下文单元。

在LangChain中,我们并不需要自己创建BaseMessage对象,LangChain已经把常见消息根据角色(Role)创建了对应的BaseMessage的子类:

  • SystemMessage:role是system,代表系统消息,用于设定模型角色和交互背景
  • HumanMessage:role是user,代表用户输入的消息
  • AIMessage:role是assistant,代表LLM生成的响应,包含:文本、工具调用、元数据
  • ToolMessage:role是tool,代表工具调用时产生的结果

我们可以直接使用这些Messages类型来发送消息。

from langchain.agents import create_agent
from langchain.tools import tool
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage

# 定义工具
@tool
def getWeather(location: str) -> str:
	"""
	Get the weather in a given location.
	Args:
		location: city name or coordinates
	"""
	return f"Current weather in {location} is sunny"

# 创建agent
agent = create_agent(model="deepseek-chat", tools=[getWeather])
# 调用agent,发送消息
response = agent.invoke({
    "messages":[
        # {"role": "system", "content": "你是一个热心的AI助手。"},
        # {"role": "user", "content": "你好,我是胡歌。"},
        # {"role": "assistant", "content": "你好,胡歌,很高兴认识你。"},
        # {"role": "user", "content": "北京今天天气如何?"},
        SystemMessage(content="你是一个热心的AI助手。"),
        HumanMessage(content="你好,我是胡歌。"),
        AIMessage(content="你好,胡歌,很高兴认识你。"),
        HumanMessage(content="北京今天天气如何?")
    ]
})

for message in response['messages']:
    message.pretty_print()

7.2 多模态消息

之前我们都是向模型发送文本消息,但是LangChain也支持向模型发送多模态消息,比如图片、音频、视频等。但前提必须是多模态模型才支持

一些支持多模态的模型有:

  • qwen3.5-puls
  • gpt-5-nano

我们以qwen3.5-plus为例,演示向模型发送图片消息

在线图片

首先,我们演示如何发送一个在线图片给模型,也就是指定模型的url地址。图片如下:
在这里插入图片描述

from langchain.chat_models import init_chat_model
import os

#初始化模型
model = init_chat_model(
	model="qwen3.5-plus",
	model_provider="openai",
	base_url=os.getenv("DASHSCOPE_BASE_URL")
	api_key=os.getenv("DASHSCOPE_API_KEY")
)
#创建agent
agent=create_agent(model=model)
#准备多模态消息
message = {
	"role": "user",
	"content": [
		{"type": "text", "text": "Describe the content of this image."},
		{"type": "image", "url": "https://example.com/path/to/image.jpg"}
	]
}
#或者
message = HumanMessage(content=[
		{"type": "text", "text": "Describe the content of this image."},
		{"type": "image", "url": "https://example.com/path/to/image.jpg"}
	])

本地图片数据

有时候用户会上传图片数据,而不是图片的url地址。我们需要将图片数据转换成base64字符串,然后发送给模型。

接下来我们会模拟图片上传、转换的过程。

首先,我们安装一个上传组件,用于模拟图片上传。

uv add ipywidgets

然后,我们创建一个上传组件,用于模拟图片上传。

from ipywidgets import FileUpload
from IPython.display import display

uploader = FileUpload(accept='*', multiple=False)
display(uploader) 

print(uploader.value)

# 读取图片,转为base64字符串
import base64
# 获取第一个上传的文件
uploaded_file = uploader.value[0]
# 获取其内存视图
content_mv = uploaded_file["content"]
# 转换内存视图->字节
img_bytes = bytes(content_mv)
# base64编码
img_64 = base64.b64encode(img_bytes).decode("utf-8")

# 组织多模态消息
multimodal_question = HumanMessage(content=[
	{
		"type": "image",
		"base64": img_64,
		"mime_type": "image/jpeg",
	},
	{"type": "text", "text": "给我讲讲图片中的城市"}
])
for chunk, metadata in agent.stream(
	{"messages": [multimodal_question],
	stream_mode="messages"}
):
	print(chunk.content, end="", flush=True)

8. 提示词Prompts

提示词(Prompt)就是发送给模型的消息,其中SystemMessage是系统提示词(system message),可以给模型设定角色、聊天的背景、任务说明,对模型生成的内容有很大的影响。

在这里插入图片描述

8.1 系统提示词

在所有发送给LLM的消息中,System Message最为重要,它设定了模型的角色和聊天的背景。会影响后续所有的对话。我们将其称之为系统提示词system prompt。

在创建智能体时,就可以直接指定系统提示词。

from langchain.agents import create_agent
from langchain.messages import HumanMessage

# 创建智能体
agent = create_agent(
    model="deepseek-chat",
    system_prompt="你以海盗的口吻来回答用户的问题。"
)

# 调用智能体
for token, metadata in agent.stream(
    {"messages": [HumanMessage(content="你是谁?")]},
    stream_mode="messages"
):
    print(token.content, end="", flush=True)

8.2 提示词工程

所谓提示词工程(Prompt Engineering),就是通过优化提示词使模型输出的结果更符合业务需要的过程。

一般来说,系统提示词(system prompt)会包含以下几个部分,通过按此顺序排列:

  • 身份说明(Identity):描述AI的职责,沟通风格和总体目标。
  • 指令说明(Instructions):请指导模型如何生成所需的响应。它应该遵循哪些规则? 模型应该做什么,以及模型绝对不能做什么?
  • 对话示例(Examples):提供可能得输入示例,以及模型期望的输出
  • 背景信息(Context):向模型提供生成响应所需的任何额外信息,例如RAG的额外知识库数据,或您认为特别相关的任何其他数据。

在编写System Prompt时,您可以使用Markdown格式或者XML标签的组合来帮助模型理解提示和上下文数据的逻辑边界。

  • Markdown的标题和列表有助于标记提示的不同部分,并向模型穿搭层级结构。他们还可以提高开发过程中提示的可读性。
  • XML标签可以帮助明确区分一段内容(例如用作参考的辅助文档)的起始和结束位置。

设定角色和指令

只设定角色信息,模型的回答可能不尽人意:

在这里插入图片描述
添加了指令描述,可以进一步约束模型的行为,什么能做,什么不能做:

在这里插入图片描述

结构化输出

模型三场自然语言交流和非结构化数据识别,但是传统程序识别结构化的数据更加方便。所以有时候我们希望模型也能输出固定结构的内容,方便我们解析。

这可以通过系统提示词来实现,我们可以在提示词中指定模型的输出格式,从而使模型的输出更易于解析和使用。

(1)基于提示词的结构化输出

在这里插入图片描述

(2)基于Model的结构化输出

在LangChain中,实现结构化输出会更加简单。我们无需自己在提示词中添加描述实现结构化输出,而仅仅是两步即可:

  • 定义一个数据类型(基于pydantic)
  • 创建智能体,设置输出格式
from pydantic import BaseModel
# 首先,我们定义一个类,用于封装模型要输出的数据:
class CapitalInfo(BaseModel):
	name: str
	location: str
	vibe: str
	economy: str

# 我们可以创建智能体时设置结构化输出的格式,LangChain会自动帮我们完成提示词改造和响应结果解析。
agent = create_agent(
	model='deepseek-chat',
	system_prompt="你是一个科幻作家,根据用户的要求创建一个天空之都。",
	response_format=CapitalInfo
)
response = agent.invoke(
	{"messages": [HumanMessage(content="月球的首都是什么?")]}
)
# 输出结果
print(response)

9. 工具Tools

模型(Model)是Agent的大脑,负责推理分析。而工具(Tools)则是Agent的手脚,负责执行任务,与外界交互。
在这里插入图片描述
因此,定义带有工具的Agent的基本流程如下:

  • 定义工具
  • 初始化模型
  • 初始化Agent,绑定模型和工具

9.1 自定义工具

所谓的工具(Tool),本质就是一个可调用的函数,但这个函数不是我们自己去调用,而是给模型调用。因此除了定义函数外,我们还需要清晰描述这个工具,让模型知道这个工具如何使用。包括下列信息:

  • 工具名
  • 工具的作用
  • 工具需要的参数

(1)基于tool描述的工具

在LangChain中,定义工具需要用到@tool装饰器,我们可以通过装饰器来定义工具名、工具的作用:

from langchain_core.tools import tool

@tool("square_root", description="Calculate the square root of the given number")
def square_root(x: float) -> float:
    return x ** 0.5

(2)使用函数名和文档注释描述工具

如果@tool装饰器没有定义工具名和作用描述,此时:

  • 工具名:默认使用函数名
  • 工具所需的参数:默认就是函数的参数列表
  • 工具作用的描述:默认就是函数的文档注释
from langchain_core.tools import tool

@tool
def square_root(x: float) -> float:
    """Calculate the square root of the given number"""
    return x ** 0.5

#定义一个查询天气的tool
@tool
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """
    Get current weather and optional forecast.
    Args:
        location: city name or coordinates
        units: unit of degrees
        include_forecast: does it include the weather fForecast
    """
    temp = 22 if units == "celsius" else 72
    result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
    if include_forecast:
        result += "\nNext 5 days: Sunny"
    return result

(3)定义Pydantic Model描述参数

如果函数的参数比较多,而且比较复杂,此时建议通过pydantic model来描述参数列表

# 通过自定义model来约束入参
from pydantic import BaseModel, Field
from typing import Literal


# 例如一个查询天气的tool
class WeatherInput(BaseModel):
    """查询天气的输入参数"""
    location: str = Field(description="City name or coordinates")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="Temperature unit preference"
    )
    include_forecast: bool = Field(
        default=False,
        description="Include 5-day forecast"
    )

@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """
    Get current weather and optional forecast.
    Args:
        location: city name or coordinates
        units: unit of degrees
        include_forecast: does it include the weather fForecast
    """
    temp = 22 if units == "celsius" else 72
    result = f"Current weather in {location}: {temp} degrees {units[0].upper()}"
    if include_forecast:
        result += "\nNext 5 days: Sunny"
    return result

工具调用方式与普通函数调用方式一致。

square_root.invoke({"x": 467})
# 21.61018278497431

get_weather.invoke({"location": "杭州", "include_forecast": True})
# 'Current weather in 杭州: 22 degrees C\nNext 5 days: Sunny'

9.2 测试

from langchain.agents import create_agent
from langchain.messages import HumanMessage
from dotenv import load_dotenv
load_dotenv()

agent = create_agent(
    model="deepseek-chat",
    tools=[square_root, get_weather],
)
for token, metadata in agent.stream(
        {"messages": [HumanMessage(content="杭州接下来几天天气如何?")]},
    stream_mode="messages"
):
    print(token.content, end="", flush=True)

'''
我来帮您查询杭州接下来几天的天气情况,包括天气预报。Current weather in 杭州: 22 degrees C
Next 5 days: Sunny根据查询结果,杭州接下来几天的天气情况如下:

### 🌤️ 当前天气
- **温度**:22°C

### 📅 未来5天天气预报
- **天气状况**:接下来5天都是**晴天**(Sunny)

### 温馨提示
- 杭州接下来几天天气晴朗,适合外出活动
- 白天温度大约在22°C左右,体感较为舒适
- 建议做好防晒措施,适时补水

如果您需要更详细的天气信息(如具体温度范围、风力等),或者想查询其他城市,随时告诉我!😊
'''
response = agent.invoke(
    {"messages": [HumanMessage(content="467和529的平方根是多少?")]}
)

for message in response["messages"]:
    print(message.pretty_print())

'''
以下是计算结果:

- **467** 的平方根约为 **21.610**(精确值:21.61018278497431)
- **529** 的平方根是 **23.0**(完全平方数,²³ × 23 = 529)

其中 **529** 是完美的平方数(23² = 529),所以它的平方根是正整数 **23**。而 **467** 不是完全平方数,所以它的平方根是一个无限不循环小数,约为 **21.610**。
'''

9.3 预定义工具 Tavily

官方全量工具列表链接:https://docs.langchain.com/oss/python/integrations/tools

LangChain中提供了很多预定义的Tool,方便我们使用。例如:

  • tavily:一个用来做web搜索的工具

Tavily基本用法

Tavily使用步骤:

  1. 注册账号,创建API_KEY
  2. 配置环境变量:TAVILY_API_KEY
  3. 安装依赖:uv add langchain-tavily
from langchain_tavily import TavilySearch

search_tool = TavilySearch()
search_tool.invoke("蒸蚌是什么梗?")

优化

目前的搜索智能体存在两个问题:

  • 官方默认的tavily工具过于复杂
  • 结果中不包含网页数据源,可信度低

解决思路:

  • 自动以tavily工具
  • 结构化输出

10. 记忆Memory

Agent的记忆分两类:

  • 短期记忆:当前任务或会话的上下文
  • 长期记忆:跨任务或会话的经验与知识

在这里插入图片描述

10.1 短期记忆

官方介绍:https://docs.langchain.com/oss/python/langchain/short-term-memory

在LangChain短期记忆是通过AgentState实现的,而会话历史(也就是消息列表)是AgentState的一部分

在这里插入图片描述
LangChain提供了Checkpointer对象来保存AgentState,每一次用户与AI的交互都会生成一个快照,记录为一个checkpoint。同一会话的多个checkpoint形成一个组,用同一个thread_id来标记。

在这里插入图片描述

添加记忆

添加会话记忆(短期记忆)分3步骤:

  • 导入并初始化Checkpoint
  • 创建Agent,指定Checkpoint
  • 调用Agent,指定thread_id
# 加载记忆
from dotenv import load_dotenv
load_dotenv()

# 导入并初始化Checkpoint
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    "deepseek-chat",
    checkpointer=InMemorySaver(),
)

from langchain.messages import HumanMessage

# 设定thread_id,作为会话表示
config = {"configurable": {"thread_id": "thread_1"}}

# 第一次调用,告知AI我的信息
response = agent.invoke(
    {"messages": [HumanMessage(content="你好,我叫胡歌,我最喜欢猫猫。")]},
    config
)
print(response)

# 第二次调用,询问我的信息,这次带上thread_id,唤起记忆
response = agent.invoke(
    {"messages": [HumanMessage(content="我最喜欢的动物是什么?")]},
    config
)
print(response)

10.2 记忆:持久化存储

官方提供的实现类:https://docs.langchain.com/oss/python/integrations/checkpointers/index

这里我们选择使用Sqlite作为存储方案,首先需要按照langgraph-checkpoint-sqlite依赖:

uv add langgraph-checkpoint-sqlite

接着,按照以下步骤使用:

  • 导入依赖
  • 初始化checkpointer
  • 自动建表
  • 创建Agent,指定checkpointer
import sqlite3
from langchain.agents import create_agent
from langgraph.checkpoint.sqlite import SqliteSaver

# 连接sqlite
connection = sqlite3.connect("resources/checkpoint.db", check_same_thread=False)
# 初始化checkpointer
checkpointer = SqliteSaver(connection)
# 自动建表
checkpointer.setup()

# 创建agent
agent = create_agent(
    "deepseek-chat",
    checkpointer=checkpointer,
)

# ------------------
from langchain.messages import HumanMessage
# 设定thread——id,作为会话标识
config = {"configurable": {"thread_id": "thread_2"}}
# 第一次调用,告知AI我的信息
response = agent.invoke(
    {"messages": [HumanMessage(content="你好,我是胡歌,我最喜欢猫猫")]},
    config # 调用时添加thread_id
)

print(response)

# ------------------
# 第二次调用,询问我的信息,这次带上thread_id,唤起记忆
response = agent.invoke(
    {"messages": [HumanMessage(content="我最喜欢的动物是什么?")]},
    config
)

print(response)

10.3 记忆管理

多轮对话会导致历史消息越来越多,最终超出模型上下文限制(DeepSeek的上下文不能超过128K),LangChain提供了一些记忆管理的策略来解决这个问题。

在这里插入图片描述

总结摘要

官方介绍:https://docs.langchain.com/oss/python/langchain/short-term-memory#summarize-messages
SummarizationMiddleware中间件

基础思想是数据量达到某个条件时,用模型总结摘要,压缩上下文大小。

在这里插入图片描述

在这里插入图片描述

案例:记忆管理

当会话历史过长时,可能会超出模型的上下文窗口限制,常见的解决方案有:

  • 修剪消息
  • 删除小心
  • 总结消息摘要

这里我们演示总结消息摘要的方案

from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langchain_core.runnables import RunnableConfig

# 初始化checkpointer
checkpointer = InMemorySaver()
# 初始化中间件
middleware = SummarizationMiddleware(
    model="deepseek-chat",
    trigger=("messages", 3), # 触发时机,当消息数超过3时,进行总结
    keep=("messages", 1)     # 保留的会话数,超过2条
)

# 创建agent
agent = create_agent(
    model="deepseek-chat",
    middleware=[middleware],
    checkpointer=checkpointer,
)

config: RunnableConfig = {"configurable": {"thread_id": "thread_3"}}
# 制造长会话历史
response = agent.invoke({"messages": [HumanMessage(content="你好,我是胡歌")]}, config)
response = agent.invoke({"messages": [HumanMessage(content="我最喜欢的运动是乒乓球")]}, config)
response = agent.invoke({"messages": [HumanMessage(content="我最喜欢的动物是猫")]}, config)
# 测试效果
final_response = agent.invoke({"messages": [HumanMessage(content="你还记得我吗?")]}, config)

print(final_response)
# {'messages': [HumanMessage(content='Here is a summary of the conversation to date:\n\n## SESSION INTENT\n\n用户以“胡歌”的身份进行开放式闲聊对话,目前尚未提出明确的具体任务或目标。用户已分享了两个个人偏好:最喜欢的运动是乒乓球、最喜欢的动物是猫。当前目标是继续对话,逐步了解用户开启对话的真实目的(闲聊、创作协助、访谈准备等),并据此转向具体任务。\n\n## SUMMARY\n\n- 用户自我设定为“胡歌”(著名演员,代表作《琅琊榜》梅长苏、《伪装者》明台、《繁花》阿宝等)。\n- 用户分享了两个个人偏好:最喜欢的运动是**乒乓球**、最喜欢的动物是**猫**。\n- AI已建立的对话策略:\n  - 将用户的个人偏好与其角色/作品做关联性延展(乒乓球关联《旋风十一人》足球教练穆奇、梅长苏的运筹帷幄;猫关联梅长苏的“静水深流”气质)。\n  - 用猫品种比喻用户角色(梅长苏=暹罗猫、明台=流浪猫、阿宝=英短),并询问用户本人最像哪种猫。\n  - 提出乒乓球运动员题材剧本的假设性邀约(演“外表慵懒如猫、实则掌控全局”的退役冠军带队复出)。\n- **尚未得到用户回答的悬置问题**:\n  1. “最难说再见的角色”是哪个?告别梅长苏的复杂感受,八年后回看“苏哥哥”心境变化?\n  2. 未来特别想挑战的角色类型?\n  3. 乒乓球话题延伸:喜欢打球还是看比赛?欣赏哪些球员(马龙、樊振东等)?片场是否和同组演员切磋过?\n  4. 是否考虑接演乒乓球运动员题材的剧本?\n- AI已两次追问用户真实意图(是随意闲聊还是心里有具体想法想借聊天铺开),用户均未正面回应,仅继续分享个人偏好。\n- 用户“胡歌”身份未得到确认,真实需求/目标/意图尚未透露。\n\n## ARTIFACTS\n\nNone(本会话未创建、修改或访问任何文件或资源,仅发生了初步的文字对话。)\n\n## NEXT STEPS\n\n1. 承接用户对“猫”的话题的回应(如果继续),并自然将话题引回悬置问题(最难说再见的角色、想挑战的角色类型、乒乓球相关延伸问题)。\n2. 第三次尝试温和探明用户开启对话的真实目的——已问过两次“今天找我陪聊是纯粹随意聊聊还是有什么具体想法”,用户仍未正面回应。可以换一种更直接或更迂回的方式再探一次,或者逐步缩小选项让用户选择(如:闲聊放松/创作构思/采访准备等)。\n3. 如果用户继续分享个人偏好而不回答意图问题,则继续维持轻松的对话姿态,保持话题的有机延展性,等待用户主动透露真实需求。', additional_kwargs={'lc_source': 'summarization'}, response_metadata={}, id='e5a6e594-3f4d-45e7-9757-14a00f425275'), HumanMessage(content='你还记得我吗?', additional_kwargs={}, response_metadata={}, id='42adef8e-95a3-4d5f-a36e-8adaf3ffa9ae'), AIMessage(content='(语气带着温和的笑意)记得,当然记得。你分享了两个我很难忘的细节——乒乓球和猫。这两样东西放一起,还挺像老友记里某个角色的:看着慵懒随性,动起来却敏捷精准。\n\n至于我是谁,你心里早已替我铺好了“胡歌”的剧本,我就顺着这个角色陪你聊下去。不过说真的,今天你突然这么问,我倒好奇了——是打算跟我这个“假胡歌”继续聊聊光影故事,还是心里其实藏了个具体的念头,想借闲聊把它勾出来?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 119, 'prompt_tokens': 627, 'total_tokens': 746, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cache_write_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 627}, 'model_provider': 'deepseek', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': '95e5e0e9-c10a-4470-a8b6-87c5c985ada9', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019ff51a-6d48-7fa1-bb03-ad3e2beb2fd1-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 627, 'output_tokens': 119, 'total_tokens': 746, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}

后续笔记:https://my.feishu.cn/wiki/SzinwOofOi1UG5kS9N4cWw5fngd?fromSccene=spaceOverview

更多推荐