跳到主要内容

总览:这是什么 / 全景图 / 主线 / 阅读地图

30 秒导读: Strands Agents 是一个 模型驱动(model-driven)的 agent 框架 / harness——你给它一个模型和一组工具,它负责把「问模型 → 模型说要调工具 → 执行工具 → 把结果再喂回模型 → 再问」这个循环自动转起来,直到模型说「我说完了」。核心就一个可递归的事件循环 event_loop_cycle,其余所有能力(流式、hooks、人类介入、中断续跑、会话持久化、沙箱、多 agent)都是挂在这个循环上的横切件。

本章是这套文档的 路由页 + 全景图:只讲「这是什么、大盘怎么转、主线走一遍、该读哪一章」,不深入任何单一子系统。细节留给 01–06 分章。


1. 这是什么(零基础也能懂)

一句话定义

Strands 是一个「让模型自己决定下一步做什么」的 agent 运行时。 你不用写「先调 A 工具、再调 B」这种流程编排;你把工具交给模型,模型在每一回合自己选择要不要调工具、调哪个,框架负责把这个来回执行到底。这就是 README 说的 "model-driven approach"——决策权在模型,框架只提供 手脚(工具执行)+ 循环(event loop)+ 护栏(控制面)

解决谁的什么问题

假设你想让一个大模型「真的能干活」,而不只是聊天:让它查数据库、跑代码、读文件、调 API。你会立刻撞上三件麻烦事:

  • 模型只会 「我要调 search(query=...)」,它自己不会真的去调——得有人把这句话解析出来、真的执行、再把结果塞回对话。
  • 一次不够:模型往往要 调好几轮工具 才能回答,你得循环。
  • 生产环境还要:换模型不改代码、每步能拦截审计、能中断等人批准、能存档续跑、能沙箱隔离危险操作。

Strands 把这些全部内建。用它的话:你只描述「有哪些工具、用哪个模型」,循环和治理交给框架。

它能做什么(功能一览)

能力说明
模型无关一套 Model 抽象,内置 Bedrock / Anthropic / OpenAI / Gemini / Ollama / LiteLLM 等 provider
工具系统@tool 装饰器一行把 Python 函数变工具;支持 MCP、目录热加载、并发执行
流式回合全程 async 流式,边生成边产出事件(文本增量、工具调用、结果)
控制面hooks(生命周期回调)、干预(intervention:放行/拒绝/引导/确认)、人类介入
中断续跑工具可抛 interrupt,循环暂停、等外部输入、再从原地续跑
上下文治理上下文注入、记忆抽取、会话持久化(文件/S3)、检查点(checkpoint)
沙箱把工具/代码放进 POSIX shell / Docker / SSH 沙箱执行
多 agentgraph(图编排)、swarm(群体)、agent-as-tool、A2A 协议

用起来什么样(最小示例)

这段演示 Strands 最核心的用法:定义一个工具、建一个 agent、直接把它当函数调。

# 示意,非源码 —— 但贴近真实 API
from strands import Agent, tool

@tool # 一行:普通函数 -> agent 可用的工具
def word_count(text: str) -> int:
"""Count words in the text.""" # docstring 会变成给模型看的工具说明
return len(text.split())

agent = Agent(tools=[word_count]) # 只说「有什么工具」,不写流程
result = agent("How many words in 'hello brave new world'?")
# 内部:问模型 -> 模型说调 word_count -> 框架执行 -> 结果回喂 -> 模型给出最终答复
print(result.message)

AgenttoolSandboxPluginInterventionHandlerSnapshot 等都是包顶层的公开导出,见 strands-py/src/strands/__init__.py:19__all__。(注:Model 不在顶层 __all__ 里——顶层只导出 models 子模块和 ModelRetryStrategy,Modelstrands.models 导入,见 03-models.md。)

一句话直觉 / 类比

把 agent 想成一个「带工具箱的对话循环」: 模型是大脑,工具是手脚,event_loop_cycle 是让大脑和手脚来回配合的那根传送带。你只需要往工具箱里放工具、指定用哪个大脑,传送带会一直转到大脑说「done」。


2. 顶层全景(它大概怎么转)

主回路结构图

下图是 Strands 的心脏。怎么读: 从上往下是一次调用的正常流向;右侧的横切件是「挂」在回路各处的治理钩子;底部那条 虚线回环 是关键——工具跑完不是结束,而是把结果并回消息、递归调用自己 再问一次模型。

