1 Agent
"Agents"(在AI语境下通常指 AI Agent,即"人工智能体")并非一个单一的技术,而是一种以目标为导向、能够自主规划并执行多步骤任务的AI系统。它标志着AI从"被动回答问题的工具"向"主动完成任务的伙伴"的范式转变。
1.1🧠 核心能力:它如何"自主"工作?
一个现代AI Agent的"自主性",建立在一套协同工作的核心能力之上,通常被概括为"感知-决策-行动-记忆"的闭环。
推理与规划:面对一个模糊的最终目标(如"策划一场产品发布会"),Agent能将其拆解为市场调研、场地预订、物料设计等一系列有序的子任务,并制定执行计划。
工具调用:这是Agent与真实世界交互的"手脚"。它能调用API、数据库、软件等外部工具来执行具体操作,比如查询库存、发送邮件或预订会议室。
记忆与情境:Agent拥有短期记忆(记住当前任务的中间结果和对话上下文)和长期记忆(存储用户偏好和历史经验),确保行动连贯,并能从过往中学习优化。
迭代执行:Agent在一个 "推理→行动→观察结果→再推理"的循环中工作。它会根据每一步的实际反馈动态调整后续步骤,而不是死板地执行预设脚本,直到目标达成。
1.2 关键区别:Agent vs. 聊天机器人
理解Agent的关键,在于看清它与我们熟悉的聊天机器人(如传统ChatGPT)的本质不同。
| 维度 | 传统聊天机器人 | AI Agent |
|---|---|---|
| 交互模式 | 被动响应:根据单次指令生成回复,需要用户一步步引导。 | 主动执行:接收一个最终目标,自主完成全过程,无需逐步干预。 |
| 任务范围 | 单点问答:擅长回答孤立的问题。 | 多步骤工作流:能处理"查询余额→发现异常→提醒用户"这样的连贯任务链。 |
| 工作逻辑 | 遵循预设的对话路径或规则。 | 基于AI进行动态规划与决策,能处理开放式请求和突发情况。 |
| 类比 | 像需要详细指导的实习生。 | 像能独当一面的专业负责人,你只需告知目标。 |
通用人工智能(AGI)将是 AI 的终极形态,几乎已成为业界共识。同样,构建 Agent则是 AI 工程应用当下的"终极形态"。
1.3 它被用来做什么?------从"对话"到"实干"
AI Agent的价值在于闭环执行能力,即不只是"说",而是能"做完"一件事。其应用正从实验性工具快速渗透到核心工作流中。
主要应用领域包括:
-
企业与生产力:自动处理客服工单、IT运维、代码生成。例如,在软件研发中,Agent辅助生成的代码占比可超过30%;在客服场景,标准咨询的自动处理率能超过70%。
-
个人事务管理:作为个人助理,自动规划行程、预订机票酒店、管理财务账单,甚至发现并提醒用户遗忘的自动续费订阅。
-
垂直行业深化:在工业制造领域,用于预测性维护和生成式设计;在金融合规领域,辅助进行材料预审和风险监控。
-
多智能体协作(MAS):面对复杂任务,由多个具备不同专长的Agent分工协作,例如一个负责市场分析,另一个负责撰写报告,共同完成一个商业项目。
1.4 Agent
将 AI 和人类协作的程度类比自动驾驶的不同阶段:

语言模型本身无法采取行动------它们只是输出文本。LangChain 的一个重要功能是创建Agent。Agent 是一种使用 LLM 作为推理引擎的系统,它决定要采取哪些行动以及这些行动的输入应该是什么。这些行动的结果可以反馈给 Agent,由 Agent 决定是否需要采取更多行动,或者是否可以完成。
与传统的固定流程链不同,Agent 具备一定的自主决策能力,更适合处理开放式、多步骤的问题。它可以拆解任务,根据任务动态决定调用哪些工具,并利用中间结果推进任务。
Agent 的核心能力/组件:
- 大模型(LLM):作为大脑,提供推理、规划和知识理解能力。
- 记忆(Memory):具备短期记忆和长期记忆,支持快速知识检索。
- 工具(Tools):调用外部工具(如API、数据库)的执行单元。
- 规划(Planning):任务分解、反思与自省框架实现复杂任务处理。
- 行动(Action):实际执行决策的能力。
- 协作:通过与其他 Agent 交互合作,完成更复杂的任务目标。

