可易

为托管智能体添加护栏

可易团队阅读约 24 分钟

说明如何用 RAI 策略为 Microsoft Foundry 托管智能体附加内容安全与网络出站护栏,并给出校验、测试和强制步骤。

目录

#为托管智能体添加护栏

本文说明如何在 Microsoft Foundry 中为托管智能体(hosted agent)附加护栏(guardrail)。在负责任人工智能(Responsible AI,RAI)策略中定义护栏,再从智能体定义引用该策略。平台在运行时应用这些护栏。本文涵盖两类护栏:

  • 内容安全控制(content safety controls) 筛查智能体收到的提示和返回的响应,从而按组织的安全配置过滤有害内容。
  • 网络出站控制(network egress controls,预览) 管理智能体发起的出站连接,使其只能到达你允许的目的地。

在智能体定义上,用 RAI 策略的资源 ID 引用护栏。部署时可以用 Azure 开发人员 CLI(Azure Developer CLI,azd)、Python SDK 或 REST API 附加。两类护栏的附加步骤相同。若要了解护栏是什么、它们检测哪些风险以及如何创建,请参阅 护栏与控制概述。

如果智能体使用 invocations 协议,仅附加策略还不够。你还要声明请求体和响应体中待筛查文本的位置。请参阅 为使用 invocations 协议的智能体添加护栏。

前提条件

  • 一个托管智能体,或一个已准备好作为托管智能体部署的容器镜像。请参阅 部署托管智能体。
  • Foundry 资源上的一个护栏(RAI 策略),以及它的完整 Azure 资源管理器(Azure Resource Manager,ARM)资源 ID。若要在 Foundry 门户中创建,请参阅 配置护栏与控制。对于网络出站护栏,也可以 用 azd provision 创建策略。ARM 资源 ID 的形式如下:
/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>
  • 若使用 Azure 开发人员 CLI:azd ai agent 扩展,版本 1.0.0-beta.12 或更高。
pip install "azure-ai-projects>=2.2.0"

要为 invocations 协议配置审核,请使用 2.7.0 或更高版本:

pip install "azure-ai-projects>=2.7.0"

护栏如何作用于托管智能体

托管智能体定义有一个可选的 rai_config 设置,其中包含 rai_policy_name 字段。把 rai_policy_name 设为护栏 RAI 策略的完整 ARM 资源 ID。平台将该策略应用于智能体的提示和响应。

省略 rai_config 时,智能体运行时没有内容安全护栏。包含 rai_config 但省略 rai_policy_name 时,平台应用默认策略 Microsoft.DefaultV2。需要更严格或组织特定的过滤时,请提供自定义策略。

rai_policy_name 始终使用完整 ARM 资源 ID,而不是裸策略名。

警告

不要依赖部署时校验来发现错误的策略 ID。在许多订阅上,引用了不存在策略的智能体仍会创建成功并报告 active,但不会应用任何内容过滤——护栏以失败开放(fail open)方式失效,有害提示会到达智能体。请确认该策略存在于账户上,然后在依赖智能体的内容安全之前 测试护栏。

rai_config 是 Foundry API 接受的形态,因此本文中的 Python SDK 和 REST 示例直接设置它。Azure 开发人员 CLI 不在 azure.yaml 中暴露 rai_config;它改用 policies 列表,并在部署时映射到 rai_config。

协议差异

护栏需要多少配置,取决于智能体暴露的协议:

协议配置
responses设置 rai_policy_name。平台知道请求和响应的形态,因此会自行定位要筛查的文本。
invocations设置 rai_policy_name 以及 invocations_moderation。请求体和响应体由你的智能体定义,因此你要声明文本所在位置。
invocations_ws内容安全审核不可用。

重要

在 invocations 协议上,未附带 invocations_moderation 的策略是无效的。平台无法在你的自定义正文形态中找到文本,因此不会筛查任何内容,请求会未经过滤通过。智能体仍会部署并返回 HTTP 200,这个缺口很容易被忽略。

添加护栏

选择你用来部署智能体的方法。这些示例把护栏附加到使用 responses 协议的智能体。在 invocations 协议上,还需要审核设置,为使用 invocations 协议的智能体添加护栏 会说明。

使用 azd 时,在 azure.yaml 里 azure.ai.agent 服务的 policies 列表中声明护栏。添加一项,type 为 rai_policy,并把 raiPolicyName 设为 RAI 策略的完整 ARM 资源 ID。部署时,azd 会把该项映射到它发送给 Foundry 的智能体定义上的 rai_config.rai_policy_name。

  1. 在 azure.yaml 中,为智能体服务添加 policies 列表:
services:
     my-agent:
       host: azure.ai.agent
       project: src/my-agent
       kind: hosted
       name: my-hosted-agent
       description: A hosted agent with a content safety guardrail
       policies:
      - type: rai_policy
        # Full ARM resource ID of the RAI policy on the Foundry resource.
        raiPolicyName: /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>
    protocols:
      - protocol: responses
        version: "2.0.0"
  1. 部署智能体:
azd deploy

平台在创建智能体版本时附加护栏。

注意

在 azure.yaml 中,该字段使用驼峰形式 raiPolicyName。已弃用的独立 agent.yaml 使用蛇形 rai_policy_name。两者都映射到智能体版本上的 rai_config.rai_policy_name。不要在 agent.manifest.yaml 中声明护栏——azd 只在 azd ai agent init 期间读取该文件,部署时会忽略它。

用 SDK 创建智能体版本时,把 RaiConfig 传给 HostedAgentDefinition 的 rai_config 参数。

from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
    AgentEndpointProtocol,
    ContainerConfiguration,
    HostedAgentDefinition,
    ProtocolVersionRecord,
    RaiConfig,
)
from azure.identity import DefaultAzureCredential

# Format: "https://<resource-name>.services.ai.azure.com/api/projects/<project-name>"
PROJECT_ENDPOINT = "your_project_endpoint"

# Full ARM resource ID of the RAI policy.
RAI_POLICY_ID = (
    "/subscriptions/<subscription-id>/resourceGroups/<resource-group>"
    "/providers/Microsoft.CognitiveServices/accounts/<account>"
    "/raiPolicies/<policy-name>"
)

credential = DefaultAzureCredential()
project = AIProjectClient(
    endpoint=PROJECT_ENDPOINT,
    credential=credential,
    allow_preview=True,
)

agent = project.agents.create_version(
    agent_name="my-agent",
    definition=HostedAgentDefinition(
        cpu="1",
        memory="2Gi",
        container_configuration=ContainerConfiguration(
            image="your-registry.azurecr.io/your-image:tag",
        ),
        protocol_versions=[\
            ProtocolVersionRecord(\
                protocol=AgentEndpointProtocol.RESPONSES, version="2.0.0"\
            )\
        ],
        rai_config=RaiConfig(rai_policy_name=RAI_POLICY_ID),
    ),
)

print(f"Agent created: {agent.name}, version: {agent.version}")

参考:HostedAgentDefinition、ContainerConfiguration 和 RaiConfig。

用 .NET SDK 创建智能体版本时,在 HostedAgentDefinition 上设置 ContentFilterConfiguration 属性。用 dotnet add package Azure.AI.Projects.Agents --prerelease 安装预发布包。

using System;
using Azure.AI.Projects.Agents;
using Azure.Identity;

// Format: "https://<resource-name>.services.ai.azure.com/api/projects/<project-name>"
var projectEndpoint = "your_project_endpoint";

// Full ARM resource ID of the RAI policy.
var raiPolicyId =
    "/subscriptions/<subscription-id>/resourceGroups/<resource-group>"
    + "/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>";

AgentAdministrationClient agentsClient = new(
    endpoint: new Uri(projectEndpoint),
    tokenProvider: new DefaultAzureCredential());

var definition = new HostedAgentDefinition(
    versions: new[] { new ProtocolVersionRecord(ProjectsAgentProtocol.Responses, "2.0.0") },
    cpu: "1",
    memory: "2Gi")
{
    ContainerConfiguration = new ContainerConfiguration("your-registry.azurecr.io/your-image:tag"),
    ContentFilterConfiguration = new ContentFilterConfiguration(raiPolicyName: raiPolicyId),
};
ProjectsAgentVersion agent = agentsClient.CreateAgentVersion(
    agentName: "my-agent",
    options: new ProjectsAgentVersionCreationOptions(definition));
Console.WriteLine($"Agent created: {agent.Name}, version: {agent.Version}");

用 SDK 创建智能体版本时,在托管智能体定义中添加带 rai_policy_name 字段的 rai_config 对象。

npm install @azure/ai-projects @azure/identity
import { AIProjectClient } from "@azure/ai-projects";
import { DefaultAzureCredential } from "@azure/identity";

// Format: "https://<resource-name>.services.ai.azure.com/api/projects/<project-name>"
const projectEndpoint =
process.env["FOUNDRY_PROJECT_ENDPOINT"] || "your_project_endpoint";

// Full ARM resource ID of the RAI policy.
const raiPolicyId =
"/subscriptions/<subscription-id>/resourceGroups/<resource-group>" +
"/providers/Microsoft.CognitiveServices/accounts/<account>" +
"/raiPolicies/<policy-name>";

const project = new AIProjectClient(
projectEndpoint,
new DefaultAzureCredential(),
);

const agent = await project.agents.createVersion("my-agent", {
kind: "hosted",
cpu: "1",
memory: "2Gi",
container_configuration: {
    image: "your-registry.azurecr.io/your-image:tag",
},
protocol_versions: [{ protocol: "responses", version: "2.0.0" }],
rai_config: { rai_policy_name: raiPolicyId },
});

console.log(`Agent created: ${agent.name}, version: ${agent.version}`);