agent("...") ┌─────────── 横切治理(挂在回路各点)──────────┐
│ __call__ -> invoke_async │ │
▼ -> stream_async │ hooks Before/AfterInvocation │
┌──────────────────────────────┐ │ _middleware InvokeModelStage 包裹模型调用 │
│ _run_loop(每次用户输入一轮) │◀────│ interventions 放行/拒绝/引导/确认 工具 │
└──────────────┬───────────────┘ │ interrupt 工具暂停 -> 等外部输入 -> 续跑 │
▼ │ telemetry 每个 cycle/tool 的 trace+metric│
┌──────────────────────────────┐ │ session 每步落盘,可续跑 │
│ event_loop_cycle │ └────────────────────────────────────────────┘
│ (一个「回合」= 一次问模型) │
└──────────────┬───────────────┘

┌───────────────────┐ stream_messages() ┌──────────────┐
│ ① Model.stream(…) │──────────────────────▶│ 模型 provider │
│ 流式拿回复 │◀──────────────────────│ (Bedrock/OpenAI…)│
└─────────┬─────────┘ └──────────────┘

stop_reason ?
├── "end_turn" ─────────────▶ EventLoopStopEvent(结束,返回 AgentResult)
├── "max_tokens"────────────▶ 抛 MaxTokensReachedException
└── "tool_use"

┌───────────────────┐
│ ② 执行工具 │ ToolExecutor(并发/顺序) + 干预 + 中断检查
└─────────┬─────────┘

把 toolResult 并回 messages

┌───────────────────┐
│ ③ recurse_event_loop ← 关键:带着新结果再问一次模型 │
└───────────────────┘ (虚线回环,直到某回合 stop_reason=="end_turn")

一句话:回路只有两种出口——模型说「说完了」就停,说「要调工具」就执行完再递归回到 ①。

部件一句话职责表

部件干什么目录 / 关键符号
agent/对外门面:Agent 类、__call__/invoke_async/stream_async、状态、会话装配agent/agent.py class Agent
event_loop/主线:一个回合的完整生命周期 + 递归 + 流式解析 + 重试event_loop/event_loop.py event_loop_cycle
tools/工具系统:@tool 装饰、注册表、并发/顺序执行器、MCP、结构化输出tools/decorator.py tool;tools/registry.py ToolRegistry
models/模型抽象层:一个 Model 抽象基类 + 各 provider 实现models/model.py class Model
hooks/生命周期事件总线:Before/After...Event,注册回调hooks/registry.py HookRegistry
_middleware/洋葱式中间件,当前只包裹模型调用(InvokeModelStage)_middleware/stages.py_middleware/types.py
interventions/工具级策略闸:Proceed/Deny/Guide/Confirm/Transforminterventions/handler.py InterventionHandler
interrupt.py中断原语:工具抛 InterruptException 暂停循环,等外部输入再续跑interrupt.py Interrupt_InterruptState
injection/上下文注入:调用前把即时文本折进模型输入,不动持久历史injection/types.py InjectionConfigInjectionContext
memory/记忆:按触发条件从对话抽取信息、写入记忆存储memory/memory_manager.py MemoryManager
session/会话持久化:每步落盘(文件 / S3 / 自定义仓库),可续跑session/session_manager.py SessionManager
sandbox/沙箱执行:POSIX shell / Docker / SSH 隔离环境跑命令与代码sandbox/base.py class Sandbox
multiagent/多 agent 编排:graph、swarm、A2A、agent-as-toolmultiagent/graph.pymultiagent/swarm.py
telemetry/可观测:OpenTelemetry trace + 指标(每 cycle / tool)telemetry/tracer.py Tracer;telemetry/metrics.py EventLoopMetrics
plugins/插件:一个对象同时打包 hooks + tools + init_agent 装配逻辑plugins/plugin.py class Plugin

一次调用的高层数据流

输入 用户 prompt(字符串 / 多模态内容 / 完整消息列表 / 空) Agent 归一化成 messages,触发 BeforeInvocationEvent 进入 event_loop_cycle:调模型流式拿回复 tool_use:过干预闸、执行工具、并回结果、递归再问 end_turn:收尾、触发 AfterInvocationEvent 输出 AgentResult(stop_reason + 最终 message + metrics + 状态 + 可选结构化输出)


3. 主线走一遍(高层,不进代码)

这一节把「一次 agent() 调用」从门面追到心脏,只看 谁调谁,代码细节留给 01 章。

从同步门面到 async 循环