2 Agent 构建
LangChain 当中提供了一个功能函数:create_agent,可以快速构建一个Agent。create_agent函数所需要传递的参数如下所示:
| 名称 | 是否必传 | 描述 |
|---|---|---|
| model | 是 | 模型实例 |
| system_prompt | 否 | 系统提示词 |
| tools | 否 | 工具列表 |
| response_format | 否 | 结构化输出的schema信息 |
| checkpointer | 否 | 用以保存状态的实例,可用以实现短期记忆 |
| middleware | 否 | 要应用于代理的一系列中间件实例。中间件可以在各个阶段拦截并修改代理的行为 |
通过传递相关参数,即可构建一个复杂的Agent实例,接下来将详细讲解其中的tools, checkpointer和middleware参数。
3 Agent 添加本地工具
Agent 添加工具,本质上是使用大模型服务的 function call 能力,将所有的工具封装成 json schema 信息,在调用大模型时,传入所有的 json schema,大模型在回答时,能够给出所需要调用的参数名字和入参信息。
OpenAI中关于function call的文档链接:https://platform.openai.com/docs/guides/function-calling?api-mode=chat。
3.1 OpenAI SDK调用添加工具 schema
以下代码演示了如何通过 OpenAI 的原生 SDK 来进行工具调用:
python
from openai import OpenAI
import json
# 初始化OpenAI客户端
client = OpenAI()
# ========== 1. 通过JSON结构定义工具(Function Calling) ==========
# tools数组:告诉大模型它可以调用哪些外部工具、工具是干嘛的、需要什么入参
tools = [
{
"type": "function",
"function": {
"name": "get_weather", # 工具函数名称,模型要靠这个名字匹配本地函数
"description": "Get today's weather for a location.", # 给模型看的工具描述,让模型知道什么时候调用它
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称, e.g. San Francisco",
},
"date" :{
"type":"string",
"description":"想要查询的天气的日期, e.g. 2023-12-25"
}
},
"required": ["city","date"], # 标记必填参数,模型必须输出这两个字段
"additionalProperties": False, # 不允许模型额外生成不在定义里的参数
},
"strict": True, # 严格模式:强制输出JSON完全符合schema,减少格式错乱
},
},
]
# 本地实现的天气查询工具函数(真实场景这里对接天气API)
def get_weather(city,date):
return f"{city} on {date} is cloudy with a chance of rain."
# 消息列表:整个对话上下文,会不断追加消息
messages = [
{"role": "user", "content": "What is the weather like in 北京 on 2024-12-25?"}
]
# ========== 2. 第一次请求模型:带上工具定义,让模型判断是否需要调用工具 ==========
response = client.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=tools, # 传入工具清单
)
# 把模型返回的这条消息(tool_call)加入对话上下文,保证上下文连贯
messages.append(response.choices[0].message)
# 遍历模型发起的工具调用请求(一次返回可能多个tool_call)
for tool_call in response.choices[0].message.tool_calls or []:
if tool_call.function.name == "get_weather":
# ========== 3. 解析模型输出的参数,执行本地工具逻辑 ==========
args = json.loads(tool_call.function.arguments) # 把模型输出的参数字符串转字典
weather = get_weather(args["city"],args["date"])
# ========== 4. 将工具返回结果包装成tool消息,追加进消息列表 ==========
# role="tool" 是固定角色;tool_call_id用来和前面模型的调用请求一一对应
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps({"weather": weather}),
}
)
# ========== 第二次请求模型:此时上下文已经包含工具返回结果 ==========
# 模型拿到工具结果后,整理信息,生成自然语言最终回答
response = client.chat.completions.create(
model="gpt-4.1",
messages=messages,
tools=tools,
)
# ========== 5. 输出模型整理后的最终自然语言回复 ==========
print(response.choices[0].message.content)
3.2 通过 LangChain 定义工具
-- 通过原生的SDK定义工具时需要使用 json 来描述工具,比较繁琐。langchain.tools模块下提供了 tool 装饰器,可以通过 在函数上使用 @tool 的方式,就可以将一个函数创建成工具。
-- 需要注意的是:定义函数时,需要通过使用 @tool 装饰器内传参:description 或者是定义函数的文档字符串的方式,来添加函数的解释说明。
-- 而对于参数的说明,没有对于参数描述的强制限制,不过也可以通过 pydantic 的 BaseModel 来定义一个入参的具体描述信息,传递到 tool 装饰内。
-- 通过 LangChain 定义好工具之后,调用 llm.bind_tools() 方法,得到一个新的 llm 对象,后续在调用新的 llm 对象时,就可以使用相关的工具
示例代码如下:
python
from langchain.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
import logging
# 开启DEBUG日志,可以看到LangChain内部详细执行过程
logging.basicConfig(level=logging.DEBUG)
# 可选:通过BaseModel详细定义工具的参数(Pydantic做参数校验、自动生成schema)
from pydantic import BaseModel, Field
class GetWeatherArgs(BaseModel):
city: str = Field(description="城市名称")
date: str = Field(description="日期,格式为YYYY-MM-DD")
@tool
def get_weather(city, date) -> str:
"""获取指定城市在指定日期的天气"""
# 模拟天气接口返回,真实场景对接第三方天气API
return f"{city} 在 {date} 天气多云,有下雨的可能性"
from langchain_openai import ChatOpenAI
# 初始化OpenAI大模型
llm = ChatOpenAI(model="gpt-4.1")
# 将工具绑定到LLM:告诉模型可用的工具、自动生成Function Calling的schema
llm = llm.bind_tools([get_weather])
# 消息列表:LangChain消息对象,区分HumanMessage/AIMessage/ToolMessage
message_list = [
HumanMessage(content="北京2024-12-25的天气")
]
# 第一轮调用模型:模型判断需要调用工具,返回tool_calls,不直接给答案
res = llm.invoke(message_list)
message_list.append(res)
# 当前结果当中包含调用工具的出参
print('LLM的首次回复:', res)
# 解析模型输出的工具调用信息,手动执行工具
tool_call = res.tool_calls[0]
args = tool_call['args'] # 模型提取出来的入参 {"city":"北京","date":"2024-12-25"}
call_tool_res = get_weather.invoke(args) # 使用langchain tool自带的invoke方法执行函数
id = tool_call['id'] # 工具调用唯一id,用来关联本次tool call
# 构造ToolMessage,这是LangChain规定的工具返回消息类型
tool_message = ToolMessage(tool_call_id=id, content=call_tool_res)
message_list.append(tool_message)
# 第二轮调用模型:传入包含工具结果的完整上下文,模型整理结果生成自然语言回答
res = llm.invoke(message_list)
print('基于工具调用返回信息后,LLM的回复:', res)
3.3构建agent时添加本地工具
了解了如何定义tool之后,就可以使用 create_agent来创建 一个可以调用工具的Agent了。

