关于 integrate-arcjet-guard-google-adk-py
为 Python Google ADK Agent 和 Runner 集成 Arcjet Guard 的技能,说明回调与插件门控、失败时拒绝及关联 ID 的处理方式。
主要功能
- 01为自定义 Agent 添加工具回调
- 02通过插件门控 Runner 工具
- 03失败时返回拒绝结果
- 04使用调用方提供的关联 ID
适用场景
- 01阻止有风险的 Agent 工具调用
- 02统一门控 Runner 中的工具
- 03运行前筛查输入消息
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ arcjet/ skills --skill integrate-arcjet-guard-google-adk-py安装后在 Claude Code 中输入/integrate-arcjet-guard-google-adk-py确认已加载。
详细介绍
将 Arcjet Guard 集成到 Python Google ADK
arcjet.guard.google_adk 封装 Agent 现有的 Arcjet 客户端。
它自身不会与 Arcjet API 通信。通用的 Guard 基础知识
(客户端、规则、标签、判定、捕获、注册)请参阅
../arcjet/references/guards_python.md。
除 Google ADK 特有内容外,其他内容均请加载该参考文档。
仅适用于官方 Python google-adk>=2.0.0,<3,不适用于 JS @google/adk
(@arcjet/guard/google-adk/v2,文档见
https://docs.arcjet.com/guards/google-adk/)、`@google/genai`、
Go 或 Java ADK。导入 arcjet.guard.google_adk 不会加载 LangChain、CrewAI 或 JS 适配器。该额外依赖是安全的(不包含
chromadb)。
导出项:guard_tool、guard_plugin、google_adk_context。没有入站辅助函数,也没有审批辅助函数。
三种接入方式,一条判定规则:
- 自行编写的
LlmAgent→guard_tool。将返回的回调 赋给LlmAgent(before_tool_callback=...)。它并不是对FunctionTool的包装。 - 覆盖整个 Runner/未附加回调的工具 →
guard_plugin。这是一个Runner BasePlugin,其before_tool_callback是整个运行范围的门控。将它放在首位。 - 关联 →
google_adk_context读取调用方提供的 ID。 它不会生成 ID,不读取invocation_id,不读取trace_id,也不读取 session-service 自动生成的 ID。
DENY 是包含 arcjetDenied: true 的跳过字典(ArcjetDenialResult,
键采用驼峰命名)。ADK 会将任何映射(包括 {})视为跳过,
并将该字典用作工具结果;但在回调链中,{} 是假值,因此工具仍会运行。
只有 None 才表示允许。切勿返回 {} 表示允许。不要抛出异常——抛出异常会成为插件/回调错误,而不是跳过。
不存在 /guards/google-adk-py/ 文档页面。不要臆造。
JS 适配器页面是 https://docs.arcjet.com/guards/google-adk/
——本指南负责讲解 Python。示例:
arcjet-py main 分支上的
examples/fastapi-google-adk-guard。
不要臆造第二个示例名称。
门控方式是 before_tool_callback 跳过字典
两个辅助函数都会评估 Guard;若结果为 DENY,或在默认
on_guard_error="deny" 下 Guard 未完成评估,则返回拒绝字典,
使原始工具函数永不运行。发生错误时必须始终拒绝执行:始终返回该拒绝字典,不要返回 None(这会执行工具),也不要抛出异常。结果为 ALLOW 时,它们返回 None。其默认的失败关闭行为与
#196 相同:只有
"allow" 才会在失败时放行;DENY 始终会阻止执行。核心 guard() 仍会在失败时放行(has_failed_open())。
guard_tool 是位置参数回调
(tool, args, tool_context)。guard_plugin 是仅限关键字参数的插件回调 (*, tool, tool_args, tool_context)。两者使用相同的跳过字典。guard_tool 要求提供 action。guard_plugin 的 action 默认为 "{tool_name}.invoked"。
PluginManager 遵循先返回者优先的规则。如果其他插件先返回字典,Guard 就不会运行。ALLOW 时插件返回 None,之后 Agent 的 before_tool_callback 仍会运行——不要在同一批工具上同时使用 guard_plugin 和 guard_tool,否则 Guard 会被调用两次。
require_confirmation 不是策略门控
require_confirmation/request_confirmation/
adk_request_confirmation/ADK SecurityPlugin/确认后恢复执行都属于人机协同流程。人类确认后,Guard 仍会在工具调用时运行。这与 CrewAI 的 human_input、JS 的
requireConfirmation、LangGraph 的 interrupt()、OpenAI Agents 的
needs_approval 和 Genkit 的 interrupt() 遇到的是同类问题。没有入站辅助函数,也没有审批辅助函数。
在 runner.run_async 之前筛查入站请求
没有入站辅助函数。Agent/模型回调
(before_model_callback、before_run_callback、
on_user_message_callback)不是这个策略关卡。在应用中调用
aj.guard(...),并且根据决策采取行动。
核心 guard() 在失败时默认放行:ALLOW 并不能证明规则已运行。
如果此调用点必须失败时拒绝,请根据 decision.has_failed_open() 设置关卡;
guard_tool/guard_plugin 已默认采用此设置。
actor/inputs —— 省略后,远程规则永远不会触发
声明了 actor 或类型化 inputs 的远程策略,只会在此调用传入这些值时
进行评估。在 guard_tool/guard_plugin(以及入站
aj.guard(...))上传入 actor= 和 inputs=。使用
arcjet.guard 中的 server_input/local_input 构造每个输入——这是 Python
的 policyInput 对应项。不要导入 JS policyInput,也不要另造一个辅助函数。
- 省略
actor/inputs后,要求这些值的远程策略永远不会触发。此调用仍会联系 Guard;只是远程规则无值可读。 - 抛出异常的解析器会导致服务降级,并在默认设置
on_guard_error="deny"下失败时拒绝。工具不会运行。 actor是经过身份验证的调用者,绝不是模型生成的工具参数。模型选定的订单 ID 或to=邮箱地址并不能代表操作者。请从签名会话、已验证令牌或其他服务端身份信息中获取actor。
解析器会看到工具调用信封:模型参数加上
tool_name(最后应用,因此工具参数中同名的值无法覆盖回调的名称)。
首先要向用户确认的问题
只询问无法从代码推断的信息;同时建议默认值。
- 哪些工具风险较高(会产生外部副作用、不可逆、花费资金或发送消息)?你编写的 Agent 使用
guard_tool。Runner 范围内/未包装的工具使用guard_plugin。 - 有哪些限制?(例如“每位用户每分钟查询 10 次” →
TokenBucket。) - 元数据中的用户是谁——不透明的用户/租户 ID(绝不使用 PII)?默认:无。同一个经过身份验证的 ID 也是
actor。将已有的会话/会话 ID 传给guard_tool/guard_plugin(session_id=...),并传给runner.run_async(..., session_id=...)。该 ID 是关联 ID,而不是用户。不要使用invocation_id或会话服务自动生成的 ID。 - Arcjet 中断是否不可接受?每个辅助函数的默认值都是
on_guard_error="deny"。请明确询问是否要在runner.run_async之前筛查入站请求:在那里失败时拒绝意味着运行不会启动,因此在那个调用点选择"allow"是常见且合理的做法。
读者容易弄错的地方
- 这不是 JS
@arcjet/guard/google-adk/v2。 导入arcjet.guard.google_adk。JS 适配器仅支持插件(没有guardTool)。 - 没有入站辅助函数。 在
runner.run_async之前使用核心guard()进行筛查。 require_confirmation/request_confirmation属于 HITL,而非策略。 不要使用SecurityPlugin作为 Arcjet 关卡。- 失败时拒绝 = 出错时始终返回带有
arcjetDenied的拒绝字典。 不要抛出异常,不要返回None,也绝不要返回{}。 guard_tool是回调,不是 FunctionTool 包装。 将它赋给LlmAgent(before_tool_callback=...)。- 不要对同一批工具同时使用
guard_plugin和guard_tool。 插件的 ALLOW 仍会传递到 Agent 回调。 - 在
Runner(..., plugins=)中将 Arcjet 放在第一位。 - 只读取关联 ID,绝不自行生成。 绝不使用
invocation_id、trace_id或会话自动生成的 ID。 - 速率限制应以经过身份验证的调用者为键,而不是模型提供的订单 ID。
actor也一样。 - 省略
actor/inputs后,远程规则永远不会触发。 解析器抛出异常会导致失败时拒绝。使用server_input/local_input,而不是 JSpolicyInput。 - 不要给每个 ADK 工具都手动套上原始
guard()。
步骤 1:安装并查找 guard 客户端
安装已发布的扩展。使用项目当前已有的包管理器,不要固定 git SHA:
pip install "arcjet[google-adk]"
uv add "arcjet[google-adk]"
该扩展会引入 google-adk>=2.0.0,<3。如果 Agent 还没有 guard
客户端,请在模块作用域仅启动一次:
import os
from arcjet.guard import launch_arcjet
aj = launch_arcjet(key=os.environ["ARCJET_KEY"])
步骤 2:为自定义 Agent 设置防护 — guard_tool
根据当前请求经过身份验证的调用方构建回调(以及 Agent)。不要在导入时固定 user_id / conversation_id。
from google.adk.agents import LlmAgent
from google.adk.tools import FunctionTool
from arcjet.guard import TokenBucket, launch_arcjet, server_input
from arcjet.guard.google_adk import google_adk_context, guard_plugin, guard_tool
aj = launch_arcjet(key=os.environ["ARCJET_KEY"])
lookup_limit = TokenBucket(
label="order.looked-up",
bucket="lookups",
refill_rate=10,
interval_seconds=60,
max_tokens=10,
)
# Per request: authenticated caller + caller-owned conversation id.
user_id = authenticated_user_id
conversation_id = authenticated_conversation_id
def lookup_order(order_id: str) -> dict:
"""Look up an order by ID."""
return {"order_id": order_id, "status": "shipped"}
# require_confirmation=True is HITL — not this policy gate
lookup = FunctionTool(func=lookup_order)
agent = LlmAgent(
name="support_agent",
description="Help the user.",
instruction="Help the user.",
tools=[lookup],
before_tool_callback=guard_tool(
guard=aj,
action="order.looked-up",
# Authenticated caller — never a model-produced order id.
actor=user_id,
inputs=lambda call: {
"order_id": server_input.string(str(call.get("order_id", ""))),
},
rules=[lookup_limit(key=user_id, requested=1)],
session_id=conversation_id,
on_guard_error="deny",
),
)
在 guard_tool 上使用 action + rules。action 可以是一个以工具调用封装(tool_name 加 input)为参数的函数。即使 rules 为空,仍会联系 Guard。速率限制应以经过身份验证的调用方为键,而不是以模型提供的订单 ID 为键。
步骤 3:为 Runner 设置防护 — guard_plugin
对未附加 guard_tool 的工具使用此方式。将 Arcjet 放在首位。不要再为这些相同的 Agent 指定 guard_tool。
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
session_service = InMemorySessionService()
runner = Runner(
app_name="support",
agent=agent,
session_service=session_service,
# Arcjet first: a deny dict skips the tool before later plugins run.
plugins=[
guard_plugin(
guard=aj,
action=lambda call: f"{call['tool_name']}.invoked",
actor=user_id,
inputs=lambda call: {
"tool": server_input.string(str(call.get("tool_name", ""))),
},
rules=[lookup_limit(key=user_id, requested=1)],
session_id=conversation_id,
on_guard_error="deny",
),
],
)
步骤 4:在 runner.run_async 之前筛查入站内容
google_adk_context 接受 sessionId 或 session_id(correlationId / conversationId 也一样)。不要求使用驼峰命名。
from google.genai import types
from arcjet.guard import DetectPromptInjection
inbound = DetectPromptInjection()
app_context = {"sessionId": conversation_id}
derived = google_adk_context(app_context)
decision = await aj.guard(
label="message.received",
actor=user_id,
inputs={"content": server_input.string(user_text)},
rules=[inbound(user_text)],
correlation_id=derived.correlation_id,
)
if decision.conclusion == "DENY":
raise RuntimeError("message blocked")
if decision.has_failed_open():
raise RuntimeError("inbound guard unavailable")
async for event in runner.run_async(
user_id=user_id,
session_id=conversation_id,
new_message=types.Content(
role="user",
parts=[types.Part(text=user_text)],
),
):
_ = event
没有入站辅助函数。
步骤 5:关联
google_adk_context 会读取由调用方提供的 ID。优先级如下:
对象(或普通映射)上的 correlationId / correlation_id,然后是 sessionId /
session_id,再然后是 conversationId / conversation_id;接着是应用自有
state 上的同名字段;然后是 correlation_id= / session_id= /
conversation_id= 关键字参数;最后是外层的 arcjet_sequence。
它不会生成 ID。它不会读取 invocation_id(ADK 始终会生成该 ID)。它不会读取 trace_id。它不会读取
toolContext.sessionId / session.id(会话自动 ID),也不会遍历进入 .session。不要为每轮对话虚构关联 ID。将相同的 ID 同时传给辅助函数和
runner.run_async(...)。如果没有剩余的有效 ID,此调用将不关联任何对象,而不会关联到生成的 ID。
验证集成
python -m py_compile(或项目的类型检查)通过。- 测试入站 PI(在
run_async之前,包括has_failed_open())、guard_tool拒绝字典的跳过行为 (arcjetDenied: true)、guard_plugin拒绝字典的跳过行为、None执行、首个插件短路(Arcjet 位于首位)、不抛出异常、 绝不返回{}、绝不生成 ID、未提供actor/inputs(远程 规则静默处理)、解析器抛出异常(失败关闭,返回拒绝字典),以及 失败关闭(Guard 无法访问 → 拒绝字典,绝不返回None)。 确认require_confirmation绝不会被视为防护条件, 并且两个辅助函数叠加使用时,ALLOW 不会导致 Guard 被重复调用。 - 在 Arcjet Console / CLI 中确认各项决策共用调用方提供的会话 / 对话 ID,而不是
invocation_id或会话自动 ID;并确认actor是经过身份验证的调用方。 - 使用真实
ARCJET_KEY进行的手动端到端测试仍待验证,直到你实际运行它。
示例:
arcjet-py main 分支上的
examples/fastapi-google-adk-guard。不要另创第二个示例名称。不要在此 skills 仓库中添加示例。