Swarm studio cn
Swarm Studio 是 Jianmu 的多 Agent Web 应用,以对话协作为核心,把 AgentRuntime(Swarm 运行时)放进一套完整的持久化、SSE 实时事件流和 React 前端界面中。它关注的不只是“把多个 Agent 放进一个聊天框”,而是把群聊协作里最容易失控的几件事约束清楚:谁在说话、消息走哪条通道、一次唤醒如何结束。这样多个专家 Agent 才能在可视化界面里进行可追踪、可恢复的结构化对话。
如果只想先建立整体印象,可以先抓住三件事:
- 它的产品模型是“模板 × 角色 × 对话”
- 它的运行约束是 Identity、Channel、Turn 三条规则
- 它的技术路径是 React 前端 + FastAPI 后端 + Jianmu Runtime 内核
架构全景¶
Swarm Studio 的运行时架构分为五个层次:前端 SPA → FastAPI 路由层 → ChatController 编排层 → ChatRuntimeAdapter 运行适配层 → Jianmu AgentRuntime 内核。前端通过 SSE(Server-Sent Events)长连接接收实时事件推送,后端将 Jianmu 的底层 Agent 事件(AgentEvent)翻译为面向 UI 的 chat_event 和 workspace_event,同时将用户消息转化为 Agent 收件箱中的 MessageEnvelope。
graph TB
subgraph Frontend["Frontend (React + Vite)"]
CP[ConversationPage]
AP[AgentsPage]
HP[HistoryPage]
CSP[ConversationsPage]
SSE_STREAM[SSE Event Stream]
end
subgraph Backend["Backend (FastAPI)"]
API[REST API /api/chat/*]
CC[ChatController]
CRA[ChatRuntimeAdapter]
ST[Storage - SQLite]
PRS[ProviderRuntimeSupport]
end
subgraph Jianmu["Jianmu Core"]
AR[AgentRuntime]
AT[AgentTeam]
MC[ModelClient]
SC[SkillsCatalog]
end
CP --> SSE_STREAM
CP --> API
API --> CC
CC --> CRA
CC --> ST
CRA --> AR
CRA --> AT
CRA --> PRS
CRA --> ST
CRA --> MC
CRA --> SC
AR --> MC
核心产品模型:「模板 × 角色 × 对话」¶
Swarm Studio 的核心数据模型围绕三个概念展开:
| 概念 | 数据库表 | 定位 | 生命周期 |
|---|---|---|---|
| Agent Profile | contacts |
可复用的专家模板/配置对象 | 持久化,跨对话复用 |
| Workspace Agent | workspace_agents |
工作空间中的运行时角色 | 由 Profile 实例化,绑定到 workspace |
| Conversation | conversations + chat_messages |
协作界面(群聊 or 私聊) | 消息积累,可归档为 History Snapshot |
可以把它理解为:Agent Profile 是模板,Workspace Agent 是实例化后的角色,Conversation 是这些角色协作的场景。Human 用户也会被持久化为一个特殊的 Agent(human-{workspace_id})。Direct Chat 本质上仍是一个两成员对话,因此整套消息路由不需要为“私聊”和“群聊”分成两套模型。
数据库表全景¶
Swarm Studio 使用 SQLite 作为持久化存储,包含以下核心表:
| 表名 | 用途 | 关键索引 |
|---|---|---|
contacts |
Agent Profile 配置(名称、角色、专注领域、系统提示词、模型、最大迭代次数) | updated_at DESC |
workspace_agents |
工作空间中的 Agent 运行时实例 | 关联 contact_id |
conversations |
对话元数据(name, kind, member_agent_ids, metadata) | updated_at DESC |
conversation_members |
对话-成员关联 + last_read_message_id |
(conversation_id, contact_id) |
conversation_agents |
对话中的运行时 Agent 列表 | (conversation_id, agent_id) |
chat_messages |
所有消息(sender_id, sender_type, content, kind, reply_policy) | (conversation_id, created_at) |
chat_reads |
已读状态追踪 | (conversation_id, reader_id) |
chat_events |
事件日志(AUTOINCREMENT id,用于 SSE since_id 分页) | (conversation_id, created_at) |
chat_agent_states |
Agent 运行时状态(status, last_error, metadata) | (conversation_id, updated_at DESC) |
chat_agent_memories |
Agent 对话记忆(llm_history, reasoning, tool_calls, open_loops 等) | (conversation_id, updated_at DESC) |
agent_global_memories |
Agent 全局记忆(summary, facts, preferences) | (workspace_id, updated_at DESC) |
chat_history_snapshots |
对话历史快照(完整消息归档) | (conversation_id, created_at DESC) |
三条硬合约:Identity、Channel、Turn¶
与仅依赖 Prompt 引导的多 Agent 方案相比,Swarm Studio 在运行时层面补充了三条明确约束,用来限制 Agent 的对话行为。这些约束被写进工具描述、消息结构和事件处理流程,而不只是停留在 System Prompt 中。
Identity 合约:名称解析与引用消歧¶
Agent 之间通过 @name 互相引用。运行时的 _resolve_agent_reference 方法执行多级消歧匹配:首先按 agent_id 精确匹配,然后扫描所有 Agent Profiles 的 display_name 和 metadata.aliases 字段,使用归一化键(去空格、去连字符、小写)进行模糊匹配。当匹配到多个候选时,返回 ambiguous_target 错误并列出所有候选项,由调用 Agent 自行澄清——这避免了「错误 @ 人」导致的对话混乱。
Channel 合约:私聊作为显式 Side Thread¶
当 Agent A 使用 send_direct_message 工具向 Agent B 发送私聊时,系统自动创建一个 kind=direct 的新 Conversation,并将该私聊对话标记为源对话的 Side Thread。Agent 在完成私聊追问后,需要通过 sync_back_to_source 将关键信息同步回源对话,或显式调用 dismiss_sync_back 声明无需回传。这种显式合约避免了「信息在私聊中丢失」的常见问题。对话诊断接口(/api/chat/conversations/{id}/diagnostics)会暴露未完成的 sync-back 义务和脏 Direct Thread。
Turn 合约:每次唤醒必须以显式 Action 结束¶
每当 Agent 被唤醒处理未读消息时,它在本轮结束后必须执行以下动作之一:send_message(公开回复)、send_direct_message(私聊追问)、no_send(明确选择不发言)、sync_back_to_source、或 dismiss_sync_back。Turn 合约通过 reply_policy 字段控制强度:
| reply_policy | 含义 | 强制执行 |
|---|---|---|
optional |
可选择是否回复 | 否(但空转触发 no_send_retry 提醒) |
preferred |
建议回复 | 否 |
required_once |
必须回复一次 | 是(自动发布 final_answer 兜底) |
当 Agent 在一轮中未执行任何 send 动作时,系统会自动注入 no_send_retry 提醒消息到其收件箱,并在 Human Direct Mention 场景下加强催促语气。
运行时生命周期:从启动到自动发布¶
Conversation Runtime 的创建与装配¶
当用户向某个对话发送消息时,ensure_conversation_runtime 被调用。该方法执行以下流程:
sequenceDiagram
participant U as User (Frontend)
participant API as FastAPI
participant CC as ChatController
participant CRA as ChatRuntimeAdapter
participant AR as AgentRuntime (Jianmu)
participant DB as SQLite
U->>API: POST /api/chat/conversations/{id}/messages
API->>CC: post_human_message()
CC->>DB: append message
CC->>CRA: wake_agents_for_message()
CRA->>CRA: ensure_conversation_runtime()
Note over CRA: 1. Create AgentTeam with ModelClient
CRA->>AR: AgentTeam.runtime
CRA->>AR: runtime.subscribe_events()
Note over CRA: 2. For each member agent:
CRA->>DB: get agent profile
CRA->>CRA: _build_role_prompt()
CRA->>AR: register_role(StudioAgentRole)
CRA->>AR: runtime.spawn(role, task)
Note over CRA: 3. Build tools per agent
CRA->>CRA: _build_tools()
Note over CRA: 4. Set auto_mode = True
CRA->>DB: upsert agent state (idle)
CRA-->>U: SSE event: agent.wakeup
可以先抓住三个结果:
- 每个 Conversation 对应一个独立的 AgentRuntime 实例,所有成员 Agent 共享这个 Runtime
- 每个 Agent 使用经过包装的 ModelClient,调用前会注入记忆摘要、未读批处理提示和 Turn Contract 上下文
- Agent 默认进入 auto_mode,收件箱收到消息后会自动被 Runtime 调度
消息唤醒与批处理¶
wake_agents_for_message 负责消息分发。它不会把消息广播给所有 Agent,而是按目标、未读状态和回复策略做选择性唤醒:
- 目标解析:如果发送者是 Agent(非 Human),从消息内容中提取
@mention目标,并结合当前有required_once活跃 Turn 的 Agent 列表 - 未读批处理:为每个目标 Agent 查询未处理消息(
list_chat_unprocessed_messages),选择触发消息(_pick_unread_trigger_message) - Turn 排队:根据
reply_policy和优先级(human_direct_mention>required_once>optional)将触发消息入队到 Agent 的pending_reply_turns - 忙检测:如果 Agent 当前正在运行(
host_status == "running")或有活跃的 Reply Turn,则只更新元数据而不注入新消息——避免打断正在进行的推理 - 消息注入:构建
_build_unread_batch_prompt,将多条未读消息压缩为一条「未读批处理提示」注入 Agent 收件箱,其中包含消息发送者、回复策略、以及格式化的 Delta Lines - 唤醒:调用
runtime.wake()激活 Agent
Runtime 事件处理与自动发布¶
_handle_runtime_event 是 Runtime 事件到 UI 事件的翻译器。它处理来自 Jianmu 内核的所有 AgentEvent,并根据事件类型执行不同策略:
| 事件类型 | 处理逻辑 |
|---|---|
agent_wake / agent_resumed |
更新状态为 running/idle,发射 ui.agent.state.changed |
agent_run_completed |
核心路径:提取 final_answer、检查 Turn Contract、执行自动发布 |
agent_mailbox_continue (FAILURE) |
区分 rate_limited 错误和普通错误;rate_limited 时设置退避计时器并延迟重试 |
agent_run_completed 中最重要的一步是自动发布:系统会按优先级从 Turn Contract、final_answer、Skill 输出以及最后一条 assistant 消息中提取可发布文本,然后调用 _publish_agent_message 发回对话。这样即使 Agent 没有显式调用 send_message,大多数情况下它的有效输出仍然能被保留下来。
工具系统:Agent 的能力边界¶
Swarm Studio 为每个 Agent 动态构建工具集。工具构建函数 _build_tools 接收 conversation_id、runtime_agent_id 和 sender_agent_id,返回一个包含以下工具的列表:
| 工具名 | 描述 | 副作用标签 | 并行安全 |
|---|---|---|---|
send_message |
向当前对话发送可见消息 | message_send |
否 |
send_direct_message |
向另一个 Agent 发送私聊(自动创建 Direct Conversation) | message_send |
否 |
get_conversation_messages |
查看对话中的可见消息 | context_fetch |
否 |
list_conversations |
列出工作空间中可见的对话 | — | 是 |
list_conversation_members |
列出指定对话的成员 | — | 是 |
list_agents |
列出系统中可用的专家身份 | — | 是 |
list_agent_profiles |
列出可复用的 Agent 模板 | — | 是 |
self |
返回当前 Agent 的身份和默认对话上下文 | — | 是 |
每个工具在执行前后通过 emit_tool_event 发射 tool.{name}.started / tool.{name}.completed 事件,同时更新 Agent 状态为 running,确保前端能实时观测到工具调用进度。send_message 和 send_direct_message 还具备重复消息抑制机制——通过消息指纹(sender + conversation + kind + content 的 SHA256)在 20 秒 TTL 内去重。
此外,Agent 还能访问 Jianmu 的 Skill 系统(通过 SwarmNode 内嵌的技能配置),工具和技能共同构成 Agent 在对话中的完整行动空间。
Provider 管理:并发控制与速率限制¶
ProviderRuntimeSupport 提供了一套完整的 LLM Provider 管理层,解决多 Agent 并发调用时的资源竞争问题:
graph LR
subgraph ProviderRuntimeSupport
SEM[Semaphore<br/>并发控制]
BUDGET[TPM Budget Bucket<br/>Token 速率限制]
COOLDOWN[Cooldown<br/>全局退避]
WRAP[RecordingChatProvider<br/>上下文注入 + Turn Contract 提取]
end
MC[ModelClient] --> WRAP
WRAP --> SEM
SEM --> BUDGET
BUDGET --> PROVIDER[LLM Provider API]
PROVIDER --> |429/rate_limit| COOLDOWN
COOLDOWN --> SEM
- 并发控制:通过
asyncio.Semaphore限制同时进行的 Provider 调用数(默认provider_max_concurrency: 1,避免多个 Agent 同时调用耗尽 API 配额) - TPM 预算桶:基于 Token Bucket 算法的速率限制器,每分钟按配置的
provider_tpm_limit(默认 30000)和provider_tpm_refill_seconds速率补充 Token。每个请求根据消息数量估算 Token 消耗,超限时排队等待 - 错误分类与退避:Provider 返回的错误被解析为
rate_limited/retryable/fatal三类。rate_limited 错误触发全局 Cooldown(默认 20s)+ Agent 级别退避重试;retryable 错误使用指数退避重试(最多 3 次)。一次 retryable 尝试耗尽后,Agent 会带着下一次重试延迟显示为waiting_retry,而不会被当作终态失败。 - RecordingChatProvider:在每次 LLM 调用前后注入动态上下文(记忆摘要、未读批处理提示)并提取 Turn Contract(Agent 的结构化输出决策)
前端架构:React SPA + SSE 实时流¶
前端是一个基于 React + TypeScript + Vite 构建的单页应用,采用四 Tab 布局:
| Tab | 页面组件 | 核心功能 |
|---|---|---|
| 💬 消息 | ConversationPage |
对话消息流、Composer 发送、运行时控制(中断/恢复)、Markdown 渲染 |
| 👥 联系人 | AgentsPage |
Agent Profile 的 CRUD、双栏编辑布局 |
| 🕘 历史 | HistoryPage |
对话历史快照的浏览与管理 |
| ⚙️ 设置 | 内联于 App | 技能设置、模型配置、后端 URL |
实时事件驱动¶
前端通过两个 SSE 端点接收实时更新:
- /api/chat/conversations/{id}/stream:针对单个对话的事件流(新消息、Agent 状态变更、工具调用开始/完成)
- /api/chat/stream:工作空间级别的事件流(对话创建、全局状态变更)
事件流使用 since_id 分页机制(基于 SQLite AUTOINCREMENT 的 chat_events.id),配合 15 秒心跳保活和自动重连。前端将 SSE 事件分派到 React State 中,驱动 UI 的增量更新而非全量刷新。
运行时状态可视化¶
前端聚合了多维度的 Agent 状态展示:
- Agent 状态派生:底层
host_status(来自 Runtime)+metadata中的 Reply Turn 和 Provider 节流状态 → 派生出面向用户的display_status(idle / running / waking / blocked / failed / stopped) - Conversation 聚合状态:汇总所有成员 Agent 的状态,生成对话级别的运行状态(running / partially_stopped / stopped 等),并在 UI 中呈现为可交互的中断/恢复按钮
- BT Skill Trace 可视化:
BTSkillTraceView组件将 Agent 执行的行为树技能渲染为树形节点图,实时展示每个节点的状态(RUNNING / SUCCESS / FAILURE),支持节点 I/O 的展开查看
API 路由全景¶
Swarm Studio 的 API 主要分成两组:传统的 /api/*,以及面向运行时对话管理的 /api/chat/*。
| 方法 | 路径 | 功能 |
|---|---|---|
| GET | /api/health |
健康检查(返回 Agent 数、对话数、默认工作空间 ID) |
| GET | /api/defaults |
获取全局默认配置 |
| PATCH | /api/defaults |
更新全局默认配置 |
| POST | /api/bootstrap |
初始化默认工作空间 |
| GET/POST/PATCH/DELETE | /api/agents |
Agent Profile CRUD |
| GET/POST/PATCH/DELETE | /api/conversations |
传统会话模板 CRUD |
| GET/POST/PATCH/DELETE | /api/chat/conversations |
Chat 对话 CRUD(核心路径) |
| POST | /api/chat/agents/{id}/direct |
创建或获取与某 Agent 的 Direct Chat |
| POST | /api/chat/conversations/{id}/messages |
发送消息(触发 Agent 唤醒) |
| GET | /api/chat/conversations/{id}/stream |
SSE 事件流(对话级别) |
| GET | /api/chat/stream |
SSE 事件流(工作空间级别) |
| POST | /api/chat/conversations/{id}/interrupt |
中断对话中所有 Agent |
| POST | /api/chat/conversations/{id}/resume |
恢复对话中所有 Agent |
| POST | /api/chat/interrupt / /api/chat/resume |
中断/恢复整个工作空间 |
| GET | /api/chat/runtime-status |
获取工作空间运行时状态摘要 |
| GET | /api/chat/conversations/{id}/diagnostics |
对话诊断(Side Thread、Agent 状态、失败事件) |
| POST | /api/chat/conversations/{id}/reset-memory |
重置对话记忆 |
| POST | /api/chat/conversations/{id}/clear-messages |
清空对话消息 |
| GET | /api/chat/history-snapshots |
历史快照列表 |
| GET | /api/chat/agents/{id} |
Agent 详情(含状态、对话列表、记忆) |
| POST | /api/chat/agents/{id}/retry |
重试失败的 Agent |
| POST | /api/chat/agents/{id}/reset-history |
重置 Agent 历史 |
| GET | /api/chat/search |
全局搜索(Agent、对话) |
配置体系¶
Swarm Studio 使用 config.yaml 进行配置,分为三大块:
Storage:SQLite 数据库路径(db_path),支持相对路径和绝对路径。默认存储在 .outputs/swarm_studio/swarm_studio.db,使本地对话与运行时数据留在源码树之外。
Defaults:新建对话时的默认值,包括 Provider 名称(默认 openai)、模型、最大运行时秒数(600s)、默认启用的技能列表(tavily_search、web_research)、技能目录、技能文件、技能 Prompt 模式(summary)。
模型默认值与会话覆盖¶
MODEL 与 BASE_URL(兼容 OPENAI_BASE_URL)是 Swarm Studio 的全局模型默认值,并优先于 config.yaml 中的同名 Defaults。它们不会被自动写入每个 Conversation;因此未设置会话级覆盖的对话会随 .env 更新使用新的默认值。
| 配置来源 | 优先级 | .env 修改后的行为 |
|---|---|---|
| Conversation 显式的 provider / model / Base URL 覆盖 | 高 | 保持不变,直到用户明确修改或清除 |
.env 的 MODEL、BASE_URL / OPENAI_BASE_URL |
中 | 对未覆盖的会话立即作为新默认值生效 |
config.yaml 的 Defaults |
低 | 仅在对应环境变量未设置时作为回退 |
更新对话名称、话题、技能等无关字段不会删除已有的会话级模型覆盖。
Runtime:Provider 运行时参数,包括最大并发数(1)、重试次数(3)、退避秒数(2.0)、429 退避秒数(15.0)、速率限制冷却秒数(20.0)、TPM 限制(30000)、TPM 补充秒数(60.0)、TPM 最小请求 Token(768)、上下文最大 Token(6000)。
这些默认值可在 Workspace 级别被覆盖(通过 metadata 字段),也可在每个 Conversation 创建时独立指定。
项目结构¶
下面这份目录更适合拿来定位代码入口,而不是逐项记忆。阅读时可以先关注三处:
backend/app.py:HTTP 路由与 SSE 入口backend/chat_controller.py/backend/chat_runtime.py:后端编排与运行时适配frontend/src/App.tsx:前端状态与事件流主入口
apps/swarm_studio/
├── README.md # 项目说明与对齐笔记
├── __init__.py
├── config.yaml # 应用配置(Storage / Defaults / Runtime)
├── backend/
│ ├── __main__.py # python -m 入口
│ ├── main.py # uvicorn 启动(端口 8111)
│ ├── app.py # FastAPI 应用 + 全部 API 路由 + SSE 流
│ ├── chat_controller.py # 编排层:对话 CRUD、消息发布、事件查询
│ ├── chat_runtime.py # 核心:5000+ 行的运行时适配器
│ ├── runtime_common.py # Provider 管理、StudioAgentRole、Skill 解析
│ ├── config.py # Pydantic 配置模型 + YAML 加载
│ ├── models.py # 数据模型:Pydantic Request + Dataclass Record
│ ├── storage.py # SQLite 持久化层(~2000 行,含自动 Schema 迁移)
│ ├── schema.sql # 数据库 Schema 定义
│ ├── agents.py # Agent Profile CRUD Service
│ ├── conversations.py # 传统 Conversation CRUD Service
│ ├── status_utils.py # Agent 状态派生逻辑
│ ├── env.py # .env 加载
│ ├── seeds.py # 默认工作空间初始化
│ └── data/ # SQLite 数据库文件目录
├── frontend/
│ ├── src/
│ │ ├── App.tsx # 主应用:Tab 路由、SSE 连接、全部状态管理
│ │ ├── main.tsx # React 入口
│ │ ├── styles.css # 全局样式
│ │ ├── pages/
│ │ │ ├── ConversationPage.tsx # 消息流 + Composer
│ │ │ ├── AgentsPage.tsx # 联系人列表 + 编辑
│ │ │ ├── ConversationsPage.tsx # 群聊列表 + 编辑
│ │ │ └── HistoryPage.tsx # 历史快照浏览
│ │ ├── components/
│ │ │ ├── AgentEditor.tsx # Agent Profile 编辑表单
│ │ │ ├── ConversationEditor.tsx # 对话编辑表单(成员选择 + 技能设置)
│ │ │ ├── Composer.tsx # 消息输入框
│ │ │ ├── Avatar.tsx # 头像组件
│ │ │ ├── BTSkillTraceView.tsx # 行为树技能追踪可视化
│ │ │ └── SkillSettingsFields.tsx # 技能设置表单控件
│ │ └── lib/
│ │ ├── types.ts # TypeScript 类型定义
│ │ └── skillSettings.ts # 技能设置序列化/反序列化
│ ├── dist/ # Vite 构建产物
│ └── vite.config.ts
└── scripts/
└── inspect_chat_runtime_health.py # 数据库健康检查脚本
信息来源:项目目录结构扫描
运行与调试¶
启动后端(默认 http://localhost:8111):
启动前端开发服务器(默认 http://localhost:5180):
数据库健康检查:
检查报告包括:脏 Direct Thread(成员不规范)、未完成的 Side Thread sync-back 义务、长时间处于running 状态的 Agent。
与核心模块的关系¶
Swarm Studio 位于 Jianmu 的上层应用层,和以下核心模块关系最紧密:
- Swarm 运行时:
AgentRuntime提供 Agent 生命周期管理、邮箱路由、事件总线。Swarm Studio 的ChatRuntimeAdapter是对 AgentRuntime 的高层包装 - 角色与团队:
AgentTeam和StudioAgentRole提供多 Agent 编组能力 - 模型接入:
ModelClient是 LLM 调用的统一入口,被ProviderRuntimeSupport包装 - 技能系统:
SkillsCatalog提供技能发现,SwarmNode将技能嵌入到 Agent 的行为树中 - 上下文构建器:
ContextBuilder和TokenBudgetFilter用于动态组装 LLM 上下文
阅读建议¶
完成本文后,建议按以下路径深入:
- Swarm 运行时:理解 Swarm Studio 底层的 Agent 生命周期和事件机制
- 角色与团队:理解 AgentTeam 和 StudioAgentRole 的协作编排
- Tree Studio:了解另一个可视化应用——工作流创建与技能导出
- 上下文构建器:理解 Swarm Studio 中动态上下文组装的具体实现