参考:AIProjectClient

通过 REST 创建智能体时,在 definition 中包含 rai_config 对象。

BASE_URL="https://{account}.services.ai.azure.com/api/projects/{project}"
API_VERSION="v1"
TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)

curl -X POST "$BASE_URL/agents?api-version=$API_VERSION" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-agent",
    "definition": {
      "kind": "hosted",
      "container_configuration": {
        "image": "myacr.azurecr.io/my-agent:v1"
      },
      "cpu": "1",
      "memory": "2Gi",
      "protocol_versions": [\
        {"protocol": "responses", "version": "2.0.0"}\
      ],
      "rai_config": {
        "rai_policy_name": "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>"
      }
    }
}'

验证护栏已应用

获取智能体版本,确认 rai_config.rai_policy_name 保存的是你的策略 ID。

curl -s -X GET "$BASE_URL/agents/my-agent/versions/1?api-version=$API_VERSION" \
  -H "Authorization: Bearer $TOKEN" | jq '.definition.rai_config'

响应包含你设置的策略:

{
  "rai_policy_name": "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>"
}

测试内容安全过滤

要确认护栏会过滤内容,向智能体的 Responses 端点发送一条违反安全策略的提示。平台在输入阶段筛查该提示,并在智能体运行之前拒绝它。

curl -i -X POST "$BASE_URL/agents/my-agent/endpoint/protocols/openai/responses?api-version=$API_VERSION" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"input":"<a prompt that your policy is configured to block>","store":true}'

被拦截的提示返回 HTTP 400,并带有 content_filter 错误:

{
  "error": {
    "code": "content_filter",
    "message": "The request was blocked due to content safety policy violation at input stage.",
    "type": "content_safety_error"
  }
}

通过策略的提示返回 HTTP 200 以及智能体的响应。如果有害提示没有被拦截,按以下顺序检查:

  1. rai_policy_name 所指向的策略确实存在于该账户。不存在的策略会失败开放且不报错。列出账户上的策略,并确认 rai_policy_name 的最后一段与其中之一匹配:
az rest --method get \
     --url "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies?api-version=2024-10-01" \
     --query "value[].name" -o tsv
  1. 该策略已配置为过滤相关的内容类别和严重级别。

护栏同样适用于流式请求。使用 "stream": true 时,违规提示会在发出任何事件之前,以同样的 HTTP 400 被拒绝。

为使用 invocations 协议的智能体添加护栏

在 responses 协议上,平台知道请求和响应的形态,因此只需要 rai_policy_name。invocations 协议接受由你的智能体定义的请求体和响应体,平台无法判断哪些字段存放用户或智能体文本。向 rai_config 添加 invocations_moderation 对象,声明文本所在位置。

在此之前,策略虽已附加,但不会筛查任何内容。

审核设置

设置是否必需说明
response_mode是智能体可以产生的响应形态:non_streaming、streaming 或 both。
input_content_type否如何解析请求体:json(默认)或 text。
output_content_type否如何解析响应体:json(默认)或 text。
input_paths当 input_content_type 为 json 时选择请求体中用户文本的路径表达式。
output_paths当 response_mode 为 non_streaming 或 both,且 output_content_type 为 json 时选择缓冲响应体中智能体文本的路径表达式。
stream_selectors当 response_mode 为 streaming 或 both,且 output_content_type 为 json 时由 event_type 和 text_field 组成的对,用于在流式事件中定位文本。

当正文是纯文本时,把 input_content_type 或 output_content_type 设为 text。平台随后直接筛查该正文,你不必为该方向提供路径。

一个智能体只能附加一个 RAI 策略,因此 invocations_moderation 作用于这唯一的策略。

#### 路径表达式

input_paths 和 output_paths 接受用 $ 表示文档根、用点号表示成员、数组下标以及 [*] 通配符。例如,$.messages[*].content 选择 messages 数组中每个元素的 content 字段。当一条路径选出多个值时,平台会把它们拼接后一起筛查。

#### 流选择器

对于流式响应,平台读取每个事件的 type 字段,并与 event_type 比较。匹配时,它读取 text_field 所命名的字段并筛查该文本。

event_type 和 text_field 都是对事件 JSON 负载顶层属性的精确、区分大小写的匹配。不能选择嵌套字段。

text_field 是字段名,不是路径表达式。使用 content,而不是 $.content。

type 不匹配任何选择器的事件,或 text_field 没有对应属性的事件,不贡献文本。该事件的内容不会被筛查;如果没有任何选择器产出文本,响应就完全不会被筛查。省略 text_field 时,平台使用 delta。

#### 响应模式

response_mode 声明智能体可以返回的形态。它只作用于输出:无论你设置什么值,输入筛查都会运行。对于输出,平台检查响应的 Content-Type,并只做一次检查:对 text/event-stream 使用流式检查,否则使用缓冲检查。