这里使用 Tavily (搜索引擎)作为工具,需要先获取它的 API-Key 并添加到环境变量。
python
# pip install langchain-tavily
import os
from langchain_tavily import TavilySearch
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
# 定义大语言模型
llm = init_chat_model(
model="gpt-4o-mini",
model_provider="openai",
)
# 定义 Tavily 联网搜索工具
# Tavily是专门给AI Agent使用的搜索引擎,可以实时联网获取互联网信息
search = TavilySearch(max_results=5) # max_results:每次搜索最多返回5条搜索结果
tools = [search] # 把工具放进工具列表,供给Agent使用
# 创建Agent
agent = create_agent(
model=llm, # 绑定使用的大模型
tools=tools, # 绑定可用工具(这里只有联网搜索)
system_prompt="你是位助手,需要调用工具来帮助用户。", # 给Agent的系统提示,规定Agent行为
)
# 调用Agent执行任务
res = agent.invoke(
{"messages": [{"role": "user", "content": "今天北京的天气怎么样?"}]}
)
print(res)
如果 Agent 执行多个步骤,这可能需要一些时间。为了显示中间进度,我们可以使用 stream 流式返回消息。
python
# pip install langchain-tavily
import os
from langchain_tavily import TavilySearch
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
# 初始化大模型,gpt-4o-mini,使用OpenAI接口
llm = init_chat_model(
model="gpt-4o-mini",
model_provider="openai",
)
# 实例化 Tavily 联网搜索工具(专为Agent设计的AI搜索引擎)
# max_results=5:每次搜索最多返回5条摘要结果
search = TavilySearch(max_results=5)
tools = [search]
# 创建Agent,内置自动工具调用循环逻辑,无需手动处理tool_call
agent = create_agent(model=llm, tools=tools)
# 使用 stream() 流式执行Agent,逐步返回过程块(chunk)
# stream 可以看到Agent完整思考链路:思考→调用工具→拿到结果→生成回答
for chunk in agent.stream(
{
"messages": [
{"role": "system", "content": "你是位助手,需要调用工具来帮助用户。"},
{"role": "user", "content": "今天北京的天气怎么样?"},
]
}
):
# 逐块打印每一步输出,查看Agent执行中间过程
print(chunk, end="\n\n")
-- 要想更加精确地看到每个步骤的耗时,以及输入输出等信息,可以通过 LangSmith 来实现。LangSmith 是一个用于调试、测试、评估和监控 LLM 应用程序(特别是基于 LangChain 和 LangGraph 构建的应用)的全流程开发平台,旨在帮助开发者将原型转化为生产级应用。
-- 使用 LangSmith 非常简单,只需要配置如下两个环境变量,不需要改动任何代码,即可将整个调用过程在 LangSmit 当中展示,其中 API_KEY 可在 langsmith 官网进行配置:
python
LANGSMITH_TRACING="true"
LANGSMITH_API_KEY="..."
-- 配置好环境变量之后,可在 LangSmith 的 Tracing Projects 中查看跟踪记录。
-- LangSmith 默认将跟踪记录到 default 项目,可通过 LANGSMITH_PROJECT 环境变量设置 LangSmith 跟踪记录保存到哪个项目,如果该项目不存在则会创建。
4 Agent 添加 MCP 工具
4.1 MCP 介绍
Model Context Protocol(MCP,模型上下文协议)是一个开源协议,它标准化了大语言模型与外部工具和数据源通信的方式,允许开发者和工具提供商只需集成一次,就能与任何兼容 MCP 的系统交互。MCP 就像 USB-C 标准:不需要为每个设备使用不同的连接器,而是使用一个端口来处理多种类型的连接。


