数据截至 (上游 commit 3a4e2ae3eec0)
AgentScope — 架构与原理
30 秒导读: AgentScope 是阿里通义实验室开源的 Python agent 框架。它给你一个
Agent类,你塞进模型、工具、系统提示词,它就跑一个能被中途打断、能停下来等你点「同意」、能在上下文快满时自己总结压缩、跑完还能整个存盘的 ReAct 循环;上面再叠一层 FastAPI 服务,把单个 agent 变成多租户、多会话、可组队的应用。
本文档对应的是 AgentScope 2.0.6(src/agentscope/_version.py:4)。2.0 是一次推倒重来:1.x 里的 msghub、sequential_pipeline 这些编排原语在本 commit 的源码里已经完全不存在(全仓库 grep 为 0 命中),多智能体协作改由 app 层的团队工具 + 消息总线承担。
1. 这是什么(零基础也能懂)
一句话定义
AgentScope 是一个 agent 运行时:你描述「一个 agent 由哪个模型、哪些工具、什么提示词组成」,它负责把「模型说话 → 调工具 → 拿结果 → 再说话」这个循环可靠地跑起来。
解决谁的什么问题
假设你要做一个能改代码的终端助手。你很快会撞上四类问题,而它们都不是「调用模型」本身:
| 你会撞上的问题 | 具体表现 | AgentScope 的答复 |
|---|---|---|
| 危险操作 | 模型想跑 rm -rf build,你想先看一眼再放行 | 权限引擎 + 「等你确认」的可挂起状态 |
| 上下文爆炸 | 读了三个大文件,token 就满了 | 自动压缩历史 + 超大工具结果卸载到文件 |
| 用户中途反悔 | Ctrl+C 打断,但工具调用悬在半空没有结果 | 中断时给每个悬空调用补一条「已被中断」的结果 |
| 跑到一半要存盘 | 进程重启后要从上次的位置继续 | 全部运行时状态收敛进一个 AgentState |
给谁用:要把 agent 做成产品的后端工程师。不是给做研究 demo 的人用的——它的重量几乎全压在「工程可靠性」上。
它能做什么
- 跑 ReAct(推理-行动交替)循环,支持流式输出、结构化输出。
- 统一接八家模型 API:OpenAI、Anthropic、Gemini、DashScope、DeepSeek、Moonshot、xAI、Ollama。
- 自带编码工具:
Bash、Read、Write、Edit、Grep、Glob,以及任务规划工具TaskCreate/TaskList等。 - 接 MCP 服务器和「技能」(skill,一组 Markdown 指令 + 脚本)。
- 把工具执行放进沙箱:Docker、Apple Container、Bubblewrap、E2B、Daytona、K8s、OpenSandbox。
- 一条命令起一个 FastAPI 服务,带多租户、多会话、Web UI、飞书/Discord 接入。
用起来什么样
下面是仓库自带的最小例子(节选自 examples/console/main.py:47-84,真实可跑):
async with LocalWorkspace(workdir=args.workdir) as workspace:
agent = Agent(
name="Friday",
system_prompt="You are a helpful assistant named Friday. ...",
model=DashScopeChatModel(
credential=DashScopeCredential(api_key=api_key),
model=args.model,
stream=True,
),
toolkit=Toolkit(
tools=await workspace.list_tools(), # 文件工具绑到工作区后端
skills_or_loaders=await workspace.list_skills(),
),
offloader=workspace, # 超长内容卸载到工作区
)
await launch_console(agent, verbosity=args.verbosity)
launch_console(src/agentscope/console/_console.py:95)把终端里的流式渲染、工具调用确认、Ctrl+C 打断全包了——你写的只有「这个 agent 是谁」。
一句话直觉
把 Agent.reply() 想成一个「可暂停的函数调用」。
普通函数调用要么返回、要么抛异常。AgentScope 的一次 reply 多了第三种结局:停在半路——因为它要等你点确认、等外部系统回结果。这个「停在半路」不是挂着一个线程,而是把状态写进 AgentState;下次带着确认结果再调一次 reply(),它从断点接着跑。整个框架的绝大多数设计,都是这句话的推论。
2. 顶层全景(它大概怎么转)
2.1 部件图
怎么读这张图:中间是 Agent,左右是它依赖的四大部件;Toolkit 下面挂着安全与执行的两层。
┌───────────────┐
输入 Msg ─────►│ Agent │────► AgentEvent 流 ──► 控制台 / SSE / Web UI
└───────┬───────┘
┌──────────────────┼──────────────────┬────────────────┐
▼ ▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ AgentState │ │ Toolkit │ │ ChatModel │ │ Middleware │
│ 唯一可存盘 │ │ 工具/MCP/ │ │ + Formatter│ │ 七个挂点 │
│ 的状态包 │ │ 技能 │ │ 八家 API │ │ 洋葱链 │
└────────────┘ └─────┬──────┘ └────────────┘ └────────────┘
▼
┌────────────────────────┐
│ PermissionEngine │ 谁能跑
│ Workspace / Backend │ 在哪跑
└────────────────────────┘
2.2 部件一句话职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
Agent | 唯一的 agent 类,跑 reply 循环 | src/agentscope/agent/_agent.py:112 |
AgentState | 上下文、摘要、权限上下文、工具缓存、任务表——全部可序列化 | src/agentscope/state/_state.py:178 |
Toolkit | 注册/分组/查找工具,统一成流式调用 | src/agentscope/tool/_toolkit.py:66 |
PermissionEngine | 五种模式 × 三类规则,判 allow / deny / ask | src/agentscope/permission/_engine.py:17 |
ChatModelBase | 统一模型接口,自带重试与流式累加 | src/agentscope/model/_base.py:37 |
FormatterBase | 把 Msg 翻译成各家 API 的 JSON | src/agentscope/formatter/_formatter_base.py:22 |
MiddlewareBase | 七个挂载点,不改源码改行为 | src/agentscope/middleware/_base.py:13 |
WorkspaceBase | 工具在哪执行(本地/容器/沙箱),兼做卸载存储 | src/agentscope/workspace/_base.py:223 |
MessageBus | app 层的活跃传输层:队列 / 回 放日志 / 广播 | src/agentscope/app/message_bus/_base.py:53 |
2.3 主线走一遍
一次 await agent.reply(msg) 高层上发生这些事(不进代码):
① 收输入 把 Msg 追加进 state.context,开一个新的 reply_id
│
② 决策 _next_action 读当前状态,返回 Reasoning / Acting / Exit 之一
│
├─ Reasoning ─► 压缩上下文(若需要)→ 注入运行时状态 → 调模型 → 事件流
│
├─ Acting ────► 按并发安全性分批 → 逐个查权限 → 执行 → 结果写回上下文
│
└─ Exit ──────► 发 ReplyEndEvent,产出最终 Msg,退出
│
③ 回到 ② 每完成一轮「推理+它产生的全部工具调用都有结果」,cur_iter += 1
关键是 ② 是纯读:_next_action(src/agentscope/agent/_agent.py:3248)的 docstring 明写 "Read-only: all side effects are performed by the caller"。所有写状态的动作都发生在 _reply_impl 里。这条分工是整个框架能做到「随时挂起、随时恢复」的根。
2.4 一次 reply 的三种结局
| 结局 | 触发条件 | 外部看到什么 |
|---|---|---|
| 完成 | 模型给了纯文本回答,或结构化输出已生成 | ReplyEndEvent(finished_reason=COMPLETED) + 最终 Msg |
| 挂起 | 有工具调用在等用户确认 / 等外部执行 | 没有 ReplyEndEvent,只吐一条「我在等你」的 Msg |
| 中断 | Ctrl+C、上游取消、显式 UserInterruptEvent | 悬空调用被补上 INTERRUPTED 结果,再发 ReplyEndEvent(INTERRUPTED) |
「挂起」这一列是理解 AgentScope 的分水岭,第 1 章会把它拆开讲。
3. 阅读地图
建议按顺序读;每章都能独立跳源码。
| 章节 | 讲什么 | 什么时候读它 |
|---|---|---|
| 01-reply-loop.md | reply 状态机、事件协议、中断与 HITL 恢复 | 想搞懂 agent 循环怎么写才不乱——先读这章 |
| 02-tool-and-permission.md | Toolkit、工具组、权限五模式、Bash 静态分析 | 你要做「能改文件/跑命令」的 agent |
| 03-context-engineering.md | 压缩、卸载、运行时状态注入 | 你的 agent 老是把上下文撑爆 |
| 04-middleware-model-formatter.md | 中间件洋葱、模型降级、多家 API 适配 | 你要接新模型或插自定义逻辑 |
| 05-multi-agent-service.md | 消息总线、inbox 交接、团队工具、沙箱 | 你要做多 agent 协作或线上服务 |
| 06-essence-and-boundaries.md | 精华、坑、横向对比、总代码地图 | 读完想带走什么 / 想知道它哪里会崩 |
4. 代码地图(入口级)
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| agent 主类与循环 | src/agentscope/agent/_agent.py | Agent、_reply_impl、_next_action |
| 四个配置类 | src/agentscope/agent/_config.py | ContextConfig、InjectionConfig、ReActConfig、ModelConfig |
| 三种「下一步动作」 | src/agentscope/agent/_utils.py | Reasoning、Acting、Exit |
| 可存盘状态 | src/agentscope/state/_state.py | AgentState、ReplyContext、ToolContext |
| 消息与内容块 | src/agentscope/message/_block.py | ToolCallBlock、ToolCallState、HintBlock |
| 事件协议 | src/agentscope/event/_event.py | EventType、ReplyEndEvent、RequireUserConfirmEvent |
| 工具管理 | src/agentscope/tool/_toolkit.py | Toolkit、call_tool、check_tool_available |
| 权限引擎 | src/agentscope/permission/_engine.py | PermissionEngine、check_permission |
| 模型基类 | src/agentscope/model/_base.py | ChatModelBase、count_tokens |
| 中间件基类 | src/agentscope/middleware/_base.py | MiddlewareBase |
| 应用层入口 | src/agentscope/app/_service/_chat.py | ChatService、run |
| 最小可跑例子 | examples/console/main.py | main |