声明智能体可能返回的每一种形态。携带了你未声明形态内容的成功响应会被拒绝并返回 HTTP 502,而不是跳过审核。只有当智能体确实会以两种方式回答时,才使用 both。

输出筛查适用于携带内容的成功响应。平台不会筛查容器返回的错误响应或空确认。

#### 限制

内容安全筛查对大负载有边界:

限制行为
请求体大于 2 MB转发到你的智能体,不做输入筛查。
缓冲响应体大于 1 MB以 HTTP 502 拒绝。响应不会到达客户端。
单次检查中文本长于 10,000 个字符分析前被截断。

当平台无法把正文解析为 JSON,或 input_paths 没有选中任何内容时,也会不经筛查转发请求。请确认路径与真实请求体匹配,而不要假定已部署的策略正在筛查它们。

添加审核设置

选择你用来部署智能体的方法。

在 azure.yaml 的 rai_policy 项中添加 invocationsModeration 块。这些设置使用驼峰命名,azd 会把它们映射为 API 接受的蛇形名称。

  1. 在 azure.yaml 中,向 rai_policy 项添加 invocationsModeration。此示例筛查请求的 message 字段。智能体流式发送形如 {"type": "token", "content": "..."} 的事件,以及最终的 {"type": "done", "full_text": "..."}。
services:
     my-agent:
       host: azure.ai.agent
       project: src/my-agent
       kind: hosted
       name: my-hosted-agent
       policies:
      - type: rai_policy
        raiPolicyName: /subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>
        invocationsModeration:
          responseMode: streaming
          inputPaths:
            - $.message
          streamSelectors:
            - eventType: token
              textField: content
            - eventType: done
              textField: full_text
    protocols:
      - protocol: invocations
        version: "2.0.0"
  1. 部署智能体:
azd deploy

平台在创建智能体版本时应用审核设置。

azd 会在部署前检查该块,因此结构错误会在本地失败,而不是在运行时失败。例如,在未暴露 invocations 协议的智能体上声明审核,会返回:

policies[0] invocationsModeration is only supported for agents that expose the 'invocations' protocol; add it to 'protocols' or remove the moderation block

这些检查覆盖结构,不覆盖含义。azd 无法判断你的路径和字段名是否与智能体实际发送的正文匹配,因此请用 测试审核设置 中的测试自行验证。

注意

invocations_moderation 需要 azure-ai-projects 2.7.0 或更高版本。

把 RaiInvocationModeration 对象传给 RaiConfig 的 invocations_moderation 参数。

from azure.ai.projects.models import (
    RaiConfig,
    RaiInvocationMode,
    RaiInvocationModeration,
    RaiSseTextSelector,
)

rai_config = RaiConfig(
    rai_policy_name=RAI_POLICY_ID,
    invocations_moderation=RaiInvocationModeration(
        response_mode=RaiInvocationMode.STREAMING,
        input_paths=["$.message"],
        stream_selectors=[\
            RaiSseTextSelector(event_type="token", text_field="content"),\
            RaiSseTextSelector(event_type="done", text_field="full_text"),\
        ],
    ),
)

按 添加护栏 中所示,把 rai_config 传给 HostedAgentDefinition,并把 protocol_versions 设为 invocations 协议。

在智能体定义的 rai_config 对象中包含 invocations_moderation。

{
"name": "my-agent",
"definition": {
    "kind": "hosted",
    "container_configuration": {
      "image": "myacr.azurecr.io/my-agent:v1"
    },
    "cpu": "1",
    "memory": "2Gi",
    "protocol_versions": [\
      {"protocol": "invocations", "version": "2.0.0"}\
    ],
    "rai_config": {
      "rai_policy_name": "/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>",
      "invocations_moderation": {
        "response_mode": "streaming",
        "input_paths": ["$.message"],
        "stream_selectors": [\
          {"event_type": "token", "text_field": "content"},\
          {"event_type": "done", "text_field": "full_text"}\
        ]
      }
    }
}
}

要确认设置已应用,获取智能体版本并检查 definition.rai_config.invocations_moderation:

curl -s -X GET "$BASE_URL/agents/my-agent/versions/1?api-version=$API_VERSION" \
  -H "Authorization: ******" | jq '.definition.rai_config.invocations_moderation'

被拦截的调用是什么样子

被拦截请求的响应,取决于平台在哪个阶段拦截,以及智能体是否流式输出。

被拦截的请求在智能体运行之前返回 HTTP 400。消息以请求 ID 结尾,提交支持请求时可以使用它:

{
  "error": {
    "code": "content_filter",
    "message": "The request was blocked due to content safety policy violation at input stage. [Request ID: <request-id>]",
    "type": "content_safety_error"
  }
}

被拦截的缓冲响应也返回 HTTP 400,消息中会指出输出阶段。

被拦截的流式响应不同。平台在筛查智能体输出之前就发送了响应头,因此状态保持为 HTTP 200。平台丢弃它一直持有的事件,发送单个错误事件,并结束流:

