为托管智能体添加护栏
说明如何用 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 或更高。
- 若使用 Python SDK:适用于 Python 的 Azure AI Projects 客户端库,版本 2.2.0 或更高:
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。
- 在
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"- 部署智能体:
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/identityimport { 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}`);通过 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 以及智能体的响应。如果有害提示没有被拦截,按以下顺序检查:
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- 该策略已配置为过滤相关的内容类别和严重级别。
护栏同样适用于流式请求。使用 "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 接受的蛇形名称。
- 在
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"- 部署智能体:
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.jsonazd 从 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 部署该资源。
- 把以下 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。
- 开通该策略:
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" }\
}\
]
}验证并强制该策略:
- 创建或更新 RAI 策略,把
mode设为Audit。 - 把策略附加到托管智能体,并部署新的智能体版本。
- 运行有代表性的任务,例如获取 Python 包元数据、检查包的源码仓库,以及下载包。
- 让智能体尝试请求一个无关主机,例如
example.com。审计模式允许该请求,但会记录策略本来会拒绝它。 - 查看出站决策。把出现在智能体正常工作流中的任何合法重定向或下载主机加进去。
- 把
mode从Audit改为Enforced,更新 RAI 策略,并部署新的智能体版本。 - 开始新会话或恢复会话,并再次运行相同任务。正在运行的沙箱不会重新加载策略变更。对已批准的包和源代码管理主机的请求会成功。对未批准主机的请求返回
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) 控制来编写:
- 在 Foundry 门户中,创建或编辑护栏,然后展开 网络 控制。
图:护栏中的网络控制,显示了出站规则行和出站请求默认动作。
- 选择 出站规则(Egress rules),并把 出站请求(Outbound requests) 的默认动作设为 拒绝 或 允许。
- 选择 添加规则(Add rules),选择 模式(Mode)(审计 或 强制),输入 主机匹配(Host match) 和 动作(Action),然后选择 添加(Add)。按需要重排规则;第一条匹配生效。对于 转换 动作,标头的值来源使用 静态值(Static value) 或 托管标识(Managed identity)。对话框中会出现 机密引用(Secret reference) 值来源,但预览期间不支持。不要使用该选项。更多信息见 预览限制。
图:创建出站规则对话框,包含审计和强制模式、主机匹配字段和动作列表。
- 选择 创建(Create),然后把护栏分配给你的托管智能体。
关于在门户中创建和分配护栏的细节,请参阅 配置护栏与控制。
证书处理
为了检查 HTTPS 流量,托管智能体运行时把出站代理的证书颁发机构(certificate authority,CA)注入沙箱信任包。代理 CA 与基础设施相关:它可能因托管智能体集群和区域而不同,并且会随时间轮换(目前大约每 30 天一次)。把 CA 包当作运行时配置。不要固定、复制或持久保存它。
配置 TLS 客户端,从沙箱中已经存在的标准环境变量读取运行时提供的 CA 包:
| 环境变量 | 用途 |
|---|---|
SSL_CERT_FILE | 供 OpenSSL 风格 TLS 客户端使用的通用 PEM CA 包路径。 |
REQUESTS_CA_BUNDLE | Python requests 及兼容的 HTTP 客户端。 |
GRPC_DEFAULT_SSL_ROOTS_FILE_PATH | 支持默认根文件覆盖的 gRPC 客户端。 |
NODE_EXTRA_CA_CERTS | Node.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 门户演练场中
要在跟踪时间线中查看出站决策:
- 登录 Microsoft Foundry 并打开项目的演练场。
- 运行或选择一次智能体调用,以打开其跟踪。
- 选择 轨迹(Trajectories) 选项卡,查看完整跟踪时间线。
- 展开跟踪节点,直到看到名为 网络出站决策(Network egress decision) 的 span。它出现在触发它的请求旁边。
- 选择该 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 日志中查看出站决策:
- 找到项目的 Application Insights 资源:
- 转到 Foundry 门户。
- 选择 运营(Operate) 选项卡。
- 选择 管理(Admin)。
- 搜索并选择你的项目。
- 打开 已连接资源(Connected resources) 选项卡。
- 找到 AppInsights 已连接资源并复制其 目标 URI(Target URI)。
每个项目应只有一个 Application Insights 连接。如果尚未配置 Application Insights,选择 添加连接(Add connection),并为项目添加一个 Application Insights 资源。
- 在 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


