在 Amazon Bedrock AgentCore 中用自然语言编写 Dogwood 策略

AWS ML Blog 2026-08-21T13:06:22.734730

AI 智能体(agent)能自动执行复杂的工作流,但如果没有适当的管控,它们也可能做出不符合企业策略或监管要求的操作。为此,我们在 Amazon Bedrock AgentCore 中加入了策略(Policy)功能,让团队可以对运行在 AgentCore 上的所有智能体统一实施管控。

最近,这一功能又得到了扩展,新增了从时间维度约束智能体行为的能力,支持速率限制、前置条件、工具调用顺序约束,以及累积效应等策略。这些策略使用 Dogwood(一种开源治理语言)编写,并由 AgentCore 网关(AgentCore Gateway)内置的 Dogwood 监控器实时应用到智能体的操作上。AgentCore 网关是 Amazon Bedrock AgentCore 的一项能力。

作为本次发布的一部分,我们还扩展了策略编写器(Policy Authoring)的能力。这是一个由 AI 驱动的工具,可以把用自然语言编写的策略规范文档转换为语法和语义都正确的 Dogwood 形式化规范。借助这一新功能,你可以生成以下类型的策略:

无论你的技术背景如何,都可以把用自然语言写成的策略文档直接导入 Amazon Bedrock AgentCore 的策略功能,为已部署的智能体系统提供安全保障。本文将通过示例演示这一新功能,并就如何用最佳实践构建自然语言策略提供指导。

自动将自然语言策略转换为 Dogwood

Dogwood 策略完全可以手工编写;对于少量控制规则而言,这是一个完全合理的起点。当你已经有用自然语言写好的规则、眼前的工作只是转写而非设计时,Policy Authoring 能发挥最大作用。

你可以提供一份只包含规则的文档:一组策略清单、操作流程里的规则章节,或者一段描述允许和禁止操作的文字。Policy Authoring 做的是「翻译」而不是「总结」,所以如果原文档把规则和理由、背景、注释混在一起,最好先把纯规则部分抽取出来再交给它。

示例场景

为了让例子更具体,我们假设有一家零售银行的客户服务 Agent。它可以核实来电者身份、提交争议、对争议费用发起退款、在客户自己的账户之间转账,还可以请求主管批准某笔费用。这些工具都通过 AgentCore Gateway 调用,每个工具接收少量参数并返回一个结果:

工具 用途 输入 输出
verify_identity 对来电者进行增强验证 { account: String } { verified: Bool }
initiate_transfer 在客户账户之间转账 { account: String, dest_account: String, amount: Long } { confirmation: String }
issue_refund 撤销一笔争议费用 { account: String, charge_id: String, amount: Long } { refunded: Bool }
file_dispute 提交一个争议案件 { account: String, description: String } { case_id: String }
request_approval 请求主管批准一笔费用 { charge_id: String } { approved: Bool }

除了策略文档,Policy Authoring 还会接收一份 schema,里面正好包含以上信息:工具名称、它们接受的参数和返回的值。这个 schema 是根据 Agent 的模型上下文协议(Model Context Protocol,MCP)工具清单生成的,所以产出的策略里引用的名称,和 Agent 实际调用时是同一套。比如,生成的策略里 context.input.amount 指的就是上表中的 amount 参数。Policy Authoring 同时还会拿到可用的 Amazon Bedrock Guardrails 检查项,以及策略允许引用的身份声明。

这家银行的合规团队把控制规则写成一份文档,格式与他们给员工看的完全一致。下面这些规则摘录自那份文档,每条规则后面跟着 Policy Authoring 为它生成的 Dogwood 策略。

在 Amazon Bedrock AgentCore 中用自然语言编写 Dogwood 策略

为了让输出更容易阅读,这里用到了两条约定。Dogwood 采用默认拒绝策略(default-deny),并且 forbid 规则的优先级高于 permit 规则。因此,一条授予能力的规则会变成带条件的 permit,而一条限制或封顶的规则则会变成 forbid。此外,条件既可以检查当前正在鉴权的调用,也可以检查同一会话中已经发生过的操作。下面的示例会同时体现这两点。

策略翻译示例

下面几个示例展示了自动形式化器(autoformalizer)如何把自然语言策略翻译成 Dogwood 公式。

对工具参数的限制