event: error
data: {"type":"error","code":"content_filter","message":"The response was blocked due to content safety policy violation."}

在客户端处理此事件。把它视为终止。更早的事件可能已经到达客户端,因此用户可能在拦截之前看到部分输出。仅有 200 状态并不表示响应通过了策略。

测试审核设置

要确认设置筛查的是正确字段,发送一条你的策略配置为拦截的请求,并检查平台是否拦截它。

如果用 azd 部署,把请求体放进文件,例如 _blocked-request.json_:

{
  "message": "<a prompt that your policy is configured to block>"
}

然后用该文件调用智能体:

azd ai agent invoke -f blocked-request.json

azd 从 azure.yaml 读取协议。把正文作为文件发送,而不是作为消息参数:azd 会把消息参数作为 text/plain 发送,而 inputContentType 为 json 时无法解析它,于是平台会不经筛查转发该请求。

也可以直接调用端点:

curl -i -X POST "$BASE_URL/agents/my-agent/endpoint/protocols/invocations?api-version=$API_VERSION" \
  -H "Authorization: ******" \
  -H "Content-Type: application/json" \
  -d '{"message":"<a prompt that your policy is configured to block>"}'

如果请求没有被拦截,请检查:

  • input_paths 与存放用户文本的字段匹配。选不中任何内容的路径意味着没有任何内容被筛查。
  • 每个 text_field 都是字段名,例如 content,而不是 $.content 这样的路径。
  • 每个 event_type 都与智能体在流式事件中发送的 type 值匹配。
  • event_type 和 text_field 的值与智能体的大小写完全一致,并且命名的是顶层属性而不是嵌套属性。
  • 策略会过滤相关的内容类别和严重级别。

如果请求反而以 HTTP 502 失败,response_mode 可能与智能体的返回不符。如果智能体两种方式都会回答,把它设为 both。

网络出站控制(预览)

重要

网络出站控制处于预览。它们只作用于托管智能体,不影响基于提示的智能体或模型部署。请使用 RAI 策略的 2026-05-15-preview API 版本来配置。预览功能不提供服务级别协议,也不适合在生产或实际运行环境中使用。此功能仅包含工具。客户有责任了解任何接收数据的端点的数据处理做法。

内容安全控制筛查提示和响应。_网络出站控制_ 管理托管智能体发起的出站连接。你定义有序规则,按目的地主机允许、拒绝、转换或改写出站请求,平台在流量离开运行时之前,于智能体沙箱内强制执行。出站规则存放在前面各节所附加的同一个 RAI 策略中,因此 azd、Python SDK 和 REST API 的附加步骤会自动应用它们。

出站规则如何求值

  • 系统按顺序从上到下求值规则。第一条匹配的规则生效。
  • 如果没有规则匹配,则应用策略的默认动作。允许列表(推荐)把默认动作设为 拒绝(Deny),拒绝列表则设为 允许(Allow)。
  • 应用出站策略时,智能体运行时会自动把自身运行所需的基础域名加入允许列表。拒绝 默认动作不会阻断这种必需的平台连接,因此不必为它添加规则。
  • 每条规则按请求主机匹配。支持 *.contoso.com 这类通配符。
  • 规则动作是 允许(Allow)、拒绝(Deny)、转换(Transform)(允许请求并修改其标头)和 改写(Rewrite)(把请求重定向到另一个目的地)。
  • 求值是失败关闭(fail-closed)的:如果策略无法求值,请求会被拒绝。

规则限制

每个策略最多可以添加 480 条出站规则。此限制适用于所有出站规则动作:允许、拒绝、转换 和 改写。若要提高此限制,请 创建 Azure 支持请求。

选择强制模式

模式行为何时使用
审计(Audit)出站流量正常流动。_本来会被拒绝_ 的请求会被记录,但不会被阻断。在强制执行之前,观察智能体调用了什么。
强制(Enforce)应用匹配规则,被拒绝的请求会被阻断。在审计模式中验证规则之后,用于生产强制。

先以 审计 模式部署,查看出站决策,细化规则,然后再切换到 强制。

注意

审计模式只改变 拒绝 动作的行为:本来会被拒绝的请求改为记录而不是阻断。转换 和 改写 动作在审计和强制两种模式下都会应用,因此在审计期间,标头转换和重定向仍然生效。

常见的出站控制用法

用出站控制把托管智能体限制在其任务所需的外部服务上。以下模式是常见起点:

用法策略做法
发现智能体的出站依赖从 审计 模式和 拒绝 默认动作开始。运行有代表性的任务,查看出站决策,并在强制策略之前添加所需主机。
把编码智能体限制在已批准的包和源码仓库允许智能体需要的包注册表、下载主机和源代码管理 API。对所有其他目的地使用 拒绝 默认动作。
把集成智能体限制在已批准的 SaaS API只允许智能体所集成服务的 API 主机,例如问题跟踪器或源代码管理提供方。
为企业 API 添加请求元数据使用带静态标头值的 转换 规则,添加工作负载标识或关联标记。不要把凭据或其他机密放进静态标头值。
让请求经过企业网关使用 改写 规则,把匹配的主机重定向到已批准的网关。在用于智能体工作负载之前,先在非生产环境测试改写。

