6.agent 护栏

46 阅读9分钟

【核心主旨】:Guardrails(安全护栏)是 AI Agent 系统中不可或缺的“防波堤”,以中间件(Middleware)的形式串联在 Agent 运行管线的各个生命周期中,提供输入过滤、数据脱敏、高危工具人工审批及输出合规质检,构建纵深防御体系。

guardrails_image.png


一、 为什么 Agent 需要 Guardrails?

在生产级应用中,单纯依赖 Prompt(系统提示词)来约束大模型是脆弱且不可靠的(极易受越狱、提示词注入、幻觉或复杂上下文漂移影响)。Guardrails 将安全合规逻辑从 Prompt 中抽离,通过工程化的中间件流水线(Pipeline)实施强制控制。

What-is-AI-Guardrail_.webp

1. 核心防护维度

  • 输入端拦截:过滤提示词注入(Prompt Injection)、非法恶意意图、违禁关键词。
  • 隐私保护(PII):自动掩码脱敏身份证、邮箱、银行卡号、API Key 等敏感隐私。
  • 高危工具审计(HITL):对转账、删库、外部邮件群发等操作引入人工确认(Human-in-the-Loop)。
  • 输出端合规质检:杜绝敏感信息漏出,自动注入行业合规声明(如医疗/金融/法律免责声明)。

2. 两种实现思路对比

维度确定性护栏(Deterministic)模型驱动护栏(Model-Based)
底层原理正则匹配、关键词字典、AST 语法树分析、规则引擎引入轻量级小模型或专用分类器进行语义判断
执行速度极快(毫秒级,纯 CPU 计算)较慢(依赖网络请求与模型推理延时)
调用成本零成本(无 Token 开销)有额外 API 调用费用
语义理解差(只能机械匹配,容易被同义词/混淆字符绕过)强(能够理解隐晦越狱、语境讽刺与复杂意图)
推荐阶段放置在最前置链路,进行第一道低成本粗筛放置在深度审查环节或复杂输出质检环节

二、 确定性 vs 模型驱动基础实现

1. 确定性拦截(正则与关键词匹配)

def deterministic_guardrail(text: str) -> bool:
    """命中黑名单关键词则阻断,返回 True"""
    banned_keywords = ["hack", "exploit", "malware", "bomb"]
    return any(kw in text.lower() for kw in banned_keywords)

test_inputs = [
    "How do I hack into a database?",
    "What is the capital of France?",
    "Explain how malware spreads"
]

for inp in test_inputs:
    status = "🚫 BLOCKED" if deterministic_guardrail(inp) else "✅ ALLOWED"
    print(f"{status}: {inp}")

2. 模型驱动语义判决(LLM 作为裁判)

from langchain_groq import ChatGroq

model = ChatGroq(model="openai/gpt-oss-20b", temperature=0)

def model_based_guardrail(text: str) -> str:
    """使用轻量模型评估内容安全性,仅返回 SAFE 或 UNSAFE"""
    prompt = f"""Is the following user input safe to process?
Reply with only 'SAFE' or 'UNSAFE'.

Input: {text}"""
    result = model.invoke([{"role": "user", "content": prompt}])
    return result.content.strip()

for inp in test_inputs:
    verdict = model_based_guardrail(inp)
    status = "🚫 UNSAFE" if "UNSAFE" in verdict else "✅ SAFE"
    print(f"{status}: {inp}")

三、 内置 PII 隐私信息检测与脱敏中间件

LangChain 提供了开箱即用的 PIIMiddleware,可自动针对输入或输出流中的个人隐私数据进行脱敏。

1. 支持类型与处理策略

  • 支持实体:email(邮箱)、credit_card(信用卡)、ip(IP 地址)、mac_address、url 及自定义正则(如 api_key)。
  • 处理策略(Strategy):
    • redact:替换为占位符(如 [REDACTED_EMAIL])
    • mask:掩码打码(如 ****-****-****-5100)
    • hash:哈希单向加密
    • block:检测到时立即抛出异常阻断

