AI 智能体的安全与保障¶
概述¶
随着 AI 智能体能力的增长,确保它们安全、可靠地运行并与你的品牌价值观保持一致至关重要。未受控制的智能体可能带来风险,包括执行不符合预期或有害的操作,如数据泄露,以及生成可能影响你品牌声誉的不适当内容。风险来源包括模糊的指令、模型幻觉、来自恶意用户的越狱和提示注入,以及通过工具使用产生的间接提示注入。
Google Cloud Vertex AI 提供了多层方法来缓解这些风险,使你能够构建强大且可信的智能体。它提供了几种机制来建立严格的边界,确保智能体只执行你明确允许的操作:
- 身份和授权:通过定义智能体和用户身份验证来控制智能体以谁的身份行事。
-
用于筛选输入和输出的防护栏:精确控制你的模型和工具调用。
- 工具内防护栏: 防御性地设计工具,使用开发者设置的工具上下文来执行策略(例如,只允许查询特定表)。
- 内置的 Gemini 安全功能: 如果使用 Gemini 模型,可以利用内容过滤器来阻止有害输出,并使用系统指令来指导模型的行为和安全准则
- 回调和插件: 在执行前后验证模型和工具调用,根据智能体状态或外部策略检查参数。
- 使用 Gemini 作为安全防护栏: 使用通过回调配置的廉价快速模型(如 Gemini Flash Lite)实现额外的安全层来筛选输入和输出。
-
沙盒化代码执行: 通过对环境进行沙盒化,防止模型生成的代码导致安全问题。
- 评估和跟踪:使用评估工具来评估智能体最终输出的质量、相关性和正确性。使用跟踪来获取智能体行为的可见性,分析智能体为达成解决方案所采取的步骤,包括其工具选择、策略和方法的效率。
- 网络控制和 VPC-SC: 将智能体活动限制在安全边界内(如 VPC 服务控制),以防止数据泄露并限制潜在影响范围。
安全与保障风险¶
在实施安全措施之前,请针对你的智能体的能力、领域和部署环境进行全面的风险评估。
风险的来源包括:
- 模糊的智能体指令
- 来自恶意用户的提示注入和越狱尝试
- 通过工具使用产生的间接提示注入
风险类别包括:
- 不一致和目标篡改
- 追求非预期或代理目标,导致有害后果("奖励黑客行为")
- 错误解读复杂或模糊的指令
- 有害内容生成,包括品牌安全
- 生成有毒、仇恨、偏见、性露骨、歧视或非法内容
- 品牌安全风险,如使用违背品牌价值观的语言或离题对话
- 不安全行为
- 执行可能损害系统的命令
- 进行未授权的购买或金融交易
- 泄露敏感个人数据(PII)
- 数据泄露
最佳实践¶
身份和授权¶
从安全角度看,工具用于在外部系统上执行操作的身份是一个至关重要的设计考量。同一智能体中的不同工具可以配置不同的策略,因此在讨论智能体配置时需要谨慎。
智能体授权¶
工具使用智能体自己的身份(例如,服务账号)与外部系统交互。必须在外部系统访问策略中明确授权智能体身份,例如将智能体的服务账号添加到数据库的 IAM 策略中以获取读取权限。这些策略约束智能体只执行开发者允许的操作:通过给资源提供只读权限,无论模型决定做什么,工具都将被禁止执行写入操作。
这种方法实现起来很简单,并且适用于所有用户共享相同访问级别的智能体。如果不是所有用户都具有相同的访问级别,那么仅仅这种方法就不能提供足够的保护,必须与下面的其他技术相结合。在工具实现中,确保创建日志以维护用户操作的归属关系,因为所有智能体的操作都将显示为来自智能体。
用户授权¶
工具使用"控制用户"的身份(例如,在 Web 应用程序中与前端交互的人类)与外部系统交互。在 ADK 中,这通常通过 OAuth 实现:智能体与前端交互以获取 OAuth 令牌,然后工具在执行外部操作时使用该令牌:如果控制用户被授权自行执行操作,则外部系统会授权该操作。
用户授权的优势在于智能体只执行用户自己可以执行的操作。这大大降低了恶意用户滥用智能体获取额外数据访问权限的风险。然而,大多数常见的委托实现都有一组固定的权限委托(即 OAuth 范围)。通常,这些范围比智能体实际需要的访问权限更广泛,需要下面的技术来进一步约束智能体的操作。
用于筛选输入和输出的防护栏¶
工具内防护栏¶
可以在设计工具时考虑安全性:我们可以创建只暴露我们希望模型采取的操作而不暴露其他操作的工具。通过限制我们提供给智能体的操作范围,我们可以确定性地消除我们永远不希望智能体采取的一类恶意操作。
工具内防护栏是一种创建通用且可重用工具的方法,这些工具暴露确定性控制,开发者可以用来为每个工具实例设置限制。
这种方法依赖于工具接收两种类型输入的事实:由模型设置的参数,以及可由智能体开发者确定性设置的 Tool Context。我们可以依靠确定性设置的信息来验证模型是否按预期行为。
例如,查询工具可以设计为期望从 Tool Context 中读取策略。
# 概念示例:设置用于工具上下文的策略数据
# 在实际的 ADK 应用中,这可能会被设置在 InvocationContext.session.state
# 或在工具初始化时传递,然后通过 ToolContext 获取。
policy = {} # 假设 policy 是一个字典
policy['select_only'] = True
policy['tables'] = ['mytable1', 'mytable2']
# 概念:将策略存储在工具稍后可以通过 ToolContext 访问的位置。
# 这一行在实际中可能有所不同。
# 例如,存储在会话状态中:
invocation_context.session.state["query_tool_policy"] = policy
# 或者在工具初始化时传递:
query_tool = QueryTool(policy=policy)
# 对于本例,我们假设它被存储在某个可访问的位置。
// 概念示例:设置用于工具上下文的策略数据
// 在实际的 ADK 应用中,这可能会被设置在 InvocationContext.session.state
// 或在工具初始化时传递,然后通过 ToolContext 获取。
policy = new HashMap<String, Object>(); // 假设 policy 是一个 Map
policy.put("select_only", true);
policy.put("tables", new ArrayList<>("mytable1", "mytable2"));
// 概念:将策略存储在工具稍后可以通过 ToolContext 访问的位置。
// 这一行在实际中可能有所不同。
// 例如,存储在会话状态中:
invocationContext.session().state().put("query_tool_policy", policy);
// 或者在工具初始化时传递:
query_tool = QueryTool(policy);
// 对于本例,我们假设它被存储在某个可访问的位置。
在工具执行期间,Tool Context 将被传递给工具:
def query(query: str, tool_context: ToolContext) -> str | dict:
# 假设 'policy' 是从上下文中获取的,例如通过会话状态:
# policy = tool_context.invocation_context.session.state.get('query_tool_policy', {})
# --- 占位策略强制执行 ---
policy = tool_context.invocation_context.session.state.get('query_tool_policy', {}) # 示例获取
actual_tables = explainQuery(query) # 假设的函数调用
if not set(actual_tables).issubset(set(policy.get('tables', []))):
# 返回一个错误信息给模型
allowed = ", ".join(policy.get('tables', ['(None defined)']))
return f"Error: Query targets unauthorized tables. Allowed: {allowed}"
if policy.get('select_only', False):
if not query.strip().upper().startswith("SELECT"):
return "Error: Policy restricts queries to SELECT statements only."
# --- 结束策略强制执行 ---
print(f"Executing validated query (hypothetical): {query}")
return {"status": "success", "results": [...]} # 示例成功返回
import com.google.adk.tools.ToolContext;
import java.util.*;
class ToolContextQuery {
public Object query(String query, ToolContext toolContext) {
// 假设 'policy' 是从上下文中获取的,例如通过会话状态:
Map<String, Object> queryToolPolicy =
toolContext.invocationContext.session().state().getOrDefault("query_tool_policy", null);
List<String> actualTables = explainQuery(query);
// --- 占位策略强制执行 ---
if (!queryToolPolicy.get("tables").containsAll(actualTables)) {
List<String> allowedPolicyTables =
(List<String>) queryToolPolicy.getOrDefault("tables", new ArrayList<String>());
String allowedTablesString =
allowedPolicyTables.isEmpty() ? "(None defined)" : String.join(", ", allowedPolicyTables);
return String.format(
"Error: Query targets unauthorized tables. Allowed: %s", allowedTablesString);
}
if (!queryToolPolicy.get("select_only")) {
if (!query.trim().toUpperCase().startswith("SELECT")) {
return "Error: Policy restricts queries to SELECT statements only.";
}
}
// --- 结束策略强制执行 ---
System.out.printf("Executing validated query (hypothetical) %s:", query);
Map<String, Object> successResult = new HashMap<>();
successResult.put("status", "success");
successResult.put("results", Arrays.asList("result_item1", "result_item2"));
return successResult;
}
}
内置的 Gemini 安全功能¶
Gemini 模型带有内置的安全机制,可用于提高内容和品牌安全。
- 内容安全过滤器:内容过滤器可以帮助阻止有害内容的输出。它们独立于 Gemini 模型,作为分层防御的一部分,防止威胁行为者尝试破解模型。Vertex AI 上的 Gemini 模型使用两种类型的内容过滤器:
- 不可配置的安全过滤器自动阻止包含禁止内容的输出,如儿童性虐待材料 (CSAM) 和个人身份信息 (PII)。
- 可配置内容过滤器允许你根据概率和严重性评分,在四个危害类别(仇恨言论、骚扰、性露骨和危险内容)中定义阻止阈值。这些过滤器默认关闭,但你可以根据需要配置它们。
- 安全系统指令:Vertex AI 中 Gemini 模型的系统指令为模型提供有关如何行为和生成什么类型内容的直接指导。通过提供具体指令,你可以主动引导模型避免生成不良内容,以满足你组织的独特需求。你可以制定系统指令来定义内容安全指南,如禁止和敏感主题,以及免责声明语言,以及品牌安全指南,确保模型的输出与你品牌的声音、语调、价值观和目标受众一致。
虽然这些措施对内容安全非常强大,但你需要额外的检查来减少智能体不一致、不安全行为和品牌安全风险。
安全防护措施的回调和插件¶
回调提供了一种简单的、特定于智能体的方法来为工具和模型 I/O 添加预验证,而插件提供了一种可重用的解决方案来在多个智能体中实现通用安全策略。
当无法修改工具以添加防护栏时,可以使用 Before Tool Callback 函数为调用添加预验证。该回调可以访问智能体的状态、请求的工具和参数。这种方法非常通用,甚至可以用来创建可重用的工具策略通用库。但如果要强制执行防护栏的信息不直接体现在参数中,则可能不适用于所有工具。
# 假设的回调函数
def validate_tool_params(
callback_context: CallbackContext, # 正确的上下文类型
tool: BaseTool,
args: Dict[str, Any],
tool_context: ToolContext
) -> Optional[Dict]: # before_tool_callback 的正确返回类型
print(f"Callback triggered for tool: {tool.name}, args: {args}")
# 示例校验:检查状态中的用户 ID 是否与参数中的一致
expected_user_id = callback_context.state.get("session_user_id")
actual_user_id_in_args = args.get("user_id_param") # 假设工具有 'user_id_param' 参数
if actual_user_id_in_args != expected_user_id:
print("Validation Failed: User ID mismatch!")
# 返回字典以阻止工具执行并提供反馈
return {"error": f"Tool call blocked: User ID mismatch."}
# 校验通过时返回 None 允许工具调用继续
print("Callback validation passed.")
return None
# 假设的智能体设置
root_agent = LlmAgent( # 使用具体的智能体类型
model='gemini-2.0-flash',
name='root_agent',
instruction="...",
before_tool_callback=validate_tool_params, # 分配回调
tools = [
# ... 工具函数或 Tool 实例列表 ...
# 例如 query_tool_instance
]
)
// 假设的回调函数
public Optional<Map<String, Object>> validateToolParams(
CallbackContext callbackContext,
Tool baseTool,
Map<String, Object> input,
ToolContext toolContext) {
System.out.printf("Callback triggered for tool: %s, Args: %s", baseTool.name(), input);
// 示例校验:检查状态中的用户 ID 是否与输入参数中的一致
Object expectedUserId = callbackContext.state().get("session_user_id");
Object actualUserIdInput = input.get("user_id_param"); // 假设工具有 'user_id_param' 参数
if (!actualUserIdInput.equals(expectedUserId)) {
System.out.println("Validation Failed: User ID mismatch!");
// 返回以阻止工具执行并提供反馈
return Optional.of(Map.of("error", "Tool call blocked: User ID mismatch."));
}
// 校验通过时返回以允许工具调用继续
System.out.println("Callback validation passed.");
return Optional.empty();
}
// 假设的智能体设置
public void runAgent() {
LlmAgent agent =
LlmAgent.builder()
.model("gemini-2.0-flash")
.name("AgentWithBeforeToolCallback")
.instruction("...")
.beforeToolCallback(this::validateToolParams) // 分配回调
.tools(anyToolToUse) // 定义要使用的工具
.build();
}
但是,当向你的智能体应用程序添加安全防护措施时,插件是实现不特定于单个智能体的策略的推荐方法。插件设计为自包含和模块化,允许你为特定安全策略创建单独的插件,并在运行器级别全局应用它们。这意味着安全插件可以配置一次并应用于使用该运行器的每个智能体,确保整个应用程序中一致的安全防护措施,而无需重复代码。
一些示例包括:
-
Gemini 作为监督插件:此插件使用 Gemini Flash Lite 评估用户输入、工具输入和输出以及智能体的响应是否适当、提示注入和越狱检测。该插件配置 Gemini 作为安全过滤器来缓解内容安全、品牌安全和智能体不一致问题。该插件配置为将用户输入、工具输入和输出以及模型输出传递给 Gemini Flash Lite,由它决定智能体的输入是否安全。如果 Gemini 认为输入不安全,智能体会返回预定的响应:"抱歉,我无法帮助处理这个问题。我可以帮助你处理其他事情吗?"。
-
Model Armor 插件:一个查询 Model Armor API 的插件,用于在智能体执行的指定点检查潜在的内容安全违规。类似于 Gemini 作为监督 插件,如果 Model Armor 发现有害内容匹配,它会向用户返回预定的响应。
-
PII 编辑插件:一个专门为 工具前置回调 设计的专用插件,专门用于在工具处理或发送到外部服务之前编辑个人身份信息。
沙盒化代码执行¶
代码执行是一个具有额外安全含义的特殊工具:必须使用沙盒化来防止模型生成的代码危害本地环境,可能造成安全问题。
Google 和 ADK 提供了几种安全代码执行的选项。Vertex Gemini Enterprise API 代码执行功能通过启用 tool_execution 工具,使智能体能够利用服务器端沙盒化代码执行。对于执行数据分析的代码,你可以使用 ADK 中内置的代码执行器工具调用 Vertex 代码解释器扩展。
如果这些选项都不能满足你的需求,你可以使用 ADK 提供的构建模块构建自己的代码执行器。我们建议创建封闭的执行环境:不允许网络连接和 API 调用,以避免不受控制的数据泄露;并在执行之间完全清理数据,以避免跨用户泄露问题。
评估¶
参见评估智能体。
VPC-SC 边界和网络控制¶
如果你在 VPC-SC 边界内执行智能体,这将保证所有 API 调用只会操作边界内的资源,从而降低数据泄露的可能性。
然而,身份和边界只提供对智能体操作的粗略控制。工具使用防护栏缓解了这些限制,并赋予智能体开发者更多权力来精细控制允许哪些操作。
其他安全风险¶
在 UI 中始终转义模型生成的内容¶
当智能体输出在浏览器中可视化时必须小心:如果在 UI 中没有正确转义 HTML 或 JS 内容,模型返回的文本可能会被执行,导致数据泄露。例如,间接提示注入可能欺骗模型包含一个 img 标签,使浏览器将会话内容发送到第三方站点;或构建 URL,如果点击,会将数据发送到外部站点。正确转义此类内容必须确保模型生成的文本不会被浏览器解释为代码。