包管理器和 SDK 可能跟随重定向,或使用单独的下载主机。不要假定注册表主机是唯一需要的目的地。用审计模式和有代表性的工作负载,找出完整的主机集合。

使用 Azure 开发人员 CLI 添加出站规则

把 RAI 策略 ARM 资源添加到 azd 项目的 Bicep 基础设施。azd provision 命令通过 ARM 部署该资源。

  1. 把以下 Bicep 添加到包含 Foundry 资源的资源组范围内的基础设施:
@description('Name of the existing Foundry resource.')
param accountName string

resource account 'Microsoft.CognitiveServices/accounts@2026-05-15-preview' existing = {
     name: accountName
}

resource egressPolicy 'Microsoft.CognitiveServices/accounts/raiPolicies@2026-05-15-preview' = {
     parent: account
     name: 'allow-contoso'
     properties: {
       mode: 'Blocking'
       basePolicyName: 'Microsoft.DefaultV2'
       egressPolicy: {
         mode: 'Enforced'
         defaultAction: 'Deny'
         rules: [\
           {\
             name: 'allow-contoso'\
             ruleType: 'Fqdn'\
             match: {\
               host: '*.contoso.com'\
             }\
             action: {\
               actionType: 'Allow'\
             }\
           }\
         ]
       }
     }
}

output RAI_POLICY_ID string = egressPolicy.id

参考:Microsoft.CognitiveServices accounts/raiPolicies。

  1. 开通该策略:
azd provision

该命令创建或更新 Microsoft.CognitiveServices/accounts/raiPolicies 子资源。把护栏附加到托管智能体时,使用 RAI_POLICY_ID 输出作为完整的策略资源 ID。

使用 REST API 添加出站规则

RAI 策略把出站规则存放在 egressPolicy 属性中。使用 Azure 资源管理器的 RAI Policies - Create Or Update 操作,以及 2026-05-15-preview API 版本来创建或更新策略:

TOKEN=$(az account get-access-token --resource "https://management.azure.com" --query accessToken -o tsv)

curl -X PUT \
  "https://management.azure.com/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CognitiveServices/accounts/<account>/raiPolicies/<policy-name>?api-version=2026-05-15-preview" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "properties": {
      "mode": "Blocking",
      "basePolicyName": "Microsoft.DefaultV2",
      "egressPolicy": {
        "mode": "Enforced",
        "defaultAction": "Deny",
        "rules": [\
          {\
            "name": "allow-contoso",\
            "ruleType": "Fqdn",\
            "match": { "host": "*.contoso.com" },\
            "action": { "actionType": "Allow" }\
          }\
        ]
      }
    }
  }'
  • 把 egressPolicy.mode 设为 Enforced 以阻断流量,或设为 Audit 以记录本来会拒绝的事件而不阻断。
  • 允许列表把 egressPolicy.defaultAction 设为 Deny,拒绝列表设为 Allow。
  • 把每条规则的 action.actionType 设为 Allow、Deny、Transform 或 Rewrite。

要查看已配置的规则,向同一 URL 发送 GET 请求,并检查 properties.egressPolicy。

若要看把默认动作与多种规则类型组合在一起的完整请求体,请参阅 Azure REST API 规范中的 PutRaiPolicyWithEgress.json 示例。

示例:限制依赖研究智能体

设想一个研究 Python 依赖及其源码仓库的编码智能体。它需要从 PyPI 读取包元数据、下载包文件,并从 GitHub API 获取仓库元数据。它不应连接到无关的互联网目的地。

从以下处于审计模式的出站策略开始。用这个对象替换上一节 REST 请求中的 egressPolicy 对象:

{
  "mode": "Audit",
  "defaultAction": "Deny",
  "rules": [\
    {\
      "name": "allow-pypi-metadata",\
      "ruleType": "Fqdn",\
      "match": { "host": "pypi.org" },\
      "action": { "actionType": "Allow" }\
    },\
    {\
      "name": "allow-python-package-downloads",\
      "ruleType": "Fqdn",\
      "match": { "host": "files.pythonhosted.org" },\
      "action": { "actionType": "Allow" }\
    },\
    {\
      "name": "allow-github-api",\
      "ruleType": "Fqdn",\
      "match": { "host": "api.github.com" },\
      "action": { "actionType": "Allow" }\
    }\
  ]
}