Agent 对外是同步的,内部全是 async。调用链是一条「同步壳 → async 生成器」的漏斗:

agent("...") # 同步,给不写 async 的用户
= Agent.__call__ agent/agent.py:697
└─ run_async( _invoke_async_and_flush ) # 起一个事件循环跑 async
└─ invoke_async agent/agent.py:779 # 把流式事件耗尽,只取最后的 result
└─ stream_async agent/agent.py:1064 # 真正的 async 生成器,逐事件产出
└─ _run_loop agent/agent.py:1220
└─ _execute_event_loop_cycle agent/agent.py:1318
└─ event_loop_cycle event_loop/event_loop.py:182

四层各干一件事,分工清晰:

  • __call__:同步入口,用 run_async 把下面整条 async 链跑完,顺带在结束时 flush 记忆(agent/agent.py:766 _invoke_async_and_flush)。
  • invoke_async:调 stream_async 并把事件流 耗尽,只返回最后一个 result(agent/agent.py:844)。想要流式的人直接用 stream_async
  • stream_async:门面的重活都在这——并发闸(同一 agent 不许并发,ConcurrencyException)、幂等去重、限额校验、把 prompt 转成 messages、开 trace span,然后驱动 _run_loop 并逐事件 yield(agent/agent.py:1128 起)。
  • _run_loop:外层 while——每次「用户输入(含 hook 请求的 resume)」转一轮;每轮触发 BeforeInvocationEvent / AfterInvocationEvent,并调 _execute_event_loop_cycle
  • _execute_event_loop_cycle:薄薄一层 上下文溢出重试——若 event_loop_cycleContextWindowOverflowException,就让 conversation_manager.reduce_context 压缩历史后 递归重试(agent/agent.py:1354)。

心脏:一个 cycle 的两种出口

真正的「一个回合」在 event_loop_cycle(event_loop/event_loop.py:182)。它先查限额,再决定这一回合要不要调模型,拿到 stop_reason 后分岔:

  • end_turn → 收尾,yield EventLoopStopEvent,循环结束(event_loop/event_loop.py:380)。
  • max_tokens → 抛 MaxTokensReachedException(event_loop/event_loop.py:305)。
  • tool_use → 走 _handle_tool_execution(event_loop/event_loop.py:698):执行工具、把 toolResult 并回消息,然后 recurse_event_loop(event_loop/event_loop.py:398)带着新结果再问一次模型。

递归就是「多轮工具」的实现方式——不是显式 for 循环,而是每处理完一批工具就递归调用自己,直到某一回合模型返回 end_turn。这条主线 01 章会逐行走。


4. 阅读地图(建议顺序)

由浅入深、由主线到横切 排序;每章一句话点明讲什么。

顺序章节一句话
101-agent-loop.md主线:event_loop_cycle 的递归结构、流式回合、stop_reason 分岔、上下文溢出重试——把第 3 节的主线逐行走透。
202-tools.md工具系统:@tool 如何把函数变工具、ToolRegistry 注册、并发 vs 顺序执行器、MCP 集成、结构化输出。
303-models.md模型抽象层:Model 抽象基类的统一 stream/structured_output 契约,以及各 provider 如何把厂商 API 归一化。
404-control-plane.md控制面:hooks 事件总线、_middleware 洋葱、interventions 策略闸、人类介入——如何在不改循环的前提下拦截每一步。
505-context-and-durability.md上下文与持久化:injection 注入、memory 抽取、session 落盘、checkpoint 中断续跑——agent 的「记忆与耐久」。
606-sandbox-and-multiagent.md越过单 agent:sandbox 隔离执行,以及 graph / swarm / A2A / agent-as-tool 的多 agent 编排。

推荐路径: 只想懂原理 → 读 01;想扩展工具/接模型 → 加 02、03;做生产治理 → 04、05;搞多 agent / 安全隔离 → 06。


5. 巧妙之处速览