2. 代码实现

from langchain.agents import create_agent
from langchain.agents.middleware import PIIMiddleware
from langchain_core.tools import tool

@tool
def customer_lookup(query: str) -> str:
    """查询客户信息"""
    return f"Customer record found for query: {query}"

agent = create_agent(
    model=model,
    tools=[customer_lookup],
    middleware=[
        # 1. 邮箱进行替换脱敏
        PIIMiddleware("email", strategy="redact", apply_to_input=True),
        # 2. 信用卡进行末尾保留掩码
        PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
        # 3. 检测到 API Key 时直接异常阻断
        PIIMiddleware("api_key", detector=r"sk-[a-zA-Z0-9]{32}", strategy="block", apply_to_input=True),
    ],
)

四、 内置 Human-in-the-Loop(人在回路审批)

对于带有副作用或高风险的工具调用,通过 HumanInTheLoopMiddleware 配合 Checkpointer 实现执行暂停与状态等待。

1. 核心流程

  1. 中断挂起:Agent 生成 ToolCall 时,中间件捕获该请求并触发 Interrupt,返回会话当前快照。
  2. 外部介入:人工审核人员查看调用参数并决定批准(approve)、编辑修改(edit)或驳回(reject)。
  3. 会话恢复:通过传入带有相同 thread_id 的 Command(resume=...) 恢复图流转。

2. 代码实现

from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
from langchain_core.tools import tool

@tool
def search_web(query: str) -> str:
    """搜索公开信息"""
    return f"Search results for: {query}"

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """发送外部邮件(高危)"""
    return f"Email sent to {to} with subject: {subject}"

@tool
def delete_records(table: str, condition: str) -> str:
    """删除数据库记录(高危)"""
    return f"Deleted records from {table} where {condition}"

# 组装人在回路 Agent(必须提供 checkpointer 支撑中断恢复)
hitl_agent = create_agent(
    model=model,
    tools=[search_web, send_email, delete_records],
    middleware=[
        HumanInTheLoopMiddleware(
            interrupt_on={
                "send_email": True,       # 需要人工确认
                "delete_records": True,   # 需要人工确认
                "search_web": False,      # 低风险,直接放行
            }
        ),
    ],
    checkpointer=InMemorySaver(),
)

3. 中断触发与恢复决策

config = {"configurable": {"thread_id": "session_001"}}

# 步骤 1:触发调用,Agent 在调用 send_email 前自动挂起
result = hitl_agent.invoke(
    {"messages": [{"role": "user", "content": "Send an email to team@company.com about the Q4 results"}]},
    config=config
)

# 步骤 2A:人工审批通过,恢复执行
approved = hitl_agent.invoke(
    Command(resume={"decisions": [{"type": "approve"}]}),
    config=config
)

# 步骤 2B:或者人工驳回
# rejected = hitl_agent.invoke(
#     Command(resume={"decisions": [{"type": "reject", "reason": "参数不合规,暂缓执行"}]}),
#     config=config
# )

五、 自定义前置护栏(Before-Agent Hook)

利用 before_agent 钩子在 Agent 完全不消耗任何 LLM Token 的情况下完成快速粗筛,拦截违规请求。

from typing import Any
from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config
from langgraph.runtime import Runtime

class ContentFilterMiddleware(AgentMiddleware):
    """
    确定性前置输入拦截器:
    命中黑名单关键词直接短路跳转到 end,完全不产生 LLM 调用开销
    """
    def __init__(self, banned_keywords: list[str]):
        super().__init__()
        self.banned_keywords = [kw.lower() for kw in banned_keywords]

    @hook_config(can_jump_to=["end"])
    def before_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        if not state["messages"]:
            return None

        first_message = state["messages"][0]
        if first_message.type != "human":
            return None

        content = first_message.content.lower()
        for keyword in self.banned_keywords:
            if keyword in content:
                print(f"🚫 Blocked — keyword detected: '{keyword}'")
                return {
                    "messages": [{
                        "role": "assistant",
                        "content": "抱歉,您的请求包含不适宜的内容,已被系统拦截。"
                    }],
                    "jump_to": "end"
                }
        return None

