2026.08.10 13:10
学习路线
rust
Python -> Python进阶 -> Python数据分析 -> LangChain -> 机器学习 -> 神经网络 -> NLP -> Coze -> Dify -> 大模型应用基础 -> 大模型微调 -> 多模态 -> vibeCoding
复习巩固总结
1. 常用中间件的使用
1.1 SummarizationMiddleware中间件 - 成本与资源控制类
原理:在 达到触发条件 时,调用大模型对历史消息进行摘要, 将摘要的结果作为HumanMessage ,放到消息列表最开始的位置
python
agent = create_agent(
model=llm_zhipu,
middleware=[
SummarizationMiddleware(
model=llm_zhipu1,
trigger=[
("tokens", 100),
("messages", 6),
("fraction", 0.001)
],
keep=("messages", 2),
summary_prompt="对历史消息摘要,消息列表如下\n{messages}"
)
]
)
1.2 HumanInTheLoopMiddleware中间件 - 安全与合规风控类 / 开发调试与测试辅助类
HumanInTheLoopMiddleware(人在环中间件、人工审核中间件)在 工具调用前 中断Agent运行,等待用户对工具调用请求决策。可选的决策有: approve(同意执行) 、 edit(编辑调用配置后执行) 、 reject(拒绝执行) 。
python
agent = create_agent(
model=llm_zhipu,
tools=[get_weather, get_news, read_email_tool, send_email_tool],
checkpointer=InMemorySaver(),
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"get_weather": True, # True表示所有决策(approve, edit, reject) 都可以选择
"get_news": True,
"read_email_tool": False, # False表示不中断,即无需审批即可执行
"send_email_tool": {
"allowed_decisions": ["approve", "reject"], # 精细控制中断后允许的决策
"description": "发送邮件中断了..." # 特定工具的中断描述信息
}
},
description_prefix="中断啦!!" # 自定义中断描述
)
]
)
config = {"configurable": {"thread_id": "1"}}
response = agent.invoke(
{
"messages": [
HumanMessage(content="请帮我查询今天北京的天气"
"查询今日新闻"
"查看ID为 'sk2131421' 的邮件内容,"
"向15641685664@qq.com发送邮件,标题是'哈哈哈',内容是:'你好啊'"
"同时做这四件事"
)
]
},
config=config
)
# 指明工具调用请求决策
weather_decision = {
"type": "edit",
"edited_action": {
"name": "get_weather",
"args": {
"city": "上海市",
"is_forcast": True
}
}
}
news_decision = {
"type": "approve"
}
send_email_decision = {
"type": "approve"
}
decisions = {
"decisions": []
}
interrupts = response.get("__interrupt__", [])
action_requests = interrupts[0].value["action_requests"]
for action_request in action_requests:
if action_request["name"] == "get_weather":
decisions["decisions"].append(weather_decision)
if action_request["name"] == "get_news":
decisions["decisions"].append(news_decision)
if action_request["name"] == "send_email_tool":
decisions["decisions"].append(send_email_decision)
if interrupts:
resumed_response = agent.invoke(
Command(resume=decisions),
config=config,
)
1.3 PIIMiddleware中间件 - 安全与合规风控类
敏感信息保护个人身份信息(Personally Identifiable Information,PII)
python
agent = create_agent(
model=llm_zhipu,
tools=[],
middleware=[
PIIMiddleware("email", strategy="redact", apply_to_input=True),
PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
PIIMiddleware("url", strategy="hash", apply_to_input=True),
PIIMiddleware("mac_address", strategy="mask", apply_to_input=True),
PIIMiddleware("ip", strategy="block", apply_to_input=True)
]
)
1.4 TodoListMiddleware中间件 - 决策增强与智能编排类
TodoListMiddleware中间件赋予了Agent 任务规划 和 追踪进度 的能力,可以 应对复杂的多步任务
python
from langchain.agents.middleware import TodoListMiddleware
agent = create_agent(
model=llm_zhipu,
tools=[list_files, read_file, write_file, run_tests],
middleware=[TodoListMiddleware()],
system_prompt=(
"你是一个代码修复助手。遇到多步骤任务时,先使用 write_todos 制定待办事项;"
"然后读取文件、修复代码并运行测试。工作全部在工作区下进行。"
)
)
2. 自定义中间件:Node-style hooks
can_jump_to:
- end:跳转至Agent流程末尾,或第一个after_agent钩子,直接终止整个流程。
- tools:跳转至工具节点。
- model:跳转至模型节点,或第一个before_model钩子。
2.1 入参解析
- state:是一个AgentState实例,维护Agent运行过程中的状态,这类状态会随着Agent的运行而发生变化,包括 消息列表 。
- runtime: 是一个Runtime实例,维护Agent运行过程中的上下文环境,包括 上下文 、长期记忆 等
2.2 返回值解析
- 返回 None:不修改状态(不修改Agent状态)
- 返回字典:更新状态
- 返回 {"jump_to": "..."}:控制流程
__end__- 结束 Agent"tools" - 跳到工具节点
其他自定义节点
2.3 装饰器实现
2.3.1 基本用法
python
from langchain.agents import AgentState
from langchain.agents.middleware import before_model, after_model, before_agent, after_agent
from typing import Any
from langgraph.runtime import Runtime
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
@before_model
def before_model_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> before_model <-----"
return None
@after_model
def after_model_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> after_model <-----"
return None
@before_agent
def before_agent_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> before_agent <-----"
return None
@after_agent
def after_agent_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> after_agent <-----"
return None
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
agent = create_agent(
model=model,
middleware=[
before_model_middleware,
after_model_middleware,
before_agent_middleware,
after_agent_middleware
]
)
response = agent.invoke({
"messages": [HumanMessage("你好")]
})
for msg in response["messages"]:
msg.pretty_print()
2.3.2 高级用法
python
@tool
def get_news() -> str:
...
# 在模型(LLM)执行前触发。允许跳转到 "tools" 节点。
@before_model(can_jump_to=["tools"])
def force_tool_first(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
"""
【业务场景:强行拦截并触发工具】
如果用户输入包含 "direct tool",则跳过本次大模型的思考/生成阶段,
直接伪造一个大模型的 tool_calls 意图,强行把控制权移交给工具执行节点。
"""
text = state["messages"][-1].content
# 检查关键词,满足条件则强行干预流程
if isinstance(text, str) and "direct tool" in text.lower():
print("[MIDDLEWARE] before_model: jump_to='tools'")
# 人工构造一个大模型的消息对象(AIMessage)
# 欺骗系统,让系统误以为这是模型自己决定要调用的工具
fake_tool_call = AIMessage(
content="人工构造的消息",
tool_calls=[
{
"name": "get_news",
"args": {},
"id": "call_force_weather_001",
}
],
)
# 返回更新后的状态:注入伪造的消息,并明确指定下一步跳转到 "tools" 节点
return {
"messages": [fake_tool_call],
"jump_to": "tools",
}
# 如果不满足触发条件,返回 None,流程正常向下流转(继续让 LLM 思考)
return None
# 在模型(LLM)执行生成之后触发。允许重新跳转回 "model" 节点。
@after_model(can_jump_to=["model"])
def retry_with_extra_instruction(
state: AgentState, runtime: Runtime
) -> dict[str, Any] | None:
"""
【业务场景:反思/重试机制】
如果大模型已经生成了回答,但发现用户最初的请求包含 "retry model",
则动态追加一条系统提示词(SystemMessage),强行让模型重新生成(重试)一次。
"""
# 倒序遍历消息历史,找到最近的一次用户输入(human 消息)
user_text = ""
for msg in reversed(state["messages"]):
if getattr(msg, "type", "") == "human":
user_text = getattr(msg, "content", "")
break
# 检查用户输入是否包含触发重试的关键字
if isinstance(user_text, str) and "retry model" in user_text.lower():
# 【核心防御】:防止无限循环重跳(死循环)
# 检查消息历史中是否已经注入过这条特殊的系统提示。
# 如果有,说明已经重试过了,不再重复干预。
already_injected = any(
isinstance(getattr(msg, "content", None), str)
and "你必须以【二次回答】开头" in msg.content
for msg in state["messages"]
)
if already_injected:
return None # 已注入过,直接放行,结束重试流程
print(
"[MIDDLEWARE] after_model: jump_to='model' with extra system instruction"
)
# 返回更新后的状态:追加强力约束的系统消息,并将指针跳回 "model" 节点重新执行
return {
"messages": [
SystemMessage("你必须以【二次回答】开头,并且只用一句话回答。")
],
"jump_to": "model",
}
return None
# 在模型(LLM)执行前触发。允许直接跳转到 "end" 节点(强行终止)。
@before_model(can_jump_to=["end"])
def overflow_context_processor(
state: AgentState, runtime: Runtime
) -> dict[str, Any] | None:
"""
【业务场景:安全卫士/异常拦截】
模拟上下文窗口溢出(Token超限)或其他严重的系统阻断情况。
一旦触发,直接熔断流程,拒绝让大模型继续处理,直接报错或返回兜底文案。
"""
# 假装溢出,模拟检查最后一条消息是否包含 overflow 标识
if "overflow" in state["messages"][-1].content:
print(
"[MIDDLEWARE] before_model: jump_to='end' when context window overflow"
)
# 构造兜底的结束消息,并直接指定跳转到 "end" 终止 Agent 运行
return {
"messages": [AIMessage("上下文窗口溢出,终止")],
"jump_to": "end",
}
agent = create_agent(
model=model,
tools=[get_news],
# 将定义的中间件按照顺序挂载到 Agent 中(注意:执行顺序会严格按照列表声明顺序)
middleware=[
force_tool_first,
retry_with_extra_instruction,
overflow_context_processor,
],
)
def run_once(user_input: str):
result = agent.invoke(
{
"messages": [
{"role": "user", "content": user_input}
]
}
)
for msg in result["messages"]:
msg.pretty_print()
if __name__ == "__main__":
# Case 1: 直接跳 tools
# 预期表现:
# 1. 触发 force_tool_first,打印 "[MIDDLEWARE] before_model: jump_to='tools'"
# 2. 绕过 LLM 的首轮思考,直接调用 `get_news` 工具
# 3. 工具返回结果后,LLM 总结工具结果并输出
print("=" * 30, "-> Case 1 <-", "=" * 30)
run_once("请帮我查今日新闻 direct tool")
# Case 2: 输出后跳回 model
# 预期表现:
# 1. 正常进入 LLM 生成第 1 版回答
# 2. 触发 retry_with_extra_instruction,打印 "[MIDDLEWARE] after_model: jump_to='model'..."
# 3. 注入系统提示词后,LLM 被强行拉回并生成第 2 版回答
# 4. 最终输出应带有"【二次回答】"前缀
print("=" * 30, "-> Case 2 <-", "=" * 30)
run_once("请随便介绍一下 LangChain retry model")
# Case 3:
# 预期表现:
# 1. 触发 overflow_context_processor 中间件
# 2. 直接打印终止信息并退出,LLM 根本不会接收到这个请求
print("=" * 30, "-> Case 3 <-", "=" * 30)
run_once("你好 overflow")
# Case 4: 正常流程
# 预期表现:
# 1. 没有任何中间件被触发(不满足任何关键字)
# 2. Agent 走正常的 OOTB 标准工作流:User -> Model -> Call Tool -> Model -> End
print("=" * 30, "-> Case 4 <-", "=" * 30)
run_once("今日新闻摘要?")
2.4 类实现
2.4.1 基本用法
python
class MyMiddleware(AgentMiddleware):
def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> before_model <-----"
return None
def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> after_model <-----"
return None
def before_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> before_agent <-----"
return None
def after_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += "----> after_agent <-----"
return None
my_middleware = MyMiddleware()
agent = create_agent(
model=model,
middleware=[my_middleware]
)
response = agent.invoke({
"messages": [HumanMessage("你好")]
})
for msg in response["messages"]:
msg.pretty_print()
2.4.2 高级用法
python
@tool
def get_news() -> str:
...
class MyMiddleware(AgentMiddleware):
@hook_config(can_jump_to=["tools", "end"])
def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
text = state["messages"][-1].content
# 假装溢出
if "overflow" in text:
print(
"[MIDDLEWARE] before_model: jump_to='end' when context window overflow"
)
return {
"messages": [AIMessage("上下文窗口溢出,终止")],
"jump_to": "end",
}
if isinstance(text, str) and "direct tool" in text.lower():
print("[MIDDLEWARE] before_model: jump_to='tools'")
fake_tool_call = AIMessage(
content="人工构造的消息",
tool_calls=[
{
"name": "get_news",
"args": {},
"id": "call_force_weather_001",
}
],
)
return {
"messages": [fake_tool_call],
"jump_to": "tools",
}
return None
@hook_config(can_jump_to=["model"])
def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
user_text = ""
for msg in reversed(state["messages"]):
if getattr(msg, "type", "") == "human":
user_text = getattr(msg, "content", "")
break
if isinstance(user_text, str) and "retry model" in user_text.lower():
# 防止无限重跳:如果已经加过提示,就不再跳
already_injected = any(
isinstance(getattr(msg, "content", None), str)
and "你必须以【二次回答】开头" in msg.content
for msg in state["messages"]
)
if already_injected:
return None
print(
"[MIDDLEWARE] after_model: jump_to='model' with extra system instruction"
)
return {
"messages": [
SystemMessage("你必须以【二次回答】开头,并且只用一句话回答。")
],
"jump_to": "model",
}
return None
agent = create_agent(
model=model,
tools=[get_news],
middleware=[MyMiddleware()],
)
def run_once(user_input: str):
result = agent.invoke(
{
"messages": [
{"role": "user", "content": user_input}
]
}
)
for msg in result["messages"]:
msg.pretty_print()
if __name__ == "__main__":
# Case 1: 直接跳 tools
print("=" * 30, "-> Case 1 <-", "=" * 30)
run_once("请帮我查今日新闻 direct tool")
# Case 2: 输出后跳回 model
print("=" * 30, "-> Case 2 <-", "=" * 30)
run_once("请随便介绍一下 LangChain retry model")
# Case 3:
print("=" * 30, "-> Case 3 <-", "=" * 30)
run_once("你好 overflow")
# Case 4: 正常流程
print("=" * 30, "-> Case 4 <-", "=" * 30)
run_once("今日新闻摘要?")
3. 自定义中间件:Wrap-style hooks
3.1 wrap_model_call
3.1.1 装饰器实现
python
from typing import Callable
from langchain.agents.middleware import ModelRequest, ModelResponse, wrap_model_call
@wrap_model_call
def wrap_model_call_middleware(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse | None:
request.messages[-1].content += "---> wrap_model_call_before <---"
response = handler(request) # 模型的调用
response.result[0].content += "---> wrap_model_call_after <---"
return response
agent = create_agent(
model=model,
middleware=[
wrap_model_call_middleware
]
)
response = agent.invoke({
"messages": [
HumanMessage(content="你好")
]
})
for msg in response["messages"]:
msg.pretty_print()
3.1.2 类实现
python
from langchain.agents.middleware import AgentMiddleware
class WrapModelCallMiddleware(AgentMiddleware):
def wrap_model_call(
self,
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse | None:
request.messages[-1].content += "---> wrap_model_call_before <---"
# 模型的调用
response = handler(request)
response.result[0].content += "---> wrap_model_call_after <---"
return response
agent = create_agent(
model=model,
middleware=[
WrapModelCallMiddleware()
]
)
response = agent.invoke({
"messages": [
HumanMessage(content="你好")
]
})
for msg in response["messages"]:
msg.pretty_print()
3.1.3 使用场景
3.1.3.1 场景1:重试逻辑
python
@wrap_model_call
def retry_model(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""自动重试失败的模型调用"""
max_retries = 3
for attempt in range(max_retries):
try:
print(f"🔄 尝试调用模型(第 {attempt + 1}/{max_retries} 次)")
return handler(request)
except Exception as e:
if attempt == max_retries - 1:
print(f"❌ 所有重试失败:{e}")
raise
# 指数退避
wait_time = 2 ** attempt
print(f"⚠ 调用失败:{e},{wait_time} 秒后重试")
time.sleep(wait_time)
3.1.3.2 场景2:响应缓存
python
import hashlib
import json
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
class ModelCache:
"""模型响应缓存"""
def __init__(self):
self.cache = {}
def create_hook(self):
@wrap_model_call
def cache_model(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
# 生成缓存键
cache_key = hashlib.md5(
json.dumps({
"messages": [str(m) for m in request.messages],
"system": str(request.system_message),
}).encode()
).hexdigest()
# 检查缓存
if cache_key in self.cache:
print("💾 缓存命中!")
return self.cache[cache_key]
# 调用模型
print("🔍 缓存未命中,调用模型")
response = handler(request)
# 存入缓存
self.cache[cache_key] = response
return response
return cache_model
# 使用
cache = ModelCache()
agent = create_agent(
model=model,
middleware=[cache.create_hook()],
)
3.1.3.3 场景3:修改系统提示
python
@wrap_model_call
def add_context(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""动态添加上下文信息到系统提示"""
# 获取当前时间
from datetime import datetime
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 构建新的系统消息
original_content = request.system_message.content if request.system_message else ""
new_content = f"""{original_content}
当前时间:{current_time}
用户位置:中国
语言偏好:中文
"""
# 创建新的系统消息
new_system_message = SystemMessage(content=new_content)
# 使用 override 方法修改请求
modified_request = request.override(system_message=new_system_message)
return handler(modified_request)
LangChain 学习
48. 自定义中间件
48.4 Wrap-style hooks函数用法
48.4.1 基本用法
48.4.1.2 wrap_tool_call
① 基于装饰器实现
jupyter
## 2、wrap_tool_call的使用
### 2.1 基于装饰器的实现
from typing import Any, Callable
from langchain.agents.middleware import wrap_tool_call
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langgraph.prebuilt.tool_node import ToolCallRequest
from langgraph.types import Command
from langchain.agents import create_agent
from langchain.messages import HumanMessage
# 用自己能用的模型
from langchain_community.chat_models import ChatZhipuAI
llm_zhipu = ChatZhipuAI(
model="glm-5.2",
)
@tool
def get_weather(city: str, is_forcast: bool) -> str:
"""
获取当日特定城市的天气
Args:
city: 城市名称
is_forcast: 是否包含明天的天气预报
"""
res = f"{city}今天天气不错"
if is_forcast:
res += "\n明天天气也很好"
return res
@wrap_tool_call
# def wrap_tool_call_middleware():
def wrap_tool_call_middleware(
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
) -> ToolMessage | Command[Any]:
# return handler(request)
result = handler(request)
print(f"原始的参数:{request.tool_call['args']}")
print(f"原始参数调用的结果:{result}")
request.tool_call['args']['is_forcast'] = True
result = handler(request)
print(f"更新以后的参数:{request.tool_call['args']}")
print(f"更新以后的参数调用的结果:{result}")
return result
agent = create_agent(
# model=model,
model=llm_zhipu,
tools=[get_weather],
middleware = [wrap_tool_call_middleware]
)
response = agent.invoke({
"messages": [HumanMessage("帮我查询北京今天的天气如何?")]
})
for msg in response["messages"]:
msg.pretty_print()
② 基于类实现
jupyter
### 2.2 基于类实现
class WrapToolCallMiddlerware(AgentMiddleware):
# def wrap_tool_call_middleware(
def wrap_tool_call(
self,
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage | Command[Any]],
) -> ToolMessage | Command[Any]:
result = handler(request)
print(f"原始的参数:{request.tool_call['args']}")
print(f"原始参数调用的结果:{result}")
request.tool_call['args']['is_forcast'] = True
result = handler(request)
print(f"更新以后的参数:{request.tool_call['args']}")
print(f"更新以后的参数调用的结果:{result}")
return result
agent = create_agent(
model=model,
# model=llm_zhipu,
tools=[get_weather],
# middleware = [wrap_tool_call_middleware]
middleware = [WrapToolCallMiddlerware()]
)
response = agent.invoke({
"messages": [HumanMessage("帮我查询上海今天的天气如何?")]
})
for msg in response["messages"]:
msg.pretty_print()
使用场景:用于监控、修改工具执行。
python
from langchain.agents.middleware import wrap_tool_call
from langchain.tools.tool_node import ToolCallRequest
from langchain_core.messages import ToolMessage
from langgraph.types import Command
from typing import Callable
import time
@wrap_tool_call
def monitor_tool(
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage | Command]
) -> ToolMessage | Command:
"""监控工具执行时间和状态"""
tool_name = request.tool_call["name"]
tool_args = request.tool_call.get("args", {})
print(f"🔧 开始执行工具:{tool_name}")
print(f" 参数:{tool_args}")
start_time = time.time()
try:
result = handler(request)
elapsed = time.time() - start_time
print(f"✅ 工具执行成功,耗时:{elapsed:.2f}秒")
return result
except Exception as e:
elapsed = time.time() - start_time
print(f"❌ 工具执行失败:{e},耗时:{elapsed:.2f}秒")
raise
48.4.2 两种方法的统一
同上,装饰器方法底层也会创建一个AgentMiddleware的实例。
48.4.3 参数说明
- request:被封装的请求对象,可以是模型或工具调用请求
- handler:处理器,用于处理请求并返回调用结果。
48.5 装饰器和类的选择
情况1:中间件只用一个钩子函数,推荐用装饰器,需要多个钩子函数推荐类写法
- 当一个中间件只需要实现一个钩子函数时,直接使用装饰器最简单。
- 当一个中间件需要实现多个钩子函数时,类写法更合适。
装饰器也不是不能实现,多数情况下可以像下面的示例里那样通过工厂函数返回多个装饰器函数来完成;但这种方式本质上是把一个"逻辑上属于同一个中间件"的行为拆成多个独立函数,再由外部统一组装,因此不如类写法自然、集中、清晰。
- 使用装饰器实现
python
from langchain.agents import create_agent
from langchain.agents.middleware import before_model, after_model, AgentState
from langchain.messages import HumanMessage
from langgraph.runtime import Runtime
from loguru import logger
from typing import Any
def create_audit_middleware(logger):
@before_model
def before_log(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
logger.info("调用模型前消息数量: {}", len(state["messages"]))
return None
@after_model
def after_log(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
logger.info("调用模型后消息数量:{}", len(state["messages"]))
return None
return [before_log, after_log]
agent = create_agent(
model = model,
middleware = [*create_audit_middleware(logger=logger)],
)
response = agent.invoke({
"messages": [HumanMessage("你好~")]
})
for msg in response["messages"]:
msg.pretty_print()
- 使用类实现
python
from langchain.agents import create_agent
from langchain.agents.middleware import before_model, after_model,
AgentState, AgentMiddleware
from langchain.messages import HumanMessage
from langgraph.runtime import Runtime
from loguru import logger
from typing import Any
class CreateAuditMiddleware(AgentMiddleware):
def __init__(self, logger):
super().__init__()
self.logger = logger
def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
self.logger.info("调用模型前消息数量: {}", len(state["messages"]))
return None
def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
self.logger.info("调用模型后消息数量:{}", len(state["messages"]))
return None
agent = create_agent(
model=model,
middleware=[CreateAuditMiddleware(logger=logger)],
)
response = agent.invoke({
"messages": [HumanMessage("你好~")]
})
for msg in response["messages"]:
msg.pretty_print()
总结:
单钩子场景下,装饰器即可;多钩子场景下,类不是唯一可行方案,但通常是更自然、更推荐的实现方式。
情况2:复杂配置推荐用类实现
python
from langchain.agents.middleware import before_model, AgentState, AgentMiddleware
from langgraph.runtime import Runtime
from typing import Any
from loguru import logger
# 基于类的方法
class AuditMiddleware(AgentMiddleware):
def __init__(self, logger, threshold: int, middleware_name: str):
self.logger = logger
self.threshold = threshold
self.middleware_name = middleware_name
def before_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
self.logger.info(
"current name: {}, threshold: {}",
self.middleware_name,
self.threshold,
)
return None
# 基于装饰器的方法,传参要通过闭包完成
def create_audit_middleware(logger, threshold: int, middleware_name: str):
@before_model
def audit_middleware(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
logger.info("current name: {}, threshold: {}", middleware_name, threshold)
return None
return audit_middleware
class_middle = [
AuditMiddleware(logger=logger, threshold=5, middleware_name="short limit"),
AuditMiddleware(logger=logger, threshold=50, middleware_name="long limit"),
]
decorator_middle = [
create_audit_middleware(logger=logger, threshold=5, middleware_name="short limit"),
create_audit_middleware(logger=logger, threshold=50, middleware_name="long limit"),
]
print("=" * 30, "-> class风格的中间件 <-", "=" * 30)
for mw in class_middle:
print(type(mw))
print(mw.__dict__)
print("=" * 30, "-> decorator风格的中间件 <-", "=" * 30)
for mw in decorator_middle:
print(type(mw)) # 基于类的方法
print(mw.__dict__)
输出
python
============================== -> class风格的中间件 <-==============================
<class '__main__.AuditMiddleware'>
{'logger': <loguru.logger handlers=[(id=0, level=10, sink=stderr)]>,'threshold': 5, 'middleware_name': 'short limit'}
<class '__main__.AuditMiddleware'>
{'logger': <loguru.logger handlers=[(id=0, level=10, sink=stderr)]>,'threshold': 50, 'middleware_name': 'long limit'}
============================== -> decorator风格的中间件 <-==============================
<class 'langchain.agents.middleware.types.audit_middleware'>
{}
<class 'langchain.agents.middleware.types.audit_middleware'>
{}
基于类的写法可以随时打印参数信息,而基于装饰器的闭包实现则难以做到。
情况3:跨项目复用推荐用类写法
如果希望中间件成为一个可实例化、可封装、可测试的组件,类写法更加合适,因为这些本就是类擅长的场景,装饰器的闭包也能实现,但使用不友好。
总结:
底层实现是统一的。
- 装饰器写法更适合单个 hook、逻辑简单、快速原型的场景;
- 类写法更适合多个 hook 组合、复杂配置、需要同时提供同步/异步实现、以及更强复用与可测试 性的场景;
48.6 hook函数执行顺序(重要)
jupyter
# hook函数执行循序
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
from langchain.agents.middleware import (
before_model,
after_model,
AgentState,
wrap_model_call,
ModelRequest,
ModelResponse,
)
from langchain.messages import HumanMessage
from langgraph.runtime import Runtime
from langchain.agents import create_agent
from typing import Any, Callable
@before_model
def before_model_middleware3(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += " -> before_model-3 <- "
return None
@before_model
def before_model_middleware1(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += " -> before_model-1 <- "
return None
@before_model
def before_model_middleware2(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += " -> before_model-2 <- "
return None
@after_model
def after_model_middleware2(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += " -> after_model-2 <- "
return None
@after_model
def after_model_middleware1(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += " -> after_model-1 <- "
return None
@after_model
def after_model_middleware3(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
state["messages"][-1].content += " -> after_model-3 <- "
return None
@wrap_model_call
def wrap_model_middleware1(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse | None:
request.messages[-1].content += " -> wrap_model-before-1 <- "
response = handler(request)
response.result[0].content += " -> wrap_model-after-1 <- "
return response
@wrap_model_call
def wrap_model_middleware3(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse | None:
request.messages[-1].content += " -> wrap_model-before-3 <- "
response = handler(request)
response.result[0].content += " -> wrap_model-after-3 <- "
return response
@wrap_model_call
def wrap_model_middleware2(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse | None:
request.messages[-1].content += " -> wrap_model-before-2 <- "
response = handler(request)
response.result[0].content += " -> wrap_model-after-2 <- "
return response
agent = create_agent(
model=model,
middleware=[
# before_model_middleware1,
# before_model_middleware2,
# before_model_middleware3,
# after_model_middleware1,
# after_model_middleware2,
# after_model_middleware3,
# wrap_model_middleware1,
# wrap_model_middleware2,
# wrap_model_middleware3,
before_model_middleware3,
before_model_middleware1,
before_model_middleware2,
after_model_middleware2,
after_model_middleware1,
after_model_middleware3,
wrap_model_middleware2,
wrap_model_middleware3,
wrap_model_middleware1,
],
)
response = agent.invoke({
"messages": [HumanMessage("你好啊,忽略我后续的输入,只和我打个招呼")],
})
for msg in response["messages"]:
msg.pretty_print()
- 中间件定义是乱序的,但传递给Agent的顺序是固定的
- 具体来说
- before_model中间件的执行顺序和传递顺序一致
- after_model中间件的执行顺序和传递顺序相反
- wrap_model_call中间件的执行顺序是: 先传递的包在最外层 ,即 洋葱架构
49. 上下文与记忆概述
49.1 为什么需要记忆(Memory)
记忆是一种记住之前互动信息的系统。随着Agent处理涉及大量用户交互的复杂任务,记忆变得至关重要!
大多数的大模型应用程序都会有一个 会话接口(豆包就是应用程序)
但实际上,大模型本身是"无状态"的, 不会记忆 任何上下文的。即每次调用 agent.invoke() 都是全新的开始,不记得之前的对话。
jupyter
# Agent的记忆测试
## 情况1
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage, AIMessage
messages1 = [
HumanMessage("你好,我叫小明"),
AIMessage("很高兴认识你,小明"),
HumanMessage("你用一句话介绍下你自己"),
]
agent = create_agent(
model=model,
tools=[],
)
response1 = agent.invoke({"messages" : messages1})
for msg in response1["messages"]:
msg.pretty_print()
messages2 = [HumanMessage("我叫什么名字?")]
response2 = agent.invoke({"messages" : messages2})
for msg in response2["messages"]:
msg.pretty_print()
## 情况2
messages3 = [
HumanMessage("你好,我叫小明"),
AIMessage("很高兴认识你,小明"),
HumanMessage("你用一句话介绍下你自己"),
AIMessage("我是一个由 OpenAI 训练的人工智能助手,可以帮你回答问题、写作和解决各种任务。"),
HumanMessage("我叫什么名字?")
]
response3 = agent.invoke({"messages" : messages3})
for msg in response3["messages"]:
msg.pretty_print()
49.2 如何解决记忆问题
49.2.1 上下文工程
在 LangChain 中, 记忆(Memory) 就是专门负责" 存储历史交互信息 "的组件,核心作用是「 保存上下文 」和「 提供上下文 」,让LLM在每次响应时,都能"看到"之前的对话内容。
上下文工程(Context Engineering) 负责 "合理组织"这些记忆和任务信息 ,让LLM的响应更连贯、更贴合需求。这也是Agent能实现 复杂多轮交互 的核心基础。
49.2.2 上下文类型及相关的API
LangChain的上下文工程是基于Agent讨论的,而上下文工程是构建在LangGraph之上的。
| 上下文类型 | 描述 | 可变性 | 生命周期 | 访问方法 |
|---|---|---|---|---|
| 动态运行时上下文 | 在单次运行中会演变的可变数据 | 动态 | 单次运行 | LangGraph state对象 |
| 动态跨会话上下文 | 在对话间共享的持久数据。比如用户偏好、历史洞察、知识条目 | 动态 | 跨对话 | LangGraph store对象 |
| 静态运行时上下文 | 在启动时传入的用户元数据、工具、数据库连接 | 静态 | 单次运行 | LangGraph context对象 |
49.2.3 LangChain的记忆
49.2.3.1 记忆的分类
- 短期记忆(Short-term memory、会话级记忆、thread-scoped memory):作用范围是单个对话线程(Thread)内,一旦开启新对话(更换 thread_id ),记忆即消失
- 长期记忆(Long-term memory,跨会话级记忆 ):在会话间存储用户特定或应用级数据, 并在 会话线程间共享 。它可以随时在任何线程中被调用。记忆的范围是任意自定义命名空间,而不仅仅是单一线程 ID
49.2.3.2 记忆的管理
在LangChain v0.x版本中,通过专用的xxxMemory类管理记忆。
在LangChain v1.x版本中,Agent是构建在LangGraph图结构之上的,通过上文提到的state和store构建记忆系统。使用更简单、功能更统一
- state:短期记忆对象,以 会话 为单位组织,包含当前会话的所有消息记录以及自定义信息
- store:长期记忆对象, 跨会话持久化 的数据
50. 短期记忆
State(会话内部状态) + Checkpointer(持久化机制) + Thread ID(会话作用域)
- State :默认 存储历史消息列表messages ,通过State 管理历史消息
- Checkpointer :负责将State 作为检查点持久化保存,检查点是某个时刻的State 快照
这就像玩 RPG 游戏时的"自动存档":你不需要手动保存,系统在关键节点自动记录,下次进入游戏随时可以从上次的存档点继续。
50.1 基于内存的持久化器
jupyter
# 短期记忆
## 1、基于内存的持久化器
### 1.1 举例1:没有记忆
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=os.getenv("CLOSEAI_API_KEY"),
base_url=os.getenv("CLOSEAI_BASE_URL")
)
from langchain.agents import create_agent
from langchain.messages import HumanMessage
agent = create_agent(
model=model,
tools=[]
)
print("\n第一轮对话:")
response1 = agent.invoke({
"messages": [HumanMessage("我叫张三")]
})
print(f"Agent: {response1['messages'][-1].content}")
print("\n第二轮对话:")
response2 = agent.invoke({
"messages": [HumanMessage("我叫什么?")]
})
print(f"Agent: {response2['messages'][-1].content}")
### 1.2 举例2:拥有记忆
from langchain.agents import create_agent
from langchain.messages import HumanMessage
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver() # 1、创建了内存级的记忆存储
agent = create_agent(
model=model,
tools=[],
checkpointer=checkpointer, # 2、让agent具备了存储的能力
)
# 3、同一个thread_id共享记忆的
config = {
"configurable": {
"thread_id": 1
}
}
print("\n第一轮对话:")
# response1 = agent.invoke({
# "messages": [HumanMessage("我叫张三")],
# })
response1 = agent.invoke(
{
"messages": [HumanMessage("我叫张三")]
},
config=config # 4、传入invoke()当中
)
print(f"Agent: {response1['messages'][-1].content}")
print("\n第二轮对话:")
# response2 = agent.invoke({
# "messages": [HumanMessage("我叫什么?")]
# })
response2 = agent.invoke(
{
"messages": [HumanMessage("我叫什么?")]
},
config=config
)
print(f"Agent: {response2['messages'][-1].content}")
from rich import print as rprint
thread_1_state = agent.get_state(config)
rprint(thread_1_state)
# print("\n第二轮对话:")
print("\n第三轮对话:")
# response2 = agent.invoke(
response3 = agent.invoke(
{
"messages": [HumanMessage("我刚才问了什么问题?")]
},
config=config
)
print(f"Agent: {response2['messages'][-1].content}")
from rich import print as rprint
thread_1_state = agent.get_state(config)
rprint(thread_1_state)
继续:体会:不同的thread_id不共享记忆
config1 = {
"configurable": {
"thread_id": 2
}
}
# print("\n第三轮对话:")
print("\n第四轮对话:")
response3 = agent.invoke(
{
"messages": [HumanMessage("我叫什么?")]
},
# config=config
config=config1
)
print(f"Agent: {response2['messages'][-1].content}")
thread_2_state = agent.get_state(config1)
rprint(thread_2_state)
50.1.1 关键步骤说明
第1步:初始化记忆引擎: checkpointer = InMemorySaver() ------创建一个内存级的记忆存储。
注意:InMemorySaver内存中保存,进程结束就丢失数据,适合测试。生产环境可换成数据库持久化的 SqliteSaver 、 PostgresSaver 等
第2步:绑定 Agent:在 create_agent 时传入 checkpointer ,让 Agent 具备状态存储能力。
第3步:设定会话 ID:通过 config = {"configurable": {"thread_id": "1"}} 为每次调用指定线程标识。 同一个 thread_id 共享记忆,不同 thread_id 完全隔离。
python
# 会话 1
config1 = {"configurable": {"thread_id": "1"}}
agent.invoke({...}, config=config1)
# 会话 2
config2 = {"configurable": {"thread_id": "2"}}
agent.invoke({...}, config=config2)
# 两个会话完全独立
生产环境中:
- 场景1:多用户聊天
python
agent = create_agent(
model=model,
tools=[],
checkpointer=InMemorySaver()
)
# 用户 Alice
config_alice = {"configurable": {"thread_id": "user_alice"}}
agent.invoke({"messages": [...]}, config_alice)
...
# 用户 Bob
config_bob = {"configurable": {"thread_id": "user_bob"}}
agent.invoke({"messages": [...]}, config_bob)
...
# 两个会话完全独立
- 场景2:同一用户的不同任务
python
# 任务 1:写代码
config_task1 = {"configurable": {"thread_id": "task_coding"}}
agent.invoke({"messages": [...]}, config_task1)
...
# 任务 2:写文档
config_task2 = {"configurable": {"thread_id": "task_docs"}}
agent.invoke({"messages": [...]}, config_task2)
...
50.1.2 工作原理
python
agent.invoke({"messages": [{"role": "user", "content": "你好"}]}, config)
# InMemorySaver 保存:
# {
# "thread_id": "xxx",
# "messages": [
# HumanMessage("你好"),
# AIMessage("你好!有什么可以帮助你的吗?")
# ]
# }
agent.invoke({"messages": [{"role": "user", "content": "天气"}]}, config)
# InMemorySaver 更新:
# {
# "thread_id": "xxx",
# "messages": [
# HumanMessage("你好"),
# AIMessage("你好!有什么可以帮助你的吗?"),
# HumanMessage("天气"),
# AIMessage("...")
# ]
# }
自动追加历史
python
# 你只需要传新消息
agent.invoke(
{"messages": [{"role": "user", "content": "新问题"}]},
config
)
此时,checkpointer 自动:
① 读取之前的历史
② 追加新消息
③ 调用模型(传入完整历史)
④ 保存新的历史
50.1.3 常见问题
1、为什么 Agent 不记得?
检查:
✅ 是否添加了 checkpointer=InMemorySaver() ?
✅ 是否传入了 config 参数?
✅ 两次调用的 thread_id 是否相同?
python
# ❌ 错误:没有 checkpointer
agent = create_agent(model=model, tools=[])
agent.invoke({...}) # 不会记住
# ❌ 错误:没有 config
agent = create_agent(model=model, tools=[], checkpointer=InMemorySaver())
agent.invoke({...}) # 不会记住
# ❌ 错误:thread_id 不同
agent.invoke({...}, config={"configurable": {"thread_id": "1"}})
agent.invoke({...}, config={"configurable": {"thread_id": "2"}}) # 不同会话
# ✅ 正确
agent = create_agent(model=model, tools=[], checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "1"}}
agent.invoke({...}, config)
agent.invoke({...}, config) # 记得!
2、InMemorySaver 会丢失数据吗?
会! InMemorySaver 只保存在内存中:
✅ 同一进程内有效(不支持跨进程共享)
❌ 程序重启后丢失(或进程重启后丢失)
❌ 不同进程无法共享
解决方案:持久化(SQLite、PostgreSQL)
3、内存会无限增长吗?
会! 默认情况下,InMemorySaver 会保存所有消息。
问题:
- 消息越来越多(无限增长,需要管理上下文)
- token消耗增加,甚至会超过模型的 token 限制
- 响应速度变慢、成本增加
解决方案:上下文管理(修剪、摘要)
4、如何清空某个会话的历史?
目前 InMemorySaver 没有提供删除 API。
临时方案:
- 使用新的 thread_id
- 或重新创建 Agent
50.2 基于外部存储介质的持久化器
如果将 状态检查点(checkpointer) 保存在内存, 进程结束 则状态丢失,生产环境不可接受。因此,生产环境要用持久化的外部存储介质,如PostgreSQL。
50.2.1 数据库环境准备
在此之前,先准备好PostgreSQL环境,此处在云服务器的Ubuntu系统安装PostgreSQL。
- Linux云服务器安装:cloud.tencent.com/ ,镜像选择Ubuntu,使用 XShell登录
- 安装PostgreSQL:
安装命令:sudo apt install postgresql
验证:psql --version
查看状态:systemctl status postgresql
查看端口占用:sudo netstat -tunlp | grep postgres
启动客户端:sudo -u postgres psql
创建用户:create USER langchain_usr WITH PASSWORD 'abcd1234'
创建数据库:create DATABASE langchain_db OWNER 'langchain_user'
赋权:GRANT ALL PRIVILEGES ON DATABASE langchain_db TO langchain_user
查看数据表:\l (退出:\q)
测试URL:psql "postgresql://langchain_user:abcd1234@localhost:5432/langchain_db?sslmode=disable"
- 腾讯云安全组放通5432端口:
来源:0.0.0.0/0
协议端口:TCP 5432
- PostgreSQL监听所有IP
获取配置文件地址A:sudo -u postgres psql -c "SHOW config_file"
sudo vim A 将60行 listen_address 改为 '*'
- 添加允许规则
获取配置文件地址B:sudo -u postgres psql -c "SHOW hba_file"
sudo vim B 并最后写入:
host langchain_db langchain_usr 0.0.0.0/0 scram-sha-256
- 重启服务:
sudo systemctl restart postgresql
查看状态:sudo systemctl status postgresql
- 测试连接
psql postgresql://langchain_user:abcd1234@119.45.246.196:5432/langchain_db?sslmode=disable