这些是读完最该带走的设计决策,细节在各分章展开:

  • 「多轮工具」用递归而非循环。 event_loop_cycle 处理完工具后调 recurse_event_loop 再进入自己(event_loop/event_loop.py:398)。每一回合是一个干净的栈帧,trace 天然形成父子树(Trace("Recursive call", parent_id=cycle_trace.id)),多轮对话的可观测性几乎白送。

  • 同步壳 + async 核。 公开 API 有 __call__/invoke 同步壳,底下全是 async 生成器,invoke_async 只是把 stream_async 的事件流耗尽取尾(agent/agent.py:844)。「一切皆事件流」:流式和非流式共用同一条代码路径,没有两套逻辑。

  • 上下文溢出是「重试」不是「报错」。 _execute_event_loop_cycle 捕获 ContextWindowOverflowException,压缩历史后递归重试(agent/agent.py:1354),对上层完全透明。

  • 中断跳过模型、直接进工具。 cycle 一开始若发现处于 interrupt 状态或最新消息已含 toolUse,就 跳过模型调用,直接把 stop_reason 设成 tool_use(event_loop/event_loop.py:283-289)——这让「暂停等人 → 续跑」不会浪费一次模型调用。

  • 中间件「最后一个 yield 即返回值」。 Python 异步生成器不能像 TS 那样 return 值,于是约定 最后产出的事件就是结果,整条中间件链对事件透明(_middleware/README.md)。短路缓存只需直接 yield 一个 ModelStopReason

  • 干预必须在类层面声明。 InterventionHandler 只检测 子类重写的方法,实例级赋值不生效(interventions/handler.py:43 docstring)——框架据此只调用你真正关心的生命周期点,避免全量回调开销。

  • 检查点在「回合边界」精确落点。 循环在 after_model(模型说要调工具、工具还没跑)和 after_tools(工具跑完)两个位置发检查点事件(event_loop/event_loop.py:326:858),续跑时据 position 决定 cycle_index 是否 +1,做到「从原地续」而非「重跑」。


6. 顶层代码地图(跳转表)

符号名 定位(比行号抗上游漂移);行号 as-of sourceCommit

主题文件关键符号
公开导出面strands-py/src/strands/__init__.py__all__(AgenttoolSandboxPluginInterventionHandlerSnapshot)
Agent 门面 / 构造strands-py/src/strands/agent/agent.pyclass AgentAgent.__init__
同步入口strands-py/src/strands/agent/agent.pyAgent.__call__(:697)、_invoke_async_and_flush(:766)
async 主入口strands-py/src/strands/agent/agent.pyinvoke_async(:779)、stream_async(:1064)
外层轮 / 重试strands-py/src/strands/agent/agent.py_run_loop(:1220)、_execute_event_loop_cycle(:1318)
心脏:回合strands-py/src/strands/event_loop/event_loop.pyevent_loop_cycle(:182)
递归再问strands-py/src/strands/event_loop/event_loop.pyrecurse_event_loop(:398)
工具执行分支strands-py/src/strands/event_loop/event_loop.py_handle_tool_execution(:698)
循环常量strands-py/src/strands/event_loop/event_loop.pyMAX_ATTEMPTSINITIAL_DELAYMAX_DELAY(:58-60)
流式解析strands-py/src/strands/event_loop/streaming.pystream_messages(:465)、process_stream(:394)
工具装饰器strands-py/src/strands/tools/decorator.pytoolDecoratedFunctionTool
工具注册 / 执行strands-py/src/strands/tools/registry.pytools/executors/ToolRegistryConcurrentToolExecutorSequentialToolExecutor
模型抽象strands-py/src/strands/models/model.pyclass Model(:161)、streamstructured_output
hooks 总线strands-py/src/strands/hooks/registry.pyHookRegistryinvoke_callbacks_async
中间件strands-py/src/strands/_middleware/stages.pyMiddlewareStage(types.py)
干预闸strands-py/src/strands/interventions/handler.pyInterventionHandler;interventions/actions.py Proceed/Deny/Guide/Confirm/Transform
中断strands-py/src/strands/interrupt.pyInterrupt_InterruptState
上下文注入strands-py/src/strands/injection/types.pyInjectionConfigInjectionContext
记忆strands-py/src/strands/memory/memory_manager.pyMemoryManager
会话持久化strands-py/src/strands/session/session_manager.pySessionManagersync_agent
沙箱strands-py/src/strands/sandbox/base.pyclass Sandboxexecuteexecute_code
多 agentstrands-py/src/strands/multiagent/graph.pyswarm.py Swarmbase.py MultiAgentBase
遥测strands-py/src/strands/telemetry/tracer.py Tracermetrics.py EventLoopMetrics
插件strands-py/src/strands/plugins/plugin.pyclass Plugin(hooks + tools + init_agent)

说明:本文档仅解剖 strands-py/(Python SDK)。仓库同时含 strands-ts/(TypeScript SDK,目标是概念/命名对等)、strandly/(CLI)、site/(文档站)——不在本章范围。