验证并强制该策略:

  1. 创建或更新 RAI 策略,把 mode 设为 Audit。
  2. 把策略附加到托管智能体,并部署新的智能体版本。
  3. 运行有代表性的任务,例如获取 Python 包元数据、检查包的源码仓库,以及下载包。
  4. 让智能体尝试请求一个无关主机,例如 example.com。审计模式允许该请求,但会记录策略本来会拒绝它。
  5. 查看出站决策。把出现在智能体正常工作流中的任何合法重定向或下载主机加进去。
  6. 把 mode 从 Audit 改为 Enforced,更新 RAI 策略,并部署新的智能体版本。
  7. 开始新会话或恢复会话,并再次运行相同任务。正在运行的沙箱不会重新加载策略变更。对已批准的包和源代码管理主机的请求会成功。对未批准主机的请求返回 HTTP 403。

确切的主机集合取决于智能体使用的包管理器、SDK 和服务。允许列表应针对该工作负载,而不是原样照抄示例。

若要一个可运行的托管智能体示例,演练允许、拒绝、转换、改写、审计、通配符和规则顺序等场景,请参阅 出站控制示例。

转换请求标头

当规则的 action.actionType 为 Transform(或 Rewrite)时,可以用 action.headers 数组修改出站请求的标头。每一项描述一次标头操作:

{
  "name": "allow-and-tag-contoso",
  "ruleType": "Fqdn",
  "match": { "host": "*.contoso.com" },
  "action": {
    "actionType": "Transform",
    "headers": [\
      { "operation": "Set",    "name": "X-Trace-Source", "value": "hosted-agent" },\
      { "operation": "Insert", "name": "X-Request-Id",   "value": "default-id" },\
      { "operation": "Remove", "name": "User-Agent" }\
    ]
  }
}

每个标头对象支持以下字段:

字段是否必需说明
operation是标头操作:Set、Insert 或 Remove。操作名不区分大小写。省略时使用 Set。
name是要修改的标头名称。
value当未使用 valueRef 时,Set 和 Insert 需要静态标头值。Remove 不使用。
valueRef当未使用 value 时,Set 和 Insert 需要托管标识值引用。Remove 不使用。

当 actionType 为 Transform 时必须包含 headers。当 actionType 为 Rewrite 时可以省略 headers。标头转换只作用于匹配该规则的请求。

注意

标头转换支持静态值和托管标识值引用。对于托管标识值引用,把 valueRef.managedIdentityRef.resource 设为目标资源 URI,例如 https://storage.azure.com/。把 format 设为带 {token} 占位符的 bearer 方案。向目标资源授予已部署智能体的 instance_identity.principal_id 所需角色。预览期间不支持机密值引用。

#### 标头操作

三种操作的差别只在于如何对待出站请求上已经存在的标头:

操作标头已存在标头不存在
Set覆盖现有值。添加该标头。
Insert保留现有值不变。添加该标头。
Remove移除该标头。无效果。

使用 Set 强制把标头设为特定值,无论智能体发送了什么。使用 Insert 仅在智能体尚未设置该标头时提供默认值。使用 Remove 在请求离开运行时之前去掉某个标头。

在门户中添加出站规则

也可以在 Foundry 门户中,把出站规则作为护栏上的 网络(Network) 控制来编写:

  1. 在 Foundry 门户中,创建或编辑护栏,然后展开 网络 控制。

图:护栏中的网络控制,显示了出站规则行和出站请求默认动作。

  1. 选择 出站规则(Egress rules),并把 出站请求(Outbound requests) 的默认动作设为 拒绝 或 允许。
  1. 选择 添加规则(Add rules),选择 模式(Mode)(审计 或 强制),输入 主机匹配(Host match) 和 动作(Action),然后选择 添加(Add)。按需要重排规则;第一条匹配生效。对于 转换 动作,标头的值来源使用 静态值(Static value) 或 托管标识(Managed identity)。对话框中会出现 机密引用(Secret reference) 值来源,但预览期间不支持。不要使用该选项。更多信息见 预览限制。

图:创建出站规则对话框,包含审计和强制模式、主机匹配字段和动作列表。

  1. 选择 创建(Create),然后把护栏分配给你的托管智能体。

关于在门户中创建和分配护栏的细节,请参阅 配置护栏与控制。

证书处理

为了检查 HTTPS 流量,托管智能体运行时把出站代理的证书颁发机构(certificate authority,CA)注入沙箱信任包。代理 CA 与基础设施相关:它可能因托管智能体集群和区域而不同,并且会随时间轮换(目前大约每 30 天一次)。把 CA 包当作运行时配置。不要固定、复制或持久保存它。

配置 TLS 客户端,从沙箱中已经存在的标准环境变量读取运行时提供的 CA 包:

环境变量用途
SSL_CERT_FILE供 OpenSSL 风格 TLS 客户端使用的通用 PEM CA 包路径。
REQUESTS_CA_BUNDLEPython requests 及兼容的 HTTP 客户端。
GRPC_DEFAULT_SSL_ROOTS_FILE_PATH支持默认根文件覆盖的 gRPC 客户端。
NODE_EXTRA_CA_CERTSNode.js 附加 CA 文件,在进程启动时读取。

