上一篇我们介绍了 Human-in-the-loop,它解决的是很具体的问题:当 Agent 准备执行高风险操作(如退款、发送邮件、修改数据等)时,可以暂停执行,把最终决定交给人工。

如果继续深入 Agent 安全性,会发现 HITL 只是 Agent 安全控制中的一种方式。用户输入中的敏感信息、模型生成的内容、Tool 的参数,以及哪些操作允许继续执行,都需要有对应的控制边界。

这些边界可以统称为 Guardrails。

在 LangChain 中,Guardrails 主要通过 Middleware 插入 Agent 的不同执行阶段,对输入、输出和工具行为进行检查、限制或拦截。本文重点介绍 Guardrails 的主要类型、应该放在哪里,以及实际项目中怎样组合使用。

一、Guardrails介绍

Guardrails 的字面含义是护栏,可以理解为运行在 Agent 执行过程中的安全检查与约束机制。它关注的是 Agent 从接收请求到返回结果的整个过程中,哪些内容能够通过、哪些操作能够执行,以及出现风险时应该怎样处理。

常见场景包括:

  • 防止个人敏感信息泄露;
  • 检测异常或恶意输入;
  • 拦截不合适的模型输出;
  • 执行业务规则和合规要求;
  • 限制高风险工具调用。

LangChain 主要通过 Middleware 实现这些 Guardrails。因为 Middleware 可以进入 Agent 执行过程中的不同位置,以实现在相应位置进行安全检查与控制。

例如:

代码片段Text
用户请求

输入检查

PII 脱敏

模型推理

工具调用检查

模型最终回答

输出检查

返回用户

这里每一个检查点,都可以看作一道 Guardrail。风险发生在哪里,就应该尽可能在靠近风险的位置设置边界。

有些安全要求当然也可以写进 System Prompt,但 Prompt 更适合告诉模型“应该怎么做”;对于权限、隐私和高风险操作这类不能被绕过的规则,更适合在代码和 Middleware 层进行限制。

二、两类Guardrails

根据判断方式不同,Guardrails 可以分成确定性 Guardrails 和模型型 Guardrails。

确定性 Guardrails 使用普通程序逻辑完成判断,例如:

代码片段Text
正则表达式
关键词匹配
权限判断
金额阈值
白名单
参数校验

比如:

代码片段Python
if user.role != "admin":
    reject_operation()
 
if amount > 1000:
    require_review()

这种方式最大的特点是结果可预测。

相同输入通常会得到相同结果,而且执行快,不需要额外调用模型。邮箱、信用卡号、用户权限、金额阈值,都很适合这种方式。

确定性 Guardrails 不适合复杂语义。

例如:

代码片段Text
帮我设计一种绕过公司内部审核流程的方法。

真正需要判断的是意图,而不是某几个固定关键词。

另一个 Guardrials 是模型型Guardrails,这类 Guardrail 会调用 LLM 或分类模型进行语义判断,例如:

代码片段Text
这段回答是否符合金融合规规则?
这个请求是否包含隐蔽的恶意意图?
最终回复是否泄露内部信息?

它更适合复杂语义,但会增加模型调用、延迟和成本,而且结果仍然具有概率性。

在实际项目中可以遵循一个简单原则:能通过代码明确判断的,不交给模型;只有规则难以描述时,再使用模型判断。

三、Guardrails放在哪里

前面的文章中,我们多次提到 Agent 通常运行在一个循环中:

代码片段Text
用户输入

模型调用

模型决定是否使用工具

工具执行

结果返回模型

继续推理

最终回答

Guardrails 要控制这个过程,就需要插入不同阶段。

在 LangChain 中,Middleware 提供了多个 Hook:

代码片段Text
before_agent

before_model

Model

after_model

Tool

再次进入 Model

after_agent

其中,before_agentafter_agent 围绕一次完整 Agent 调用执行;before_modelafter_model 位于 Agent Loop 内部,因此可能执行多次。

另外还有 wrap_model_callwrap_tool_call,可以直接包裹模型或工具调用。

