14.6 标签化流式输出
标签化流式输出(Tagged Streaming)是 RAF 宿主服务的核心功能之一——每个 `session/update` 事件携带 `_meta.raf.agent_id` 标签,使前端能够区分多 Agent 对话中不同 Agent 的输出,并实时追踪各子 Agent 的执行状态。
核心概念
sequenceDiagram
participant W as WorkflowAgent
participant A1 as CodingAgent
participant A2 as ReviewerAgent
participant T as SubAgentStatusTracker
participant C as ACP 客户端
W->>A1: 开始执行
T->>T: register("coding", "CodingAgent")
T->>T: ensure_active("coding")
A1-->>C: session/update {_meta.raf.agent_id: "coding"}
A1-->>W: 完成
W->>A2: 开始执行
T->>T: register("reviewer", "ReviewerAgent")
T->>T: ensure_active("reviewer")
A2-->>C: session/update {_meta.raf.agent_id: "reviewer"}
A2-->>W: 完成
T->>T: mark_all_completed()
T->>C: session/update {_meta.raf.sub_agents: [{status: "completed"}]}
SubAgentStatusTracker
`SubAgentStatusTracker` 追踪多 Agent 编排中每个子 Agent 的执行状态:
pub struct SubAgentStatusTracker {
agents: Mutex<HashMap<String, SubAgentState>>,
}
pub struct SubAgentState {
pub agent_type: String,
pub status: SubAgentStatus, // Pending | Executing | Completed | Error
pub started_at: Option<Instant>,
pub completed_at: Option<Instant>,
}
pub enum SubAgentStatus {
Pending,
Executing,
Completed,
Error,
}
核心 API
| 方法 | 说明 | |------|------| | `register(id, type)` | 注册子 Agent 进行追踪 | | `register_all(ids)` | 批量注册 | | `ensure_active(id)` | 标记为执行中(首次从 Pending 切换时返回 true) | | `mark_completed(id)` | 标记为已完成 | | `mark_error(id)` | 标记为错误 | | `mark_all_completed()` | 将所有活跃 Agent 标记为完成 | | `build_status_meta()` | 构建 `_meta` 中的状态信息 | | `all_terminated()` | 检查是否全部进入终止状态 |
状态元数据
pub fn build_status_meta(&self) -> serde_json::Value {
let statuses: Vec<serde_json::Value> = agents.iter().map(|(id, state)| {
serde_json::json!({
"id": id,
"type": state.agent_type,
"status": state.status, // "pending" | "executing" | "completed" | "error"
"elapsed_ms": state.started_at.map(|t| t.elapsed().as_millis() as u64),
})
}).collect();
serde_json::json!({
"sub_agents": statuses
})
}
流式输出格式
带标签的流式更新
每个 `session/update` 事件携带 Agent 标识:
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"session_id": "abc-123",
"content": [
{
"type": "text",
"text": "这段代码有一个潜在的性能问题..."
}
],
"_meta": {
"raf": {
"agent_id": "coding",
"agent_type": "CodingAgent",
"parent_agent_id": "workflow_1",
"sub_agents": [
{
"id": "coding",
"type": "CodingAgent",
"status": "executing",
"elapsed_ms": 1234
},
{
"id": "reviewer",
"type": "ReviewerAgent",
"status": "pending"
}
]
}
}
}
}
字段说明
| 字段 | 说明 | |------|------| | `_meta.raf.agent_id` | 当前发言 Agent 的 ID | | `_meta.raf.agent_type` | Agent 类型标签 | | `_meta.raf.parent_agent_id` | 父编排 Agent ID(如果是子 Agent) | | `_meta.raf.sub_agents` | 所有追踪中子 Agent 的状态数组 | | `_meta.raf.sub_agents[].id` | 子 Agent ID | | `_meta.raf.sub_agents[].type` | 子 Agent 类型 | | `_meta.raf.sub_agents[].status` | 状态:pending / executing / completed / error | | `_meta.raf.sub_agents[].elapsed_ms` | 已执行时长(毫秒) |
前端渲染示例
多 Agent 对话视图
前端可以利用 `agent_id` 标签将不同 Agent 的输出渲染到独立的对话区域:
// 前端流式事件处理
function handleSessionUpdate(update) {
const agentId = update._meta?.raf?.agent_id || "unknown";
const content = update.content;
for (const part of content) {
if (part.type === "text") {
// 追加到对应 Agent 的对话面板
appendToAgentPanel(agentId, part.text);
} else if (part.type === "reasoning") {
appendToAgentReasoning(agentId, part.text);
}
}
// 更新子 Agent 状态 UI
const subAgents = update._meta?.raf?.sub_agents || [];
updateSubAgentStatusPanel(subAgents);
}
子 Agent 状态面板
function updateSubAgentStatusPanel(subAgents) {
for (const agent of subAgents) {
const statusIcon = {
pending: "⏳",
executing: "🔄",
completed: "✅",
error: "❌",
}[agent.status];
renderStatusBadge(agent.id, statusIcon, agent.elapsed_ms);
}
}
在 ACP Handler 中的集成
async fn handle_prompt_with_tracking(
req: PromptRequest,
registry: &AgentRegistry,
bridge: &SessionBridge,
conn: &ConnectionTo<Client>,
) {
let tracker = SubAgentStatusTracker::new();
// 如果 Agent 是工作流,注册所有子 Agent
if is_workflow_agent(&agent) {
let sub_agents = discover_sub_agents(&agent, registry);
tracker.register_all(&sub_agents);
}
let stream = agent.run(messages, session, options).await?;
tokio::pin!(stream);
while let Some(chunk) = stream.next().await {
let mut meta = serde_json::Map::new();
// 注入 Agent 标识
meta.insert("agent_id".into(), json!(agent.id().to_string()));
meta.insert("agent_type".into(), json!(agent.metadata().agent_type));
// 追踪状态(如果状态发生变化)
if tracker.ensure_active(agent.id().to_string()) {
// 状态从 Pending → Executing,附加状态信息
}
// 注入子 Agent 状态
let status_meta = tracker.build_status_meta();
for (key, value) in status_meta.as_object().unwrap() {
meta.insert(key.clone(), value.clone());
}
let update = build_update(session_id, chunk, meta);
conn.send_notification(update).await?;
}
// 标记全部完成
tracker.mark_all_completed();
let final_meta = tracker.build_status_meta();
// 发送最终状态更新
}
注意事项
1. **状态变化频率**:`ensure_active` 仅在首次从 Pending 切换时返回 true,避免不必要的状态更新 2. **元数据开销**:每个 `session/update` 都携带 `_meta.raf`,对于高频流式输出需要考虑带宽 3. **线程安全**:`SubAgentStatusTracker` 使用 `Mutex` 保护内部状态,适用于多线程环境 4. **超时处理**:`elapsed_ms` 持续增长,客户端可用于检测长时间无响应的 Agent 5. **前端解耦**:`agent_id` 标签使前端可以灵活选择渲染策略——全合并视图或多面板视图