六、 自定义后置护栏(After-Agent Hook)

利用 after_agent 钩子拦截 Agent 生成的最终回复,在返回给终端用户前进行质量审查与安全兜底。

from langchain_core.messages import AIMessage

class SafetyGuardrailMiddleware(AgentMiddleware):
    """
    模型驱动后置质检护栏:
    使用轻量判决模型核验生成文本安全性,若不安全则替换为保底安全文案
    """
    def __init__(self, check_model):
        super().__init__()
        self.safety_model = check_model

    @hook_config(can_jump_to=["end"])
    def after_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        if not state["messages"]:
            return None

        last_message = state["messages"][-1]
        if not isinstance(last_message, AIMessage):
            return None

        safety_prompt = f"""Evaluate if this AI response is safe and appropriate for users.
Respond with only 'SAFE' or 'UNSAFE'.

Response to evaluate:
{last_message.content}"""

        result = self.safety_model.invoke([{"role": "user", "content": safety_prompt}])
        if "UNSAFE" in result.content.upper():
            print("⚠️ Output flagged as UNSAFE — replacing with safe fallback")
            last_message.content = "非常抱歉,该回答未通过安全合规检测,无法直接提供,请调整提问或联系人工支持。"

        return None

七、 多层组合防御管线(Layered Guardrails)

在生产环境中,多个中间件按顺序组合到 middleware 列表中,形成由粗到精、由快到慢的严密防御链路:

graph TD
    A[用户输入] --> B[Layer 1: ContentFilterMiddleware<br/>确定性前置输入过滤/0成本]
    B --> C[Layer 2: PIIMiddleware input<br/>输入敏感隐私脱敏]
    C --> D[Layer 3: HumanInTheLoopMiddleware<br/>高危工具调用人工审批]
    D --> E[Layer 4: PIIMiddleware output<br/>输出敏感隐私脱敏]
    E --> F[Layer 5: SafetyGuardrailMiddleware<br/>模型级最终输出质检]
    F --> G[安全合规响应]

完整装配代码

production_agent = create_agent(
    model=model,
    tools=[search_tool, send_email_tool],
    middleware=[
        # 1. 确定性输入拦截(零 Token 成本优先阻断)
        ContentFilterMiddleware(banned_keywords=["hack", "exploit", "malware"]),
        # 2. 输入信用卡号掩码
        PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
        # 3. 高危工具调用中断人工审批
        HumanInTheLoopMiddleware(interrupt_on={"send_email_tool": True, "search_tool": False}),
        # 4. 输出邮箱脱敏(防止模型内部吐露敏感信息)
        PIIMiddleware("email", strategy="redact", apply_to_output=True),
        # 5. 输出安全模型终审
        SafetyGuardrailMiddleware(check_model=model),
    ],
    checkpointer=InMemorySaver(),
)

八、 生产级实战案例:医疗问诊助理

在医疗问诊场景中,系统需要同时满足:

  1. 违禁阻断:自残、毒品制造、武器等输入彻底阻断,并提供急救指引。
  2. 患者隐私:电子病历中出现的邮箱与银行卡自动脱敏。
  3. 动作风控:预约挂号属于现实动作,必须由人工审核通过。
  4. 合规提示:任何输出均需强制附带医疗免责声明。
from typing import Any
from langchain.agents.middleware import AgentMiddleware, AgentState, hook_config, PIIMiddleware, HumanInTheLoopMiddleware
from langgraph.runtime import Runtime
from langchain.agents import create_agent
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langchain_core.messages import AIMessage