不同位置适合处理不同问题。

身份和请求级权限,可以放在 before_agent;不允许进入模型的数据,应在模型调用前处理;工具权限和参数校验,应靠近 Tool;最终内容审核,则适合放在 after_agent

四、处理敏感信息

LangChain 内置了 PIIMiddleware,用于检测和处理个人身份信息(Personally Identifiable Information,PII)。

当前内置类型包括邮箱、信用卡、IP 地址、MAC 地址和 URL,也支持通过正则表达式或自定义检测函数扩展。

检测到敏感信息后,可以选择:

策略处理方式
redact用占位符替换
mask隐藏部分内容
hash转换成稳定哈希
block阻止继续执行

例如,不希望用户邮箱直接发送给模型:

代码片段Python
from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware
 
agent = create_agent(
    model="gpt-5.5",
    tools=[],
    middleware=[
        PIIMiddleware(
            "email",
            strategy="redact",
            apply_to_input=True,
        )
    ],
)
 
result = agent.invoke({
    "messages": [{
        "role": "user",
        "content": "我的邮箱是 test@example.com,请帮我记录。"
    }]
})

经过 PIIMiddleware 处理后,模型实际接收到的输入已经发生变化。

处理后类似于:

代码片段Text
我的邮箱是 [REDACTED_EMAIL],请帮我记录。

也就是说,敏感信息是在进入模型之前被处理,而不是等模型回答之后再删除。

PIIMiddleware 还可以通过 apply_to_output 检查模型输出,通过 apply_to_tool_results 检查工具返回结果。

五、自定义安全边界

PII 只是一个通用场景,实际项目中的 Guardrails 往往需要自己实现。

例如,一个内部 Agent 不允许处理包含特定危险指令的请求,可以在 before_agent 中拦截。

代码片段Python
from typing import Any
 
from langchain.agents import create_agent
from langchain.agents.middleware import AgentState, before_agent
from langgraph.runtime import Runtime
 
 
@before_agent(can_jump_to=["end"])
def content_filter(
    state: AgentState,
    runtime: Runtime,
) -> dict[str, Any] | None:
 
    if not state["messages"]:
        return None
 
    message = state["messages"][0]
    content = str(message.content).lower()
 
    if "drop database" in content:
        return {
            "messages": [{
                "role": "assistant",
                "content": "该请求已被安全策略阻止。"
            }],
            "jump_to": "end",
        }
 
    return None
 
 
agent = create_agent(
    model="gpt-5.5",
    tools=[],
    middleware=[content_filter],
)

调用:

代码片段Python
result = agent.invoke({
    "messages": [{
        "role": "user",
        "content": "请执行 DROP DATABASE production"
    }]
})

运行结果:

代码片段Text
该请求已被安全策略阻止。

这里比较重要的是:

代码片段Python
"jump_to": "end"

它意味着 Middleware 不只是修改内容,还可以直接改变 Agent 的执行路径。

代码片段Text
before_agent

发现违规

jump_to="end"

结束执行

此时模型不会继续被调用。

当然,这个关键词示例只是为了说明机制。实际项目如果要限制 SQL 删除操作,更可靠的方式通常是在 SQL Tool 或工具调用附近检查真正准备执行的语句。

六、检查最终输出

有些风险只有 Agent 完成任务以后才能判断。

例如研究 Agent 调用了多个工具,最终生成一段完整回答,这时可能需要检查:

代码片段Text
最终内容是否符合公司政策?
是否泄露敏感信息?
是否满足业务规范?

这类检查适合放在 after_agent

如果规则比较复杂,可以使用模型型 Guardrail:

代码片段Python
from typing import Any
 
