integrate-arcjet-guard-strands-agents-py
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegrate Arcjet Guard into Python Strands Agents
将Arcjet Guard集成到Python Strands Agents中
arcjet.guard.strands_agentsOfficial Python / only — not JS
(docs
https://docs.arcjet.com/guards/strands-agents/), not community forks.
Importing does not load LangChain, CrewAI,
or JS . Unlike CrewAI (no extra), the
extra exists at the pin
( in ).
The extra is safe (no chromadb).
strandsstrands-agents>=1.11.0,<2@arcjet/guard/strands-agents/v1arcjet.guard.strands_agents@arcjet/guard/strands-agents/v1arcjet[strands-agents]strands-agents = ["strands-agents>=1.11.0,<2"]pyproject.tomlExports: , , . Authored
plus / .
guard_toolguard_hooksstrands_agent_context@toolAgent(hooks=)add_hookThree surfaces, one decision rule:
- An authored →
@tool. Hand the agent the copy this returns — the original stays unguarded.guard_tool - Unwrapped / MCP tools → . Gate is per-tool
guard_hooks(BeforeToolCallEvent.cancel_toolorTrue). Already-wrapped tools are skipped so Guard is not called twice.str - Correlation → reads a caller-owned id from
strands_agent_context. It never mints. It never readsinvocation_state.trace_id
Docs: https://docs.arcjet.com/guards/strands-agents-py/. Example:
(pins ; no verify fixture). Do not invent a second example
name.
examples/fastapi-strands-agents-guarda6308061arcjet.guard.strands_agents仅支持官方Python / —— 不支持JS (文档地址:https://docs.arcjet.com/guards/strands-agents/),也不支持社区复刻版本。导入`arcjet.guard.strands_agents`不会加载LangChain、CrewAI或JS 。与CrewAI不同(无需额外依赖),存在额外依赖(在中定义为)。该额外依赖是安全的(不包含chromadb)。
strandsstrands-agents>=1.11.0,<2@arcjet/guard/strands-agents/v1@arcjet/guard/strands-agents/v1arcjet[strands-agents]pyproject.tomlstrands-agents = ["strands-agents>=1.11.0,<2"]导出内容:、、。需结合已编写的与 / 使用。
guard_toolguard_hooksstrands_agent_context@toolAgent(hooks=)add_hook三种应用场景,一套决策规则:
- 已编写的→ 使用
@tool。将该方法返回的副本交给代理——原工具保持未受防护状态。guard_tool - 未包装的/MCP工具 → 使用。通过每个工具的
guard_hooks(值为BeforeToolCallEvent.cancel_tool或字符串)实现防护。已包装的工具会被跳过,避免Guard被重复调用。True - 关联 → 从
strands_agent_context中读取调用方拥有的ID。它不会生成ID,也不会读取invocation_state。trace_id
The gate is per-tool BeforeToolCallEvent.cancel_tool
BeforeToolCallEvent.cancel_tool防护基于每个工具的BeforeToolCallEvent.cancel_tool
BeforeToolCallEvent.cancel_toolguard_hooksDENYon_guard_error="deny"ArcjetDenialResultTruecancel_tool"allow"DENYguard()has_failed_open()guard_hooksDENYon_guard_error="deny"ArcjetDenialResultTruecancel_tool"allow"DENYguard()has_failed_open()event.interrupt()
is not a policy gate
event.interrupt()event.interrupt()
并非策略防护
event.interrupt()BeforeToolCallEvent.interrupt()human_inputhumanInTheLoopMiddlewareinterrupt()needsApprovalinterrupt()BeforeToolCallEvent.interrupt()human_inputhumanInTheLoopMiddlewareinterrupt()needsApprovalinterrupt()Screen inbound before Agent(...)
/ __call__
/ stream_async
Agent(...)__call__stream_async在Agent(...)
/ __call__
/ stream_async
之前筛选入站消息
Agent(...)__call__stream_asyncThere is no inbound helper. Call in the application and
act on the decision. Core fails open: is not proof
the rules ran. Gate on if this call site
must fail closed; / already default to that.
aj.guard(...)guard()ALLOWdecision.has_failed_open()guard_toolguard_hooks没有入站辅助工具。请在应用中调用并根据决策执行操作。核心为故障开放:并不代表规则已执行。如果该调用点必须故障关闭,则需基于进行防护; / 已默认采用此逻辑。
aj.guard(...)guard()ALLOWdecision.has_failed_open()guard_toolguard_hooksQuestions to ask the human first
首先要向用户确认的问题
Ask only what you cannot infer from the code; suggest defaults.
- Which tools are risky (external side effects, irreversible, spends
money, sends messages)? Those get . MCP / unwrapped tools you did not author get
guard_tool.guard_hooks - What limits? (e.g. "10 lookups/min per user" → .)
TokenBucket - Who is the user for metadata — an opaque user/tenant ID (never PII)?
Default: none. Put the conversation / session id you already have on
and on
invocation_state. That id is the correlation id, not the user.guard_hooks(...) - Is an Arcjet outage unacceptable? Every helper defaults to
. Ask explicitly about inbound screening before the agent runs: failing closed there means the agent does not run, so
on_guard_error="deny"is a routine and legitimate choice at that one call site."allow"
仅询问无法从代码中推断的内容,并给出建议默认值。
- 哪些工具具有风险(存在外部副作用、不可逆转、产生费用、发送消息)?这些工具需要使用。你未编写的MCP/未包装工具则使用
guard_tool。guard_hooks - 需要设置哪些限制?(例如:“每个用户每分钟最多10次查询” → 使用。)
TokenBucket - 元数据对应的用户是谁——是不透明的用户/租户ID(绝不能是PII)?默认:无。将你已有的对话/会话ID放入以及
invocation_state中。该ID是关联ID,而非用户ID。guard_hooks(...) - Arcjet服务中断是否不可接受?所有辅助工具默认。请明确询问代理运行前的入站筛选:此处故障关闭意味着代理不会运行,因此
on_guard_error="deny"是该调用点的常规合法选择。"allow"
The things readers get wrong
读者容易误解的点
- This is not JS . Import
@arcjet/guard/strands-agents/v1.arcjet.guard.strands_agents - There is no inbound helper. Screen with core before
guard()/Agent(...)/__call__.stream_async - is HITL, not policy. Deny is
event.interrupt()(cancel_toolorTrue).str - Fail closed = always set on error. Do not raise and do not leave it unset.
cancel_tool - Correlation is read, never minted. Never , never
trace_id, never SessionManager auto-ids.agent.id - Already-wrapped tools are skipped by so Guard is not called twice. Hand the agent the copy
guard_hooksreturns.guard_tool - Key rate limits on the authenticated caller, not a model-supplied order id.
- Do not hand-wrap every Strands tool with raw .
guard()
- 这不是JS 。 请导入
@arcjet/guard/strands-agents/v1。arcjet.guard.strands_agents - 没有入站辅助工具。 请在/
Agent(...)/__call__之前使用核心stream_async进行筛选。guard() - 是人工介入(HITL),而非策略防护。 拒绝操作需设置
event.interrupt()(值为cancel_tool或字符串)。True - 故障关闭 = 错误时始终设置。 不要抛出异常,也不要留空。
cancel_tool - 关联ID仅读取,从不生成。 绝不会使用、
trace_id或SessionManager自动生成的ID。agent.id - 已包装的工具会被跳过,避免Guard被重复调用。请将
guard_hooks返回的副本交给代理。guard_tool - 关键速率限制基于已认证的调用方,而非模型提供的订单ID。
- 不要用原生手动包装每个Strands工具。
guard()
Step 1: Install and find the guard client
步骤1:安装并找到Guard客户端
Until-published: PyPI 0.9.0 does not include this module. Pin
to git SHA :
arcjetarcjeta630806169b92757192f3f5cce2e305827b26567bash
pip install "arcjet[strands-agents] @ git+https://github.com/arcjet/arcjet-py.git@a630806169b92757192f3f5cce2e305827b26567"If the agent has no guard client yet, launch one once at module scope:
python
import os
from arcjet.guard import launch_arcjet
aj = launch_arcjet(key=os.environ["ARCJET_KEY"])在正式发布前:PyPI上的 0.9.0不包含此模块。请将固定到Git SHA :
arcjetarcjeta630806169b92757192f3f5cce2e305827b26567bash
pip install "arcjet[strands-agents] @ git+https://github.com/arcjet/arcjet-py.git@a630806169b92757192f3f5cce2e305827b26567"如果代理还没有Guard客户端,请在模块作用域中初始化一次:
python
import os
from arcjet.guard import launch_arcjet
aj = launch_arcjet(key=os.environ["ARCJET_KEY"])Step 2: Gate authored tools — guard_tool
guard_tool步骤2:防护已编写的工具 —— guard_tool
guard_toolpython
from strands import Agent, tool
from arcjet.guard import DetectPromptInjection, TokenBucket, launch_arcjet
from arcjet.guard.strands_agents import guard_hooks, guard_tool, strands_agent_context
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,
)
mcp_limit = TokenBucket(
label="mcp.invoked",
bucket="mcp-access",
refill_rate=20,
interval_seconds=60,
max_tokens=20,
)
inbound = DetectPromptInjection()
user_id = authenticated_user_id
@tool # event.interrupt() is HITL — not this policy gate
def lookup_order(order_id: str) -> dict:
"""Look up an order by ID."""
return {"order_id": order_id, "status": "shipped"}
lookup_order = guard_tool(
guard=aj,
tool=lookup_order,
action="order.looked-up",
rules=[lookup_limit(key=user_id, requested=1)],
on_guard_error="deny",
)python
from strands import Agent, tool
from arcjet.guard import DetectPromptInjection, TokenBucket, launch_arcjet
from arcjet.guard.strands_agents import guard_hooks, guard_tool, strands_agent_context
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,
)
mcp_limit = TokenBucket(
label="mcp.invoked",
bucket="mcp-access",
refill_rate=20,
interval_seconds=60,
max_tokens=20,
)
inbound = DetectPromptInjection()
user_id = authenticated_user_id
@tool # event.interrupt()是人工介入(HITL)——并非此策略防护
def lookup_order(order_id: str) -> dict:
"""根据ID查询订单。"""
return {"order_id": order_id, "status": "shipped"}
lookup_order = guard_tool(
guard=aj,
tool=lookup_order,
action="order.looked-up",
rules=[lookup_limit(key=user_id, requested=1)],
on_guard_error="deny",
)Step 3: Gate unwrapped / MCP tools — guard_hooks
guard_hooks步骤3:防护未包装的/MCP工具 —— guard_hooks
guard_hookspython
mcp_tools = [] # from an MCP client you did not wrap with guard_tool
agent = Agent(
tools=[lookup_order, *mcp_tools],
# The agent-wide gate for tools guard_tool did not wrap.
# Already-wrapped tools are skipped, so Guard is not called twice.
# BeforeToolCallEvent.cancel_tool is True or a str (JSON of the payload).
hooks=[
guard_hooks(
guard=aj,
action="mcp.invoked",
rules=[mcp_limit(key=user_id, requested=1)],
session_id=conversation_id,
on_guard_error="deny",
),
],
)python
mcp_tools = [] # 来自你未用guard_tool包装的MCP客户端
agent = Agent(
tools=[lookup_order, *mcp_tools],
# 针对未被guard_tool包装的工具的代理级防护。
# 已包装的工具会被跳过,避免Guard被重复调用。
# BeforeToolCallEvent.cancel_tool的值为True或字符串(有效载荷的JSON格式)。
hooks=[
guard_hooks(
guard=aj,
action="mcp.invoked",
rules=[mcp_limit(key=user_id, requested=1)],
session_id=conversation_id,
on_guard_error="deny",
),
],
)Step 4: Screen inbound before the agent runs
步骤4:在代理运行前筛选入站消息
python
invocation_state = {"sessionId": conversation_id}
derived = strands_agent_context(invocation_state)
decision = await aj.guard(
label="message.received",
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")
agent(user_text, invocation_state=invocation_state)There is no inbound helper.
python
invocation_state = {"sessionId": conversation_id}
derived = strands_agent_context(invocation_state)
decision = await aj.guard(
label="message.received",
rules=[inbound(user_text)],
correlation_id=derived.correlation_id,
)
if decision.conclusion == "DENY":
raise RuntimeError("消息被拦截")
if decision.has_failed_open():
raise RuntimeError("入站Guard不可用")
agent(user_text, invocation_state=invocation_state)没有入站辅助工具。
Step 5: Correlation
步骤5:关联
strands_agent_contextinvocation_statecorrelationIdsessionIdrequestIdtrace_idagent.idguard_hooks(...)strands_agent_contextinvocation_statecorrelationIdsessionIdrequestIdtrace_idagent.idguard_hooks(...)Verify the integration
验证集成
- (or the project's type-check) passes.
python -m py_compile - Exercise inbound PI (before the agent runs), a deny, a
guard_tooldeny on an unwrapped tool (guard_hooksiscancel_toolor a str), a rate limit, and fail-closed (an unreachable guard). ConfirmTrueis never called as the gate.event.interrupt() - Confirm in the Arcjet Console / CLI that decisions share the
caller-owned session / request id — not a or
trace_id.agent.id - Manual E2E with a real is still-to-verify until you run it.
ARCJET_KEY
Worked example:
.
Do not invent a second example name. Do not add an example in this
skills repo.
examples/fastapi-strands-agents-guard- 执行(或项目的类型检查)通过。
python -m py_compile - 测试入站提示注入(代理运行前)、拒绝、未包装工具的
guard_tool拒绝(guard_hooks为cancel_tool或字符串)、速率限制以及故障关闭(Guard不可达)场景。确认True从未被用作防护机制。event.interrupt() - 在Arcjet控制台/CLI中确认决策共享调用方拥有的会话/请求ID —— 而非或
trace_id。agent.id - 使用真实的进行手动端到端测试,直到运行通过才算完成验证。
ARCJET_KEY
完整示例:。请勿创建第二个示例名称。请勿在此技能仓库中添加示例。
examples/fastapi-strands-agents-guard