现在位置: 首页 > LangChain 教程 > 正文

LangChain 中间件钩子 -- @before_model 与 @after_model

before_model 和 after_model 是最常用的两个 中间件(Middleware) 钩子。它们在每次模型调用前后执行,适合做内容过滤、消息预处理、响应审核等。


@before_model——模型调用前拦截

@before_model 在每次调用模型之前执行。你可以在这里修改消息、注入上下文条件、或直接跳过模型调用。

场景 1:消息预处理——限制对话长度

实例

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import create_agent
from langchain.agents.middleware import before_model
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage


@before_model
def limit_context(state, runtime):
    """限制消息历史长度,防止上下文过长"""
    messages = state.get("messages", [])

    # 保留 system message + 最近 6 条消息
    MAX_MESSAGES = 6
    if len(messages) > MAX_MESSAGES:
        # 裁剪到最近的消息
        trimmed = messages[-MAX_MESSAGES:]
        # 确保第一条是用户消息
        if trimmed and trimmed[0].type != "human":
            trimmed = trimmed[1:]
        return {"messages": trimmed}

    return None


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
    model=model,
    middleware=[limit_context],
    system_prompt="你是菜鸟教程 RUNOOB 的助手。",
)

# 模拟多轮对话
result = agent.invoke({
    "messages": [
        HumanMessage(content="第一轮"),
        HumanMessage(content="第二轮"),
        HumanMessage(content="第三轮"),
        HumanMessage(content="第四轮"),
        HumanMessage(content="第五轮"),
        HumanMessage(content="第六轮"),
        HumanMessage(content="第七轮"),
    ]
})
print(f"消息数: {len(result['messages'])}")
print(f"回复: {result['messages'][-1].content}")

运行结果:

消息数: 8
回复: 你好!看起来你正在进行多轮对话测试。有什么可以帮你的吗?

场景 2:内容过滤——屏蔽敏感词

实例

from langchain.agents.middleware import before_model


SENSITIVE_WORDS = ["密码", "银行卡号", "身份证号"]


@before_model
def content_filter(state, runtime):
    """检查用户消息是否包含敏感词,如果包含则拦截"""
    messages = state.get("messages", [])
    if not messages:
        return None

    last_msg = messages[-1]
    content = str(last_msg.content) if hasattr(last_msg, 'content') else ""

    for word in SENSITIVE_WORDS:
        if word in content:
            print(f"[拦截] 检测到敏感词: {word}")
            # jump_to="end" 直接结束,不让模型回复
            return {
                "jump_to": "end",
                "messages": [
                    HumanMessage(content=f"抱歉,为了安全,不能处理包含「{word}」的请求。")
                ]
            }

    return None


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
    model=model,
    middleware=[content_filter],
    system_prompt="你是菜鸟教程 RUNOOB 的助手。",
)

# 正常问题
result = agent.invoke({
    "messages": [HumanMessage(content="Python 怎么入门?")]
})
print(f"正常问题: {result['messages'][-1].content[:80]}")

# 敏感问题
result = agent.invoke({
    "messages": [HumanMessage(content="我的银行卡号是多少?你能帮我查吗?")]
})
print(f"\n敏感问题: {result['messages'][-1].content}")

运行结果:

[拦截] 检测到敏感词: 银行卡号
正常问题: Python 入门可以从安装 Python 环境开始...

敏感问题: 抱歉,为了安全,不能处理包含「银行卡号」的请求。

@after_model——模型调用后处理

@after_model 在模型回复后执行,适合审核模型输出、提取关键信息、追加后续指令等。

场景 3:响应内容审核

实例

from langchain.agents.middleware import after_model


FORBIDDEN_TOPICS = ["政治", "暴力", "色情"]


@after_model
def response_audit(state, runtime):
    """审核模型回复,如果涉及禁止话题则替换"""
    messages = state.get("messages", [])
    if not messages:
        return None

    last_msg = messages[-1]
    content = str(last_msg.content) if hasattr(last_msg, 'content') else ""

    for topic in FORBIDDEN_TOPICS:
        if topic in content:
            runtime.stream_writer({
                "type": "warning",
                "message": f"检测到回复包含「{topic}」相关内容,已被替换"
            })
            # 返回一个覆盖的消息(通过 add_messages reducer)
            from langchain.messages import AIMessage
            return {
                "messages": [
                    AIMessage(content="抱歉,我无法回答这个问题。"
                                      "请询问编程学习相关的内容。")
                ]
            }

    return None


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
    model=model,
    middleware=[response_audit],
    system_prompt="你是菜鸟教程 RUNOOB 的助手。",
)

result = agent.invoke({
    "messages": [HumanMessage(content="Python 有哪些学习资源?")]
})
print(f"正常回复: {result['messages'][-1].content}")

场景 4:自动追加提示信息

实例

from langchain.agents.middleware import after_model
from langchain.messages import AIMessage


@after_model
def append_disclaimer(state, runtime):
    """在每次模型回复后自动追加免责声明"""
    messages = state.get("messages", [])
    if not messages:
        return None

    last_msg = messages[-1]

    # 只在 AI 的最终回复(没有 tool_calls)时追加
    if (last_msg.type == "ai"
        and last_msg.content
        and not (hasattr(last_msg, 'tool_calls') and last_msg.tool_calls)):
        # 替换最后一条 AI 消息,加上免责声明
        return {
            "messages": [
                AIMessage(
                    content=(
                        last_msg.content
                        + "\n\n---\n*以上内容由菜鸟教程 RUNOOB AI 助手生成,仅供参考。*"
                    )
                )
            ]
        }

    return None


model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
    model=model,
    middleware=[append_disclaimer],
    system_prompt="你是菜鸟教程的助手。",
)

result = agent.invoke({
    "messages": [HumanMessage(content="Python 容易学吗?")]
})
print(result["messages"][-1].content)

运行结果:

Python 是一门非常适合初学者的编程语言,它的语法简洁、贴近自然语言...
---
*以上内容由菜鸟教程 RUNOOB AI 助手生成,仅供参考。*

can_jump_to——流程跳转控制

在 before_model 和 after_model 中,你可以通过 can_jump_to 参数和 jump_to 状态来控制 Agent 的流程:

实例

from langchain.agents.middleware import before_model


@before_model(can_jump_to=["end"])  # 声明可以跳转到的目标
def conditional_exit(state, runtime):
    """在特定条件下直接结束 Agent"""
    messages = state.get("messages", [])
    if not messages:
        return None

    # 如果用户说"再见",直接结束
    last_content = str(messages[-1].content)
    if last_content.strip() in ["再见", "拜拜", "bye"]:
        return {
            "jump_to": "end",  # 直接结束 Agent
            "messages": [{"role": "assistant", "content": "再见!期待下次为您服务。"}]
        }

    return None
can_jump_to 值含义适用场景
["end"]可跳转到结束条件退出、安全拦截
["model"]可跳转回模型需要让模型重新处理
["tools"]可跳转到工具节点跳过模型直接执行工具
["model", "end"]可跳转到模型或结束多种条件分支

如果不在 can_jump_to 中声明目标,jump_to 会被忽略。这是一种安全机制,防止中间件意外跳转到不合法的节点。