from langchain.agents.middleware import AgentState, after_agent
from langchain.chat_models import init_chat_model
from langchain.messages import AIMessage
from langgraph.runtime import Runtime
 
 
safety_model = init_chat_model("gpt-5.4-mini")
 
 
@after_agent(can_jump_to=["end"])
def safety_guardrail(
    state: AgentState,
    runtime: Runtime,
) -> dict[str, Any] | None:
 
    last_message = state["messages"][-1]
 
    if not isinstance(last_message, AIMessage):
        return None
 
    result = safety_model.invoke([{
        "role": "user",
        "content": f"""
判断下面的回答是否安全。
只返回 SAFE 或 UNSAFE。
 
回答:
{last_message.content}
"""
    }])
 
    if "UNSAFE" in str(result.content):
        last_message.content = "该回答无法提供。"
 
    return None

模型判断具有一定随机性,运行结果:

如果安全模型返回:

代码片段Text
UNSAFE

用户最终看到:

代码片段Text
该回答无法提供。

这就是模型型 Guardrail。

它能够理解复杂语义,但代价是额外模型调用。

七、高风险行为控制

前面的 Guardrails 主要控制数据和内容,但 Agent 更大的风险往往来自行为本身

例如模型判断:

代码片段Text
订单 A1024 应该退款 899 元。

即使这个判断完全正确,也不意味着 Agent 应该自动拥有退款权限。

对于发送邮件、修改数据库、删除文件、退款、转账等行为,需要控制的是:谁拥有最终执行权。

上一篇已经重点介绍了 Human-in-the-loop,这里不再展开 Interrupt、Resume 等具体流程。

从 Guardrails 的角度看,HITL 可以理解为一种行为边界:当某个 Tool 属于高风险操作时,通过 HumanInTheLoopMiddleware 暂停执行,把最终决定交给人工。

代码片段Text
普通查询 Tool

自动执行

退款 Tool

Guardrail

人工 approve / edit / reject

决定是否执行

因此 Guardrails 和 HITL 不是两个完全独立的概念。

更准确地说:

代码片段Text
Guardrails
├── 输入检查
├── PII保护
├── 输出检查
├── 业务规则
├── 模型安全判断
└── Human-in-the-loop

HITL 是 Guardrails 中针对高风险行为的一种控制方式。

八、组合多层Guardrails

真实项目通常不会只有一道 Guardrail。

不同 Middleware 可以分别承担自己的安全职责。例如一个客服 Agent:

代码片段Text
用户请求

请求检查

PII脱敏

模型推理

工具规则检查

必要时人工审批

工具执行

最终输出检查

返回用户

代码结构可能类似:

代码片段Python
agent = create_agent(
    model="gpt-5.5",
    tools=[
        search_tool,
        get_order_tool,
        send_email_tool,
    ],
    middleware=[
        content_filter,
        PIIMiddleware(
            "email",
            strategy="redact",
            apply_to_input=True,
        ),
        HumanInTheLoopMiddleware(
            interrupt_on={
                "send_email": True,
            }
        ),
        safety_guardrail,
    ],
)

运行结果

普通查询可能经过:

代码片段Text
输入检查
→ PII检查
→ Model
→ Search Tool
→ Model
→ 输出检查
→ 返回

高风险操作则可能经过:

代码片段Text
输入检查
→ PII检查
→ Model
→ 高风险 Tool
→ HITL
→ Tool执行
→ Model
→ 输出检查
→ 返回

从这个过程可以看到,一个完整的 Guardrails 体系并不是最后统一加一个“安全模型”。

更合理的判断方式是:

代码片段Text
能否通过确定规则判断?

能 → 使用代码规则

不能

是否属于高风险行为?

是 → 考虑人工审核

不是

使用模型做语义判断

同时还要确认这个问题:风险真正出现在哪里?

如果敏感数据不能进入模型,就在模型调用前处理;如果风险来自 Tool,就应该在 Tool 附近设置边界。

总结

Guardrails 是分布在 Agent 执行过程中的多道安全边界。确定性问题优先交给代码,复杂语义再使用模型,高风险行为则可以结合 HITL。

设计时最重要是判断风险发生在哪里、谁应该负责判断,以及应该在什么位置阻止它继续传播或执行。把边界放在真正产生风险的位置,Guardrails 才能成为可靠的系统约束。