总览:这是什么 / 全景图 / 主线 / 阅读地图
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 沙箱执行 |
| 多 agent | graph(图编排)、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)
Agent、tool、Sandbox、Plugin、InterventionHandler、Snapshot 等都是包顶层的公开导出,见 strands-py/src/strands/__init__.py:19 的 __all__。(注:Model 不在顶层 __all__ 里——顶层只导出 models 子模块和 ModelRetryStrategy,Model 从 strands.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/Transform | interventions/handler.py InterventionHandler |
interrupt.py | 中断原语:工具抛 InterruptException 暂停循环,等外部输入再续跑 | interrupt.py Interrupt、_InterruptState |
injection/ | 上下文注入:调用前把即时文本折进模型输入,不动持久历史 | injection/types.py InjectionConfig、InjectionContext |
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-tool | multiagent/graph.py、multiagent/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_cycle抛ContextWindowOverflowException,就让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. 阅读地图(建议顺序)
按 由浅入深、由主线到横切 排序;每章一句话点明讲什么。
| 顺序 | 章节 | 一句话 |
|---|---|---|
| 1 | 01-agent-loop.md | 主线:event_loop_cycle 的递归结构、流式回合、stop_reason 分岔、上下文溢出重试——把第 3 节的主线逐行走透。 |
| 2 | 02-tools.md | 工具系统:@tool 如何把函数变工具、ToolRegistry 注册、并发 vs 顺序执行器、MCP 集成、结构化输出。 |
| 3 | 03-models.md | 模型抽象层:Model 抽象基类的统一 stream/structured_output 契约,以及各 provider 如何把厂商 API 归一化。 |
| 4 | 04-control-plane.md | 控制面:hooks 事件总线、_middleware 洋葱、interventions 策略闸、人类介入——如何在不改循环的前提下拦截每一步。 |
| 5 | 05-context-and-durability.md | 上下文与持久化:injection 注入、memory 抽取、session 落盘、checkpoint 中断续跑——agent 的「记忆与耐久 」。 |
| 6 | 06-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:43docstring)——框架据此只调用你真正关心的生命周期点,避免全量回调开销。 -
检查点在「回合边界」精确落点。 循环在
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__(Agent、tool、Sandbox、Plugin、InterventionHandler、Snapshot) |
| Agent 门面 / 构造 | strands-py/src/strands/agent/agent.py | class Agent、Agent.__init__ |
| 同步入口 | strands-py/src/strands/agent/agent.py | Agent.__call__(:697)、_invoke_async_and_flush(:766) |
| async 主入口 | strands-py/src/strands/agent/agent.py | invoke_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.py | event_loop_cycle(:182) |
| 递归再问 | strands-py/src/strands/event_loop/event_loop.py | recurse_event_loop(:398) |
| 工具执行分支 | strands-py/src/strands/event_loop/event_loop.py | _handle_tool_execution(:698) |
| 循环常量 | strands-py/src/strands/event_loop/event_loop.py | MAX_ATTEMPTS、INITIAL_DELAY、MAX_DELAY(:58-60) |
| 流式解析 | strands-py/src/strands/event_loop/streaming.py | stream_messages(:465)、process_stream(:394) |
| 工具装饰器 | strands-py/src/strands/tools/decorator.py | tool、DecoratedFunctionTool |
| 工具注册 / 执行 | strands-py/src/strands/tools/registry.py、tools/executors/ | ToolRegistry、ConcurrentToolExecutor、SequentialToolExecutor |
| 模型抽象 | strands-py/src/strands/models/model.py | class Model(:161)、stream、structured_output |
| hooks 总线 | strands-py/src/strands/hooks/registry.py | HookRegistry、invoke_callbacks_async |
| 中间件 | strands-py/src/strands/_middleware/stages.py | MiddlewareStage(types.py) |
| 干预闸 | strands-py/src/strands/interventions/handler.py | InterventionHandler;interventions/actions.py Proceed/Deny/Guide/Confirm/Transform |
| 中断 | strands-py/src/strands/interrupt.py | Interrupt、_InterruptState |
| 上下文注入 | strands-py/src/strands/injection/types.py | InjectionConfig、InjectionContext |
| 记忆 | strands-py/src/strands/memory/memory_manager.py | MemoryManager |
| 会话持久化 | strands-py/src/strands/session/session_manager.py | SessionManager、sync_agent |
| 沙箱 | strands-py/src/strands/sandbox/base.py | class Sandbox、execute、execute_code |
| 多 agent | strands-py/src/strands/multiagent/ | graph.py、swarm.py Swarm、base.py MultiAgentBase |
| 遥测 | strands-py/src/strands/telemetry/ | tracer.py Tracer、metrics.py EventLoopMetrics |
| 插件 | strands-py/src/strands/plugins/plugin.py | class Plugin(hooks + tools + init_agent) |
说明:本文档仅解剖
strands-py/(Python SDK)。仓库同时含strands-ts/(TypeScript SDK,目标是概念/命名对等)、strandly/(CLI)、site/(文档站)——不在本章范围。