4.2 MCP 架构
MCP 遵循客户端-服务器架构,架构中包括:
| MCP 主机 | 协调和管理一个或多个 MCP 客户端的 AI 应用 |
|---|---|
| MCP 客户端 | 一个保持与 MCP 服务器连接的组件,通过 MCP 定义的消息处理通信,从服务器查找并请求资源和工具,并管理与服务器的连接生命周期 |
| MCP 服务器 | 一个向 MCP 客户端提供服务的程序,通过协议暴露工具、资源和提示模板功能 |

4.3 MCP 数据传输协议
MCP 客户端和服务端之间的数据传输机制,包括 Stdio、Streamable HTTP、SSE。
| Stdio | 使用标准输入和输出流,与在终端输入命令并看到响应时使用的机制相同。适用于本地开发,例如本地文件服务器等 |
| Streamable HTTP | 该传输使用 HTTP POST 和 GET 请求,服务器可以选择使用SSE来流式传输多个服务器消息。支持流式传输和服务器到客户端通知,并支持标准 HTTP 身份验证方法,包括授权令牌、API 密钥和自定义头信息 |
| SSE | 带有 SSE(Server-Sent Events 服务器发送事件)的 HTTP,MCP早期传输机制,现逐渐被 Streamable HTTP 取代 |
4.4 MCP工作流程
MCP的工作流程如下所示:
- (1)握手与能力声明(Handshake & Discovery):当AI应用程序(Host)启动时,它会根据配置启动 MCP Server(通常是一个子进程)。Host 记住了这些工具的名字和用法说明,但此时 并没有执行任何代码 。
- (2)用户提问与上下文注入(Context Injection):User提出问题后,Host 把用户的这个问题,加上刚才 Server 汇报的 工具说明书 ,一起打包发给 LLM。
- (3)模型决策(LLM Reasoning):LLM决定是否需要去调用相关工具。
- (4)路由与执行(Routing & Execution):Host 收到指令,发现是要调用 MCP 工具,Host 通过 MCP 协议(JSON-RPC)给 MCP Server 发消息,Server 收到请求, 在它自己的进程(如果是StreamableHttp方式,则是在远端执行)里执行相关操作。 所有的驱动依赖、复杂逻辑都在 Server 端。
- (5)结果回传(Result Feedback):Server 拿到结果,打包成 MCP 响应发回给 Host,Host再将相关结果,传递给大模型,让大模型做下一步的输出。
4.5 MCP SDK
4.5.1 Stdio
以下是使用MCP进行stdio模式下的服务端编码过程:
python
# pip install mcp
from mcp.server.fastmcp import FastMCP
# 创建 FastMCP 服务实例,服务名称为 Demo
# MCP:Model Context Protocol,模型上下文协议,用来标准化给大模型提供工具、资源、提示词
mcp = FastMCP("Demo")
# ========== 注册 MCP 工具(Tool) ==========
# @mcp.tool() 装饰器:把这个函数暴露成MCP工具,供支持MCP的客户端(Claude Desktop等)调用
@mcp.tool()
def add(a: int, b: int) -> int:
"""加法工具:接收两个整数,返回相加结果"""
return a + b
# ========== 注册 MCP 资源(Resource) ==========
# @mcp.resource() 装饰器:注册资源,使用URI标识 greeting://default
# 资源是可供模型读取的静态/动态内容(文档、配置、常量文本等,只读)
@mcp.resource("greeting://default")
def get_greeting() -> str:
"""获取一条问候资源文本"""
return "Hello from static resource!"
# ========== 注册 MCP 提示词模板(Prompt) ==========
# @mcp.prompt() 装饰器:注册可复用的Prompt模板,客户端可以传参渲染提示词
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
"""生成问候提示词模板,支持不同风格"""
styles = {
"friendly": "写一句友善的问候",
"formal": "写一句正式的问候",
"casual": "写一句轻松的问候",
}
return f"为{name}{styles.get(style, styles['friendly'])}"
if __name__ == "__main__":
# 启动MCP服务
# transport="stdio":使用标准输入输出通信(最常用,适合Claude Desktop本地对接)
# 还有其他传输方式:http、websocket等
mcp.run(transport="stdio")
stdio客户端使用MCP的流程为:
- 通过stdio_client启动进程:构建stdio启动参数
- 建立会话:通过ClientSession构建会话session
- 握手初始化:调用session.initialize()
- 获取能力: 通过session.list_tools() / session.call_tools() 等获取或者使用MCP能力
示例代码如下所示:
python
# pip install mcp
import asyncio
from mcp.client.stdio import stdio_client
from mcp import ClientSession, StdioServerParameters
async def stdio_run():
"""
MCP 客户端示例
通过 stdio 方式启动并连接上面我们写的 MCP Server(mcp_server_stdio.py)
依次演示:查看工具列表、调用工具、读取资源、加载提示词模板
"""
# 服务端进程启动参数
server_params = StdioServerParameters(
# python解释器路径,使用虚拟环境内的python
command=r"D:\PycharmProjects\LangGraph_demo\.venv\Scripts\python.exe",
# 启动服务端脚本:就是刚才写的MCP服务文件
args=[r"./mcp_server_stdio.py"],
)
# 启动子进程,建立stdio管道通信,得到读写流 read / write
async with stdio_client(server_params) as (read, write):
# 创建MCP会话,会话是客户端和服务端交互的载体
async with ClientSession(read, write) as session:
# 和MCP服务端完成握手、初始化连接,必须先执行
await session.initialize()
# ========== 查询服务端暴露的所有工具 ==========
tools = await session.list_tools()
print("=== 可用工具列表 ===")
print(tools)
print()
# ========== 调用 add 加法工具,传入参数 a=1,b=2 ==========
call_res = await session.call_tool("add", {"a": 1, "b": 2})
print("=== 调用 add 工具返回结果 ===")
print(call_res)
print()
# ========== 查询服务端暴露的所有资源 ==========
resources = await session.list_resources()
print("=== 可用资源列表 ===")
print(resources)
print()
# ========== 读取指定URI的资源 greeting://default ==========
read_res = await session.read_resource("greeting://default")
print("=== 读取 greeting 资源结果 ===")
print(read_res)
print()
# ========== 查询服务端暴露的所有提示词模板 ==========
prompts = await session.list_prompts()
print("=== 可用Prompt模板列表 ===")
print(prompts)
print()
# ========== 获取渲染后的提示词模板,传入参数 name=Jack ==========
get_res = await session.get_prompt("greet_user", {"name": "Jack"})
print("=== 获取 greet_user 提示模板结果 ===")
print(get_res)
print()
# 运行异步主函数
asyncio.run(stdio_run())
4.5.2 StreamableHttp
构建 StreamableHttp 服务端 和 stdio服务端类似,只是需要将服务端运行起来。运行的方式同样是通过 mcp.run,并设置 transport 为 streamable-http 即可:
python
# 安装依赖命令:pip install mcp
# mcp 是 Model Context Protocol 官方SDK,FastMCP 是快速开发服务端的封装
from mcp.server.fastmcp import FastMCP
# 创建一个 MCP 服务实例,名字叫 Demo(用于客户端识别这个服务)
mcp = FastMCP("Demo")
# -------------------------- 工具 Tool:可供大模型调用的函数(可执行逻辑) --------------------------
# 使用 @mcp.tool() 装饰器,把函数注册成 MCP 工具,大模型可以调用这个函数、传参、拿到返回结果
@mcp.tool()
def add(a: int, b: int) -> int:
"""加法工具:接收两个整数,返回相加结果"""
return a + b
# -------------------------- 资源 Resource:只读数据源(不是执行函数,读取静态/动态内容) --------------------------
# @mcp.resource(uri) 注册资源,uri 是资源唯一标识,格式自定义 greeting://xxx
# 客户端可以通过这个 URI 读取资源内容,适合读取配置、文档、静态文本
@mcp.resource("greeting://default")
def get_greeting() -> str:
"""获取默认问候资源"""
return "Hello from static resource!"
# -------------------------- 提示模板 Prompt:预定义提示词模板,支持参数渲染 --------------------------
# @mcp.prompt() 注册提示模板,客户端可以传入参数渲染出完整 prompt 交给大模型
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
"""生成问候提示词模板
:param name: 要问候的人名
:param style: 风格,可选 friendly / formal / casual
"""
styles = {
"friendly": "写一句友善的问候",
"formal": "写一句正式的问候",
"casual": "写一句轻松的问候",
}
# 根据传入的 style 匹配文案,拼接成最终提示词返回
return f"为{name}{styles.get(style, styles['friendly'])}"
# 程序入口
if __name__ == "__main__":
# mcp.settings.host = "0.0.0.0" # 放开注释则监听所有网卡,允许局域网其他机器访问
# mcp.settings.port = 8888 # 放开注释可以自定义端口
# 启动服务,transport 选择 streamable-http(HTTP流式传输,适合远程客户端连接)
# 默认地址:http://127.0.0.1:8000
mcp.run(transport="streamable-http")
简单总结三者区别
- @tool :动作、计算、操作,会执行代码,比如加法、查数据库、调用 API
- @resource :读取数据,只读,类似文件 / 文档,通过 URI 寻址
- @prompt:提示词模板,负责拼接 prompt 文本,交给 LLM 去生成回答,本身不执行业务逻辑
StreamableHttp 和 Stdio 客户端编码方式类似,构建 client 的方式,从 stdio_client 的方式换成了 streamable_http_client,配置服务端所在的地址。
python
# 安装依赖:pip install mcp
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async def streamablehttp_run():
# MCP Streamable HTTP 服务端地址,就是上面Demo服务的接入端点
url = "http://127.0.0.1:8000/mcp"
# 创建 streamable-http 客户端连接,返回 (read 读流, write 写流, 其他上下文)
async with streamable_http_client(url=url) as (read, write, _):
# 使用读写流创建 MCP ClientSession,所有交互都通过 session
async with ClientSession(read, write) as session:
# 和MCP服务端握手初始化,协商协议版本、能力
await session.initialize()
# 查询服务端暴露的所有工具列表
tools = await session.list_tools()
print(tools)
print()
# 调用名为 add 的工具,传入参数 a=1, b=2
call_res = await session.call_tool("add", {"a": 1, "b": 2})
print(call_res)
print()
# 查询服务端暴露的所有资源列表
resources = await session.list_resources()
print(resources)
print()
# 根据资源URI读取资源内容(只读资源)
read_res = await session.read_resource("greeting://default")
print(read_res)
print()
# 查询服务端暴露的所有提示词模板
prompts = await session.list_prompts()
print(prompts)
print()
# 获取提示词模板,传入参数 name="Jack" 渲染prompt
get_res = await session.get_prompt("greet_user", {"name": "Jack"})
print(get_res)
print()
# 启动异步主函数
asyncio.run(streamablehttp_run())
5 Agent 添加记忆
-- 前面的 agent 在多次 invoke 过程中,并不会将会话历史保存下来,也就是没有短期记忆。要想 Agent 拥有短期记忆,就需要在 create_agent 当中为 checkpointer 参数赋值,该参数其实是底层 LangGraph 用以保存多次调用记录的检查点配置。
-- Agent 拥有短期记忆能力之后,会将会话历史按照 thread_id 作为粒度进行隔离的。Thread_id 是来自于 agent 的底层运行时---LangGraph---当中的概念。不同的 thread_id,对应着不同的消息列表
如下图所示:

在具体调用时,只需要指定一个 thread_id,即可保证将所有短期记忆,存储到一个特定的消息列表当中;LLM在回复时,只会以当前 thread_id 所对应的消息列表作为上下文进行回复。
下面构造一个实际的 WeatherAgent, 来演示记忆功能:
python
import os
import datetime
from langchain_tavily import TavilySearch
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
# ---------------------- 初始化搜索工具 ----------------------
# TavilySearch 是专门面向大模型的搜索工具,会返回结构化搜索结果
# max_results=5:每次搜索最多返回5条结果
search = TavilySearch(max_results=5)
# 把工具放进列表,传给agent,agent可以自主选择调用
tools = [search]
# ---------------------- 初始化大模型 ----------------------
# init_chat_model 是 langchain 统一的模型加载入口,适配多家厂商
llm = init_chat_model(
model="gpt-4o-mini",
model_provider="openai",
)
# ---------------------- 关键点1:定义 checkpointer 存储实例 ----------------------
# InMemorySaver:内存级别的存储,用来保存对话状态、历史消息
# 只存在程序内存里,程序重启后记忆全部丢失,生产环境要用 RedisSaver / PostgresSaver
checkpointer = InMemorySaver()
# ---------------------- 创建支持记忆的 Agent ----------------------
agent = create_agent(
model=llm,
tools=tools,
checkpointer=checkpointer, # 关键点2:把存储实例注入agent,开启记忆持久化能力
)
# ====================== 第一次对话调用 ======================
print("=== 第一次调用 ===")
# agent.stream() 流式输出,一块一块返回结果,适合实时打印
for chunk in agent.stream(
input={
"messages": [
{
"role": "system",
"content": f"当前时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
},
{"role": "user", "content": "今天北京天气怎么样?"},
]
},
# 关键点3:thread_id 会话ID,同一个thread_id代表同一段对话
config={"configurable": {"thread_id": "abc123"}},
):
print(chunk, end="\n\n")
# ====================== 第二次对话调用 ======================
print("=== 第二次调用 ===")
for chunk in agent.stream(
input={
"messages": [
{"role": "user", "content": "我刚才问你什么了"},
]
},
# 这里写的是 abcedf 和上面 abc123 不一样!
# 两个不同thread_id = 两个独立会话,agent 回忆不到上一轮问题
# 想要记住,必须改成同样的 thread_id="abc123"
config={"configurable": {"thread_id": "abcedf"}},
):
print(chunk, end="\n\n")
总结
checkpointer+thread_id= LangGraph 会话记忆方案checkpointer:仓库,用来存状态thread_id:会话钥匙;钥匙相同,读取同一份历史;钥匙不同,全新对话
InMemorySaver局限:进程退出记忆清空,不能多实例共享create_agent是 LangGraph 新版预制 Agent,自带工具调用、状态管理,比旧版AgentExecutor简洁stream():流式返回,适合做聊天界面;如果要一次性拿结果,用.invoke()
修复后的第二次调用配置(能正常记住上下文)
config={"configurable": {"thread_id": "abc123"}}
6 Agent 执行添加中间件
-- 在 create_agent 方法当中,还有一个 middleware 参数,可以传入中间件列表,即可在调用过程中使用中间件。
-- 在 LangChain 的 Agent 当中,中间件用于更加方便地控制Agent在执行过程当中的一些行为,例如,总结中间件可以在调用大模型前对历史消息列表进行总结,人在循环(Human-In-the-Loop)中间件,可以在调用工具过程中引入人类审核机制,人类审核通过后,才继续调用工具。
-- 在没有中间件时,整个 Agent 的执行过程如下所示:

-- 用户先输入请求,会直接进入到model节点,model节点判断,如果需要调用工具,则进入到tools节点,调用完工具之后,再回到model节点,model节点内,大模型继续判断,是否需要进一步调用工具,如果不需要直接走到END节点即可。
-- 而在引入了中间件之后,整个调用过程如下图所示:

整个调用Agent的过程,可以在不改变model和tools的相关代码前提下,实现多处调整:
- before/after_agent:在agent调用的起始输入和终点输出,进行相关处理(切片编程思想);
- before/after_model: 在model调用的前后,进行相关处理(切片编程思想);
- wrap_tool/model_call: 通过handler回调的方式,拦截工具/模型执行,可以为工具执行/模型执行,添加重试,缓存,多次调用等相关逻辑(代理思想)。
注意:不同的中间件,可选择性地仅对以上的一个或者多个节点定义处理逻辑。
以下是两种中间件,及其分别实现的逻辑:
1)SummarizationMiddleware
总结历史消息的中间件,可以通过多种不同的策略,使用一个单独的大模型总结历史消息。该中间件实现了before_model的处理逻辑:在调用模型前,先按照配置的策略对消息进行压缩,再传递给model节点进行调用。
实例化方式如下所示:
python
# 导入 LangGraph Agent 的中间件:人类介入/摘要中间件
# SummarizationMiddleware:自动对超长对话历史做摘要,压缩消息上下文,防止消息列表无限膨胀、token超限
from langchain.agents.middleware.human_in_the_loop import SummarizationMiddleware
# 实例化一个专门负责做摘要的 LLM
# 摘要任务不需要很强的模型,用 gpt-4o-mini 性价比高
summarize_llm = ChatOpenAI(model="gpt-4o-mini")
# 实例化摘要中间件
# trigger=("messages", 10) 触发规则:
# 监控状态里的 messages 消息列表,当消息数量达到 10 条时,自动触发摘要
# 触发后:把前面历史消息压缩成一段摘要文本,保留最新消息,减少上下文占用
summary_middleware = SummarizationMiddleware(model=summarize_llm, trigger=("messages", 10))
trigger表示所使用的策略,传入方式为元组,可以传入多种不同的策略:
- ("messages", 100):agent消息列表的长度达到100以后,即进行总结压缩;
- ("fraction",0.5): agent消息列表当中的token总长度(langchain内部提供了token计算函数,也可以在该函数当中自定义token_counter)达到了model最大输入token数的0.5时,进行总结压缩;
- ("tokens",3000): agent消息列表当中的token总长度达到了一个绝对值之后(3000),进行总结压缩。
2)HumanInTheLoopMiddleware
调用工具前实现人工审核功能的中间件,可以对不同的工具,定义不同的审核策略(是否需要审核)。该中间件实现了after_model的处理逻辑。在模型回复后,该中间件判断模型的回复结果当中是否有工具调用。如果有,判断所需要调用的模型当中,是否有需要人工审核的工具。
具体实例化方式如下所示:
python
# 导入 LangGraph Agent 的人工介入中间件(HITL,Human-in-the-Loop)
# 作用:指定某些工具调用前自动暂停 Agent,交给人工审核确认,通过之后才真正执行工具
from langchain.agents.middleware.human_in_the_loop import HumanInTheLoopMiddleware
# 配置规则字典:key = 工具名称,value = 是否需要人工中断审核
# send_email: True → 调用 send_email 发邮件工具时,先暂停,等待人类确认
# get-current-date: False → 调用该工具不需要人工确认,Agent直接执行
tool_name_to_interrupt = {'send_email':True,'get-current-date':False}
# 实例化人工介入中间件
# interrupt_on:传入上面的规则表,定义哪些工具触发人工拦截
human_in_the_loop_middleware = HumanInTheLoopMiddleware(interrupt_on=tool_name_to_interrupt)
示例
python
import asyncio
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
# ====================== 1. 定义Agent可用工具 ======================
@tool
def get_weather(city: str) -> str:
"""查询天气
Args:
city: 城市名称
Returns:
天气结果字符串
"""
return f"{city}的天气晴朗,气温25度。"
@tool
def transfer_money(amount: int, to_account: str) -> str:
"""转账工具 (敏感高危操作)
Args:
amount: 转账金额
to_account: 收款账户
Returns:
转账结果信息
"""
print(f"!!! 正在执行转账: {amount} -> {to_account} !!!")
return f"成功转账 {amount} 元给 {to_account}。"
# ====================== 2. 初始化大模型 ======================
llm = init_chat_model("gpt-4o-mini", model_provider="openai")
# ====================== 3. 配置人工介入中间件 HITL ======================
# interrupt_config:配置哪些工具调用需要人工拦截审核
# key:工具名称;value=True → 开启拦截,等待人工确认;False → 直接放行自动执行
interrupt_config = {
"transfer_money": True, # 转账属于高危操作,调用前中断,等待人审批
"get_weather": False # 查询天气是只读查询,无需人工审核,直接执行
}
hitl_middleware = HumanInTheLoopMiddleware(interrupt_on=interrupt_config)
# ====================== 4. 创建Agent ======================
# ⚠️ 重要:中断功能必须搭配 checkpointer
# 中断时需要保存当前图的运行状态,等人工确认后恢复继续跑
checkpointer = InMemorySaver()
agent = create_agent(
model=llm,
tools=[get_weather, transfer_money],
middleware=[hitl_middleware], # 挂载人工审核中间件
checkpointer=checkpointer,
)
# ====================== 异步主演示函数 ======================
async def run_demo():
print("=== HumanInTheLoopMiddleware 演示 ===")
print("场景:用户让 Agent 转账,Agent 在执行前会暂停等待批准。")
thread_id = "thread-1"
# 会话配置,同一个thread_id绑定同一份状态(中断信息、对话历史都存在这里)
config = {"configurable": {"thread_id": thread_id}}
# 用户原始提问
print("\n[User]: 请帮我转账 100 元给 Alice")
# 初始输入:用户消息
current_input = {"messages": [{"role": "user", "content": "请帮我转账 100 元给 Alice"}]}
# Command 用来存放恢复中断时的指令,初始为空
current_command = None
while True:
try:
# 分支:如果有恢复指令,就用Command恢复图;否则发送新用户输入
if current_command:
result = await agent.ainvoke(current_command, config=config)
current_command = None # 清空恢复指令,避免重复使用
else:
if not current_input:
break
result = await agent.ainvoke(current_input, config=config)
current_input = None # 本轮用户输入已经处理完毕
# 打印返回消息,区分不同消息类型
if "messages" in result:
for msg in result["messages"]:
# AI消息:想要调用工具
if hasattr(msg, "tool_calls") and msg.tool_calls:
print(f"[Agent]: 我想调用工具: {msg.tool_calls}")
# tool类型消息:工具执行完成后的返回结果
if msg.type == "tool":
print(f"[Tool Output]: {msg.content}")
# AI普通回复,没有工具调用,代表任务结束
if msg.type == "ai" and not msg.tool_calls:
print(f"[Agent]: {msg.content}")
except Exception as e:
print(f"发生错误: {e}")
pass
# ========== 判断是否触发人工中断 ==========
# 当中间件拦截成功,返回结果里会带上 "__interrupt__" 标记
if "__interrupt__" in result:
interrupt_value = result["__interrupt__"][0].value
print(f"\n!!! 检测到中断 (Middleware 拦截) !!!")
print(f"中断详情: {interrupt_value}")
# 模拟人工审核交互界面
print("\n[System]: 请审核上述操作 (approve/reject/edit):")
decision_type = "approve" # 这里硬编码模拟用户选择:approve 同意;也可以改成 reject拒绝
print(f"[User]: {decision_type}")
decisions = []
# 取出本次被拦截等待审批的操作列表
action_requests = interrupt_value.get("action_requests", [])
for req in action_requests:
print(f" - 批准操作: {req['name']}")
decisions.append({"type": "approve"})
# ✅ 核心:构造 Command(resume=xxx) 给LangGraph,恢复被暂停的流程
# resume里面携带人工审批结果,交给HITL中间件处理
current_command = Command(resume={"decisions": decisions})
print("[System]: 恢复执行...")
continue # 回到循环头部,用current_command恢复运行
# 没有中断标记,说明流程正常跑完,退出循环
break
if __name__ == "__main__":
asyncio.run(run_demo())
核心要点整理
HumanInTheLoopMiddleware工作链路
Agent 推理 → 决定调用transfer_money→ 中间件匹配到该工具需要拦截 → 抛出中断、保存状态到 checkpointer → 外部接收中断,人工选择approve/reject/edit→ 组装Command(resume=...)→ 恢复执行Command的作用
LangGraph 标准类型,专门用来恢复中断的流程,不是普通用户消息。- 三种审批动作
approve:放行,正常执行工具reject:拒绝,不执行工具,告诉 Agent 该操作被禁止edit:修改工具入参之后再批准执行
- 局限性
InMemorySaver内存存储,程序重启中断状态丢失;生产环境替换为 RedisSaver / PostgresSaver。