比如,退款只能在营业时间内被批准(营业时间定义为 UTC 时间上午

when { context.system.now.toTime() >= duration("9h")

&& context.system.now.toTime() <= duration("17h") }

when { context.input.amount <= 2500 }

一句话里包含两个独立要求,最终变成一个策略、两个条件;要允许退款,这两个条件必须同时成立。context.input.amount 是 agent 发起 issue_refund 调用时传入的 amount 参数,比较时以工具声明的单位为准。文档里的“$2,500”要和工具中金额的单位一致。时间比较是在调用被决策的那一刻读取时钟,两个条件都不依赖 agent 之前做过什么。关于 duration 这类时间函数的更多细节,参见 Time-based policy support。

必需的前置步骤

除非调用者身份已在前 15 分钟内对该账户完成验证,否则不要发起转账。

permit ( principal, action == AgentCore::Action::"initiate_transfer", resource )

when temporal {

formerly within 15m AgentCore::Action::"verify_identity"::response{

input.account: context.input.account,

output.verified: true

}

}

这条规则不能只凭转账请求本身来判断,所以生成的条件会去看 agent 已经做过什么。formerly within 15m 询问它描述的事件在过去 15 分钟内是否发生过。这里指 verify_identity 调用完成(::response,即返回结果,而不是调用动作本身),并且返回结果 verified: true。在事件模式中,不带前缀的 input.account 指先前事件中的字段,而 context.input.account 指当前待决策调用上的字段。把二者设为相等,正是“同一账户”这一表述精确化的方式。验证的是其它账户,或者尝试过但返回未验证,都不满足规则。由于检查的历史记录来自当前会话,规则不需要单独为调用者加 ID,这也是 verify_identity 只接收一个 account 参数的原因。

累积上限

如果过去 12 小时的转账总额会超过 50,000 美元,则阻止转账。

forbid ( principal, action == AgentCore::Action::"initiate_transfer", resource )

when temporal {

A rate limit

The agent might attempt no more than three refunds against the same account within one hour.

forbid ( principal, action == AgentCore::Action::"issue_refund", resource )
when temporal {
  exists (n: Long). (
    (count for (t: Timepoint). where (
      formerly within 1h (
        AgentCore::Action::"issue_refund"::request{ input.account: context.input.account } && tp(t)
      )
    )) == n && n > 3
  )
};

这条策略与上一条结构相同,区别在于它是对事件计数,而不是对某个字段求和。计数范围仅限于针对当前调用所涉及账户的退款,并且当前这次调用也计入在内,因此一小时内的第四次尝试会被拒绝。这条规则明确写的是“尝试”,与上一条不同,它没有留下任何需要推测的地方:被拒绝或失败的退款同样计入限额。

A check on free-form text

Reject any dispute filing whose description contains a Social Security number.

forbid ( principal, action == AgentCore::Action::"file_dispute", resource )
when {
  BedrockGuardrails::SensitiveInformation(["US_SOCIAL_SECURITY_NUMBER"], [context.input.description])
    .maxConfidenceScore().greaterThanOrEqual(decimal("0.2"))
};

};

有些规则要判断的是自由文本的含义,而不是某个结构化字段的值,所以没法靠字段比较来判定。这类规则在生成策略时,会在规则点名的字段上内联执行一次 Amazon Bedrock Guardrails 检查,并把返回的置信度与阈值进行比较。这条规则没有写明阈值,因此翻译时使用该检查的默认值;如果文档里确实写了阈值(比如「高置信度」或具体的数字),则改用这个值。

### 一个同时依赖多种条件类型的规则

退款金额超过 500 美元时,需要主管在最近 30 分钟内批准该笔费用。

forbid ( principal, action == AgentCore::Action::"issue_refund", resource )

when { context.input.amount > 500 }

unless temporal {

formerly within 30m AgentCore::Action::"request_approval"::response{

input.charge_id: context.input.charge_id,

output.approved: true

}
```

这条规则由两个子句组成,它们的校验方式完全不同:一个对当前调用的参数设阈值,另一个检查在此之前已经发生的情况。两个子句都写在同一条策略里。这条规则收窄了原有权限:拒绝超过 500 美元的退款,而 unless 子句就是例外——只要有对应的审批记录,就解除拒绝。和前面的前置条件示例一样,charge_id 的关联关系确保了某笔费用的审批记录不会变成另一笔费用的退款许可。

最佳实践

清晰无歧义的策略能带来更可预期的行为和更少的错误,无论执行策略的是人还是自主智能体(agent)。同时,这也能提升前面介绍的自然语言转 Dogwood 策略方案的生成效果。下面是一些编写自然语言策略的提示和最佳实践。

说清楚你指的是「尝试」还是「结果」。「After a transfer」是有歧义的,「After a transfer succeeds」就没有。尝试指智能体发出的任何一次调用,包括被拒绝或失败的调用;只有成功完成的调用才会带回工具返回的值。速率限制和累计上限通常针对尝试次数,而前置条件和顺序规则则针对结果。

写明时间窗口。「Recently」没法直接翻译成规则,「Within the past 30 minutes」就可以。时间窗口从正在决策的这次调用向前回溯,所以如果规则希望按日历边界(比如每天或每月)重置,而不是随时间滑动,就必须明确说明,因为这两种情况需要不同的控制方式。

指明规则所关联的对象。「No more than three transfers per hour」并没有说清楚是谁的三次:是这个调用方的三次,还是这个账户的三次?两种含义都可以表达,但它们是不同的策略,这句话哪一边都没有选。只要规则涉及计数、求和或关联,就要指明把事件联系起来的那个字段。

给出阈值及其边界。「More than three」和「at least three」之间只差一次操作,而这次操作往往正是规则要阻止的那一次。内容检查的置信度阈值同理。

检查生成的 Dogwood 策略是否正确。

每条 Dogwood 策略都会和它来源的那句话一起返回,方便你把两者放在一起对照着看。虽然校验过程可以确认策略格式正确、并且锚定在正确的 schema(数据结构)上,但它并不能证明策略表达的就是作者的本意。这个判断仍要由文档的所有者来做。

了解哪些内容无法强制执行

虽然上述创作服务可以筛选并重点标出那些与 AgentCore 中 Policy 强制执行机制不兼容的策略,但你还是应该了解一些常见问题。

在这些情况下,真正有用的输出不是策略,而是一个「无法翻译成 Dogwood」的标签。看到这样的标签,你就该考虑备用方案:重写这条规则、把它挪到别的控制点,或者接受它继续留在人工流程里。

策略编写的工作原理

自动形式化流程分四步。

首先是分解文档。面向读者编写的规则往往是复合的:一个编号条款经常携带多项独立的义务,有时候一个句子就包含两条,前文营业时间的例子就是如此。分解步骤把它们拆成原子规则,每条原子规则只针对某个具体工具或一组工具,可以独立执行。

接着是分流。一条规则要么能用 Dogwood 及其组成监视器表达,要么就不能。表达不了的规则会被搁置、不做翻译,原因通常就是上一节提到的四类。在尝试翻译之前就过滤掉它们,可以避免一条表达不了的规则被变成「校验通过但执行错误」的策略。

留下的规则进入自动形式化步骤,转换为 Dogwood 策略,整个过程以随文档一起提供的工具模式(tool schema)为锚点。

最后是校验。每条候选策略都使用 Dogwood 开源语言自带的命令行工具验证。这是整个流程中唯一不靠主观判断的部分:编译器是精确且确定性的权威,它能判断策略能否解析、策略中的每个名字是否存在于模式中。如果候选策略被驳回,系统会把诊断信息返回,带着这些错误重新翻译该规则,最多循环若干轮。

最终输出两类结果:一类是通过语法校验、与环境模式兼容的策略,以及生成这些策略的原子自然语言规则;另一类是被搁置的原子自然语言规则。

结语

本文演示了 Policy Authoring 如何把一份书面策略文档转成 Dogwood 策略。

前面我们看到的示例涉及工具参数约束、前置条件、累计上限、速率限制,以及对自由文本内容的检查。同时,我们也梳理了什么样的自然语言规则更容易被准确翻译成策略:有明确的时间窗口、有指定的主体、有清晰的阈值,并且在「尝试动作」和「最终结果」之间作出明确选择。Dogwood 本身就支持直接编写,喜欢直接上手写策略的团队也完全可以继续这么做。而「从文本生成策略」这个功能,主要面向的是那些规则已经以文字形式存在的常见场景,目的是缩短从一份你本来就在维护的文档,到一套可以审查、可以部署的策略之间的距离。

想开始使用,可以参阅 AgentCore 文档中的 Policy 章节,了解如何创建策略引擎并从文档生成策略;如果你希望自行阅读或扩展生成的策略,也可以参考 Dogwood 语言指南。想进一步了解这些策略在运行时如何被解释和执行,可阅读《用时间策略保护 Amazon Bedrock AgentCore 中的 AI 代理》和《Introducing Dogwood: runtime verification for AI agents》两篇文章。

我们还要感谢团队中其他应用科学家的贡献:Chao Shang、Sadat Shahriar、Wanyu Du 和 Devang Kulshreshtha,他们为本次发布提供了重要支持。

关于作者

Sandesh Swamy
Sandesh 是 AWS 的高级应用科学家。在亚马逊任职的 9 年间,他在 Alexa、聊天应用中的 Amazon Q Developer、Amazon Q 以及 Bedrock Guardrails 等项目中作出了关键贡献,在构建模型以及保护基于大语言模型(LLM)的应用方面拥有丰富经验。目前他专注于代理(Agent)安全,同时采用随机方法和确定性方法。

Min Bai
Min 是 AWS Agentic AI 部门的高级应用科学家,负责安全机制方面的工作,致力于提升代理式 AI 系统的可靠性和规则遵循能力。在 AWS 工作的 5 年里,Min 此前还参与过 Ground Truth 服务以及面向多模态场景的 LLM Guardrails 相关工作。

查看原文