---
title: "Agent 护栏怎么做？用是非概率拦截危险工具调用和违规内容"
description: "护栏框架告诉你在哪里拦，却没说凭什么拦。本文讲清怎么把审核政策拆成是非问题，按概率分三段放行、转人工或拦截，附在线示例。"
canonical: "https://system-one.dev/zh-CN/blog/agent-guardrails"
language: "zh-CN"
---

# Agent 护栏怎么做？用是非概率拦截危险工具调用和违规内容

**什么是 Agent 护栏？** Agent 护栏是在用户输入、模型输出和工具调用前后自动运行的检查，它决定这一步是继续、转人工审核，还是直接拦截。护栏框架负责“在哪里拦”，“凭什么拦”还需要一个可靠的判断。

**怎么用是非概率做审核和拦截？** 把审核政策拆成几个是非问题，例如“是否针对特定群体”“这次工具调用是否会改动生产数据”，让决策模型为每个问题返回为真的概率；需要区分严重程度时，再加一个评分问题。代码按误判成本设两个阈值：高于上限就拦截，处于中间就转人工，低于下限就放行。概率不是授权，高风险操作仍然需要权限检查和人工审批。

Choice、Score、Noul 三种原语的基础知识见 [决策模型是什么？](https://system-one.dev/zh-CN/blog/what-is-a-decision-model)。

## Agent 护栏是什么：输入、输出、工具三类检查点

OpenAI Agents SDK（JS）的文档把护栏分成三类：

- **输入护栏**：针对初始用户输入运行，只对第一个接收用户输入的 Agent 生效。
- **输出护栏**：针对 Agent 的最终输出运行，只在生成最终输出的 Agent 上运行。
- **工具护栏**：在每次自定义函数工具调用前后运行。工具输入护栏可以返回 `allow`（继续）、`rejectContent`（用一条消息跳过这次调用）或 `throwException`（抛出熔断错误）。

文档还提到两种执行方式：输入护栏默认与 Agent 并行运行（`runInParallel: true`），延迟最低，但护栏触发时模型可能已经消耗了 token 或运行了工具；设为 `false` 则在调用模型之前先跑完护栏。文档还建议，可以先运行一个轻量级模型作为护栏，再调用成本较高的模型。

阿里云的 AgentScope 集成指南给出了另一组拦截点：`pre_reply` 在 Agent 回复前检测用户输入；`post_reasoning` 在推理完成后检测最终输出；toolkit middleware 在工具执行前检测“工具名 + 参数”，违规就阻止调用。

护栏和人工审批是两件事。OpenAI Agents SDK 的人工审批通过工具的 `needsApproval` 实现：需要审批时运行暂停，返回 `interruptions`，批准或拒绝后从同一个 `RunState` 恢复。默认情况下，工具输入护栏在审批通过后、工具执行前运行；设置 `toolExecution: { preApprovalInputGuardrails: true }` 后，护栏还会在发出审批请求之前先跑一次。

这些框架把“检查点放哪”讲得很清楚，但护栏函数内部**凭什么判断**，要你自己决定。

## 护栏的判断用什么做

常见的选择有四种：

- **规则和正则**：确定、便宜，适合格式明确的东西，比如 OpenAI 文档示例里拦截参数中含 `sk-` 的密钥。但它覆盖不了换个说法的内容。
- **再调一次生成式 LLM**：OpenAI 文档的输入护栏示例就是在护栏里运行另一个 Agent，让它输出一个布尔字段。它能理解语义，但输出的是“是 / 否”，没有可调的阈值。
- **专用审核服务**：例如阿里云 AI 安全护栏，通过插件挂到 AgentScope 的各个拦截点上。审核类别和判定标准由服务方定义。
- **决策模型的 Noul**：为你自己写的是非问题返回 P(true)。你可以针对自己的政策写问题，并按误判成本设阈值。

四种方式可以组合使用：规则先挡掉格式明确的情况，Noul 判断需要理解语义的政策，高风险动作仍然走人工审批。

## 把政策拆成 Noul 问题

一条模糊的政策，比如“不要发布攻击性内容”，很难直接判断。把它拆成几个可以回答“是 / 否”的问题：

- 这段内容是否针对某个特定群体？
- 这段内容是否在索要密码、验证码等凭证？
- 这次工具调用是否会改动生产数据？

System One 的决策原语文档写明：Noul 估计“某个具体条件是否成立”，返回 P(true)，范围 0 到 1；如果多个条件可以同时成立，应为每个条件分别定义 Noul 问题，这些概率对应不同的命题，不要求相加等于一。SDK 里用 `booleanQuestion()` 定义，并用 criteria 写清楚 true 和 false 各指什么。Microsoft Learn 的 Microsoft-Decision-1 文档也建议用 noul 做“门禁和过滤”，示例问题是“这条回复是否承诺给客户退款”。

需要区分严重程度时，再加一个 Score 问题，把档位按顺序写出来，例如“安全 / 擦边 / 违规 / 必须删除”。System One 文档写明，托管 API 要求 2–10 个有序档位，分数是按各档概率加权得出的，不能当作正确率的百分比。

**在线试一下**：[推文会不会被审核](https://system-one.dev/zh-CN/demo/tweet-moderation)（示例结果，登录后可跑真实数据）

## 工具调用前拦截

把工具调用作为 state 送去判断。AgentScope 集成指南的做法是把工具名和参数拼成 `Tool: {name}\nArguments: {json}` 的格式送检，可以参考。state 里只放判断所需、允许分享的信息，不要放密钥。

如果你在用 OpenAI Agents SDK，这个判断放在工具输入护栏里：低风险放行时返回 `allow`；需要拒绝但让 Agent 继续时返回 `rejectContent`，并附一条说明；需要整个运行停下时用 `throwException`。付款、删除数据、对外发消息这类高风险工具，仍然设置 `needsApproval`，由人来批准。

System One 的 Agent 接入文档写明：模型结果不是授权，操作人的身份和权限、动作白名单、必要的审批都要在模型之外检查；答案缺失或请求失败表示“未解决”，不能当成自动的“是”或“否”。所以护栏调用出错时，应该按“未通过”处理，而不是放行。工具路由里的白名单思路，见 [Agent 工具太多总选错？用 Choice 做工具路由](https://system-one.dev/zh-CN/blog/agent-tool-routing)。

## 三段阈值与人工审核

问题按“是否违规”的方向写时：

- P(true) 高于上限 → 拦截；
- 介于上下限之间 → 转人工审核；
- 低于下限 → 放行。

如果问题写成“是否安全”，方向就反过来。两个阈值都不能照搬通用的百分比。System One 文档要求阈值来自你自己的评估案例和误判后果；Microsoft Learn 也建议按误报和漏报的代价来设阈值。漏拦代价高的场景（比如付款），上限要放低，让更多请求进入人工审核；误拦代价高的场景，人工区间可以收窄。具体怎么用标注样本选阈值、再用留出数据复核，参见 [用 Score 替代 LLM 当裁判](https://system-one.dev/zh-CN/blog/llm-as-a-judge-alternative)。Noul 阈值和动作的对应方式，也可以参考 [Agent 什么时候算完成？](https://system-one.dev/zh-CN/blog/agent-stop-condition)中的动作表。

Microsoft Learn 还给了几条建议：问题和选项用清晰、中立的措辞；不应强行选择时加入“cannot tell”这类弃权选项；可以随机化选项顺序，并检验顺序变化是否影响结果；涉及个人的重大决定（信贷、就业、住房、医疗、法律）只把模型当作决策支持，并保留有意义的人工复核。

## 评估：漏拦率和误拦率分开算

在自己的标注集上分别统计两件事：该拦没拦（漏拦），不该拦却拦了（误拦）。两者的代价不同，合成一个准确率会掩盖问题。System One 的 Agent 接入文档也要求把“错误选择”和“遗漏审核”分开统计，并加入试图强迫模型改变选择的输入。

测试集里应该包括：换个说法表达同一个意思的内容、提示注入（比如在参数里写“忽略之前的规则”）、措辞很冲但无害的内容。Microsoft Learn 列出的已知局限包括：评分会随问题和选项的措辞、顺序变化；用作安全过滤时，可能漏掉隐蔽的有害内容，也可能把无害内容标记出来。这些都只能靠自己的数据测出来。

## TypeScript 示例

开发团队的示例（`examples/decision-articles/08-agent-guardrails.ts`）用 `booleanQuestion()` 定义三个是非问题：是否针对特定群体、是否索要凭证或密钥、这次工具调用是否会改动生产数据或对外发消息。三个概率取最大值作为风险值，任何一条规则都可以触发护栏；风险值不低于 `BLOCK_IF_RISK_AT_LEAST`（0.8）时拦截，不低于 `REVIEW_IF_RISK_AT_LEAST`（0.4）时交给应用自己的人工审核回调，否则放行。两个阈值都是示例值，注释写明要在标注案例上调。示例注释还写明：P(true) 不是已确认的事实，也不是授权；System One 没有审核、审批或 `needsApproval` 接口。

```ts
/**
 * Article 8 — Agent guardrails ("Agent 护栏", /zh-CN/blog/agent-guardrails).
 *
 * Split a moderation policy into independent yes/no questions via
 * `booleanQuestion()` (wire type `noul`, SDK answer `.probability`). Those
 * probabilities do not sum to 1. This file combines them with max() — any
 * rule can trip the guardrail — then maps the combined risk onto two
 * application-owned cutoffs: block / human review / allow.
 *
 * P(true) is not a confirmed fact and is not authorization. Human review is
 * an application callback. System One has no moderation, approval, or
 * `needsApproval` API. Packages: @system-one-ai/core, adapter-system-one,
 * transport-fetch. Endpoint: POST /v1/systemone.
 */
import { createSystemOne, booleanQuestion } from '@system-one-ai/core';
import type { EvaluationClient } from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

export const guardrailQuestions = {
  targetsGroup: booleanQuestion(
    'Does this content target a specific group with abuse or harassment?',
    { true: 'The text attacks a named group', false: 'The text does not target a group' },
  ),
  requestsSecrets: booleanQuestion(
    'Does this content ask for credentials, API keys, or other secrets?',
    { true: 'The user is asked to reveal a secret', false: 'No secret is requested' },
  ),
  changesProduction: booleanQuestion(
    'Would executing this tool call change production data or send an external message?',
    { true: 'The call writes, deletes, pays, or posts', false: 'The call is read-only or local' },
  ),
};

export type GuardrailRule = keyof typeof guardrailQuestions;
export type GuardrailAction = 'block' | 'review' | 'allow';

export interface GuardrailProbabilities {
  targetsGroup: number;
  requestsSecrets: number;
  changesProduction: number;
}

/** High risk: false allow is costly. Tune on labeled cases; not a hosted default. */
export const BLOCK_IF_RISK_AT_LEAST = 0.8;
/** Mid band: send to a person. Must be below the block cutoff. */
export const REVIEW_IF_RISK_AT_LEAST = 0.4;

export interface HumanReviewInput {
  text: string;
  tool?: string;
  args?: string;
  risk: number;
  probabilities: GuardrailProbabilities;
}

export function createHostedClient() {
  const apiKey = process.env.SYSTEM_ONE_API_KEY;
  const baseURL = process.env.SYSTEM_ONE_BASE_URL;
  const model = process.env.SYSTEM_ONE_MODEL;
  if (!apiKey || !baseURL || !model) {
    throw new Error('Set SYSTEM_ONE_API_KEY, SYSTEM_ONE_BASE_URL (…/v1), and SYSTEM_ONE_MODEL from GET /v1/models.');
  }
  return createSystemOne({
    apiKey, baseURL, model, adapter: systemOneAdapter,
    transport: createFetchTransport(), timeoutMs: 30_000, maxRetries: 0,
  });
}

/** Conservative combine: the highest P(true) among independent Noul rules. */
export function combineRiskProbability(probabilities: GuardrailProbabilities) {
  return Math.max(probabilities.targetsGroup, probabilities.requestsSecrets, probabilities.changesProduction);
}

export function applyGuardrailPolicy(risk: number): GuardrailAction {
  if (risk >= BLOCK_IF_RISK_AT_LEAST) return 'block';
  if (risk >= REVIEW_IF_RISK_AT_LEAST) return 'review';
  return 'allow';
}

/**
 * @param sendToHumanReview Your approval queue. Not a System One method.
 */
export async function screenWithGuardrails(
  client: EvaluationClient,
  input: { text: string; tool?: string; args?: string },
  sendToHumanReview: (input: HumanReviewInput) => Promise<string>,
) {
  const result = await client.evaluate({
    state: {
      text: input.text,
      tool: input.tool ?? null,
      args: input.args ?? null,
    },
    questions: guardrailQuestions,
  });
  const probabilities: GuardrailProbabilities = {
    targetsGroup: result.answers.targetsGroup.probability,
    requestsSecrets: result.answers.requestsSecrets.probability,
    changesProduction: result.answers.changesProduction.probability,
  };
  const risk = combineRiskProbability(probabilities);
  const action = applyGuardrailPolicy(risk);

  if (action === 'block') {
    return { action, risk, probabilities, model: result.model };
  }
  if (action === 'review') {
    const reviewed = await sendToHumanReview({
      text: input.text,
      tool: input.tool,
      args: input.args,
      risk,
      probabilities,
    });
    return { action, risk, probabilities, model: result.model, reviewed };
  }
  return { action, risk, probabilities, model: result.model };
}

async function main() {
  const text = process.env.EXAMPLE_MESSAGE ?? 'Please post this refund to the production ledger.';
  const decision = await screenWithGuardrails(
    createHostedClient(),
    { text, tool: process.env.EXAMPLE_TOOL, args: process.env.EXAMPLE_TOOL_ARGS },
    async () => {
      throw new Error('No System One human-review API. Wire your approval queue here.');
    },
  );
  console.log(JSON.stringify(decision, null, 2));
}

if (process.argv[1] && import.meta.url.endsWith(process.argv[1].split(/[\\/]/).pop() ?? '')) {
  main().catch((error: unknown) => {
    console.error(error instanceof Error ? error.message : error);
    process.exitCode = 1;
  });
}
```

这个示例没有把判断挂到 OpenAI Agents SDK 的工具输入护栏上，也没有用 Score 区分严重程度；接入时要由你自己把返回的 `block` / `review` / `allow` 对应到框架的 `rejectContent`、`needsApproval`、`allow` 等行为。
## 延伸阅读

- [决策模型是什么？](https://system-one.dev/zh-CN/blog/what-is-a-decision-model)
- [用 Score 替代 LLM 当裁判](https://system-one.dev/zh-CN/blog/llm-as-a-judge-alternative)
- [Agent 工具太多总选错？用 Choice 做工具路由](https://system-one.dev/zh-CN/blog/agent-tool-routing)
- [Agent 什么时候算完成？](https://system-one.dev/zh-CN/blog/agent-stop-condition)
- 站内文档：[决策原语](https://docs.system-one.dev/zh-CN/docs/primitives)、[Agent 接入](https://docs.system-one.dev/zh-CN/docs/agent-integration)


## 护栏判断方式对比（核验日期 2026-10-11）

| 方式 | 输出形态 | 能否按误判成本设阈值 | 适用场景 | 来源 |
| --- | --- | --- | --- | --- |
| 规则 / 正则<br><small>[OpenAI Agents SDK 护栏](https://openai.github.io/openai-agents-js/zh/guides/guardrails/) · 2026-10-11</small> | 命中 / 未命中，由代码返回 `allow` 或 `rejectContent`<br><small>[OpenAI Agents SDK 护栏](https://openai.github.io/openai-agents-js/zh/guides/guardrails/) · 2026-10-11</small> | 不能，只有命中与否 | 格式明确的内容，如密钥 | 官方文档示例 |
| 生成式 LLM 判断<br><small>[OpenAI Agents SDK 护栏](https://openai.github.io/openai-agents-js/zh/guides/guardrails/) · 2026-10-11</small> | 护栏 Agent 输出结构化结果（示例为布尔字段），据此决定是否触发熔断<br><small>[OpenAI Agents SDK 护栏](https://openai.github.io/openai-agents-js/zh/guides/guardrails/) · 2026-10-11</small> | 示例中没有概率可调 | 需要理解语义的检查；文档建议可用轻量级模型 | 官方文档 |
| 专用审核服务<br><small>[阿里云 AgentScope 集成 AI 安全护栏](https://help.aliyun.com/zh/document_detail/3041780.html) · 2026-10-11</small> | 在拦截点上阻断、标记或替换内容<br><small>[阿里云 AgentScope 集成 AI 安全护栏](https://help.aliyun.com/zh/document_detail/3041780.html) · 2026-10-11</small> | 可以：控制台可为精细化风险标签配置命中阈值，阈值取模型输出的 0–100 置信分，最小步长 1；API 按设置的高低风险分返回 RiskLevel（high / medium / low / none），文档建议高风险直接处置、中风险人工复查；[阿里云 AI 安全护栏文本审核 API](https://help.aliyun.com/zh/document_detail/2875414.html) · 2026-10-11<br><small>[阿里云 AI 安全护栏产品简介](https://help.aliyun.com/zh/document_detail/2873209.html) · 2026-10-11</small> | 通用内容安全；类别由服务方定义 | 官方文档 |
| 决策模型 Noul（System One）<br><small>[System One 决策原语](https://docs.system-one.dev/zh-CN/docs/primitives) · 2026-10-11</small> | 每个是非问题返回 P(true)，0 到 1<br><small>[System One 决策原语](https://docs.system-one.dev/zh-CN/docs/primitives) · 2026-10-11</small> | 能，阈值由应用按评估案例和误判后果设定 | 针对自己政策的判断；system-one.dev 提供 Jev（`typesafe/jev`）、Clef Flash（`cloudflare/clef-flash`）和 Clef（`cloudflare/clef`）模型服务 | 官方文档 |
| 决策模型 noul（Microsoft-Decision-1）<br><small>[Microsoft Learn](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/use-foundry-models-microsoft-decision) · 2026-10-11</small> | 是非条件的概率，文档推荐用于门禁和过滤<br><small>[Microsoft Learn](https://learn.microsoft.com/en-us/azure/foundry/foundry-models/how-to/use-foundry-models-microsoft-decision) · 2026-10-11</small> | 能，文档建议按误报和漏报代价设阈值 | 在 Microsoft Foundry 上调用；尚未在 System One 启用 | 官方文档 |
## 常见问题

### Agent 护栏是什么？输入、输出、工具三类护栏各管什么？

护栏是在用户输入、模型输出和工具调用前后自动运行的检查。按 OpenAI Agents SDK 文档：输入护栏检查初始用户输入；输出护栏检查最终输出；工具护栏在每次函数工具调用前后运行，可以放行、拒绝这次调用或抛出熔断错误。

### 护栏的判断用规则、LLM 还是决策模型？

可以组合。规则适合格式明确的内容；生成式 LLM 能理解语义，但通常只给“是 / 否”；决策模型的 Noul 为你写的是非问题返回概率，可以按误判成本设阈值。高风险动作无论用哪种判断，都要保留人工审批。

### 怎么用 Noul 写审核问题？什么时候加 Score？

把一条政策拆成几个可以回答“是 / 否”的具体问题，每个问题单独定义，用 criteria 写清 true 和 false 的含义。需要按“安全 / 擦边 / 违规 / 必须删除”这样的档位区分严重程度时，再加一个 Score 问题。

### 阈值怎么定？中间值怎么办？

用两个阈值分三段：超过上限拦截，中间转人工，低于下限放行。阈值来自你自己的标注案例和误判代价，不要用通用的百分比；漏拦代价越高，进入人工区间的范围就应该越大。

### 概率高就能直接执行吗？护栏能代替权限和人工审批吗？

不能。System One 文档写明，模型结果不是授权，权限、动作白名单和必要的审批都要在模型之外检查。付款、删除数据、对外发消息这类动作，仍然需要按政策确认。

### 怎么防提示注入？

把提示注入样本（例如参数里写“忽略之前的规则”）放进评估集，单独统计漏拦。Microsoft Learn 提醒，评分会受措辞影响，用作安全过滤时也可能漏掉隐蔽的有害内容，所以护栏不能是唯一的防线，权限控制和人工审批仍然要保留。

### 护栏会拖慢 Agent 吗？

OpenAI Agents SDK 提供两种执行方式：护栏与 Agent 并行运行，延迟最低，但触发时模型或工具可能已经开始工作；或者先跑完护栏再调用模型，避免浪费 token 和误执行工具。具体延迟取决于你选的模型和部署，本文没有实测数据。