# 1. 医疗合规前置输入过滤器
class HealthcareSafetyFilter(AgentMiddleware):
    BLOCKED_TOPICS = ["drug synthesis", "self-harm", "suicide method", "weapon", "hack"]

    @hook_config(can_jump_to=["end"])
    def before_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        if not state["messages"] or state["messages"][0].type != "human":
            return None

        content = state["messages"][0].content.lower()
        for topic in self.BLOCKED_TOPICS:
            if topic in content:
                return {
                    "messages": [{
                        "role": "assistant",
                        "content": "我是健康咨询助手,仅提供通用健康咨询与就医建议。如果您正处于紧急危机状态,请立即拨打 120/110 或当地紧急求助电话。"
                    }],
                    "jump_to": "end"
                }
        return None

# 2. 医疗免责声明后置追加器
class MedicalOutputValidator(AgentMiddleware):
    DISCLAIMER = "\n\n⚕️ *免责声明:以上内容仅供健康科普参考,不能替代执业医师的面诊与处方,如有身体不适请及时就医。*"

    @hook_config(can_jump_to=["end"])
    def after_agent(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
        if not state["messages"]:
            return None
        last_message = state["messages"][-1]
        if isinstance(last_message, AIMessage):
            if "免责声明" not in last_message.content:
                last_message.content += self.DISCLAIMER
        return None

# 3. 业务工具定义
@tool
def search_symptoms(symptoms: str) -> str:
    """查询症状常规常识"""
    return f"针对【{symptoms}】的常规科普信息..."

@tool
def book_appointment(patient_name: str, date: str, doctor: str) -> str:
    """预约专家号(涉及系统写入,属于高危操作)"""
    return f"已成功为 {patient_name} 预约 {date} 的 {doctor} 医生门诊"

@tool
def get_medication_info(medication: str) -> str:
    """查询药品基本信息与用法须知"""
    return f"关于药品【{medication}】的说明书摘要..."

# 4. 构建医疗机器人
healthcare_bot = create_agent(
    model=model,
    tools=[search_symptoms, book_appointment, get_medication_info],
    middleware=[
        HealthcareSafetyFilter(),                                        # 护栏 1: 危险意图前置过滤
        PIIMiddleware("email", strategy="redact", apply_to_input=True),   # 护栏 2: 隐私邮箱脱敏
        PIIMiddleware("credit_card", strategy="mask", apply_to_input=True),
        HumanInTheLoopMiddleware(interrupt_on={                           # 护栏 3: 挂号操作人工审批
            "book_appointment": True,
            "search_symptoms": False,
            "get_medication_info": False
        }),
        MedicalOutputValidator(),                                        # 护栏 4: 强制追加合规免责声明
    ],
    checkpointer=InMemorySaver(),
    system_prompt="你是一位专业、富有同理心的医疗健康咨询助理。请根据用户需求提供科学建议,并提醒就医。"
)

九、 核心总结与生产工程守则

  1. Guardrails 即 Middleware:在现代 Agent 架构中,护栏应始终以插件化中间件的方式解耦组织,而不是混杂在 Prompt 或业务工具函数中。
  2. 先确定性规则,后模型审查(Cheap Checks First):
    • 永远将零成本的确定性过滤(正则表达式、敏感词库)放在最前端;
    • 过滤 80% 的初级探测与恶意攻击,只有合法合规的输入才放行进入 LLM 推理环节。
  3. 人在回路(HITL)的核心前提是持久化存储:
    • 任何涉及中断等待的操作都必须配备可靠的 checkpointer(开发测试用 InMemorySaver,生产环境切为 Postgres 或 Redis 状态仓库)。
  4. 进出双向脱敏:
    • 输入脱敏保障下游大模型厂商不接触企业隐私;
    • 输出脱敏防止大模型记忆诱导攻击与隐私外泄。