例如,使用 Python requests:

import os
import requests

response = requests.get(
    "https://example.com",
    verify=os.environ["REQUESTS_CA_BUNDLE"],
)

请记住这些约束:

  • 不要固定 CA 的主题、公钥、指纹或文件内容。这些值都可能在轮换时改变,或因集群而不同。
  • 不要把注入的 CA 持久保存到容器镜像、持久卷、快照或源代码管理。CA 是运行时基础设施,不是应用配置。
  • 轮换可能发生在沙箱运行期间。长时间运行的进程可能需要重新加载 TLS 配置或重启。
  • 如果必须构建单一自定义包(例如把企业根证书与运行时根证书合并),请在进程启动时基于当前运行时包构建,并把它视为临时文件:
cat "$SSL_CERT_FILE" /app/custom-roots.pem > /tmp/runtime-ca-bundle.pem
export SSL_CERT_FILE=/tmp/runtime-ca-bundle.pem
export REQUESTS_CA_BUNDLE=/tmp/runtime-ca-bundle.pem

查看出站决策

智能体把网络出站决策发送到项目的 Application Insights 和跟踪监控工具。你可以在 Foundry 门户演练场和 Application Insights 中查看这些决策,以确认网络出站行为与所配置的策略一致。

#### 在 Foundry 门户演练场中

要在跟踪时间线中查看出站决策:

  1. 登录 Microsoft Foundry 并打开项目的演练场。
  2. 运行或选择一次智能体调用,以打开其跟踪。
  3. 选择 轨迹(Trajectories) 选项卡,查看完整跟踪时间线。
  4. 展开跟踪节点,直到看到名为 网络出站决策(Network egress decision) 的 span。它出现在触发它的请求旁边。
  5. 选择该 span 以查看决策细节。

图:Foundry 门户演练场中的轨迹选项卡,跟踪时间线中显示了网络出站决策 span。

span 细节显示理解请求为何被允许或拒绝所需的信息:

字段说明
决策(Decision)策略求值的结果,例如 Allow 或 Deny。
原因(Reason)人类可读的解释,例如 “Request matched allow rule” 或 “No egress policy rule allowed this destination.”。
匹配规则(Matched rule)决定结果的规则名称,如果有规则匹配。
规则来源(Rule source)匹配规则的来源,例如连接器或策略定义。
强制(Enforcement)策略是被强制执行,还是以审计模式运行。
目的地(Destination)出站请求的方法和 URL。
默认动作(Default action)没有规则匹配时应用的动作,例如 Deny。

被允许的请求显示为成功状态,被拒绝的请求显示为失败状态,因此不必离开跟踪视图就能发现被阻断的调用。

#### 在 Application Insights 中

要在 Application Insights 日志中查看出站决策:

  1. 找到项目的 Application Insights 资源:
  1. 转到 Foundry 门户。
  2. 选择 运营(Operate) 选项卡。
  3. 选择 管理(Admin)。
  4. 搜索并选择你的项目。
  5. 打开 已连接资源(Connected resources) 选项卡。
  6. 找到 AppInsights 已连接资源并复制其 目标 URI(Target URI)。

每个项目应只有一个 Application Insights 连接。如果尚未配置 Application Insights,选择 添加连接(Add connection),并为项目添加一个 Application Insights 资源。

  1. 在 Azure 门户中打开该 Application Insights 资源,转到 日志(Logs)。运行以下查询以查看最近的网络出站决策:
traces
| where timestamp > ago(1h)
| where message == "Network egress decision"

每个事件都包含目的地主机、匹配规则、决策和强制模式等细节。

被阻断的请求是什么样子

当规则拒绝一次出站调用时,出站代理向智能体的网络客户端返回 HTTP 403 响应。智能体用自己的逻辑处理该错误。例如,如果被阻断的调用是一次工具调用,智能体通常会报告工具调用失败,并尝试另一种做法。策略内部细节不会暴露给最终用户。

预览限制以及后续计划

网络出站控制是一项附加功能。预览期间:

  • 出站控制只作用于托管智能体。
  • 强制发生在 Foundry 托管的智能体沙箱内部。出站控制补充你自己的网络控制,例如 Azure 防火墙(Azure Firewall),而不是取代它们。它们不会把强制委托给客户管理的防火墙,也不会通过 Azure Policy 集中强制。
  • 在门户中,规则按主机匹配。

以下能力尚不可用,计划在后续更新中提供:

  • 机密标头值——预览期间不支持从机密引用注入标头值。
  • 诸如 Azure 服务标记和 IP 地址范围之类的规则类型。
  • MCP 工具策略、个人身份信息与数据丢失防护检查,以及自定义 webhook 挂钩。

PRIMARY_SOURCE: https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/add-hosted-agent-guardrails

  • #护栏
  • #Microsoft Foundry
  • #内容安全
  • #出站控制
  • #RAI