引言
Agent 的推理过程往往是漫长的等待——用户盯着加载动画,不知道 Agent 在做什么。流式响应把"等待结果"变成"实时观察思考",是 Agent 用户体验的关键升级。2026年,三种流式协议各有优劣,选型不当会导致体验降级或工程复杂度爆炸。
一、三种协议对比
核心特性矩阵
| 特性 | SSE | WebSocket | gRPC Stream |
|---|---|---|---|
| 通信方向 | 服务器→客户端(单向) | 双向 | 双向 |
| 底层协议 | HTTP/1.1 或 HTTP/2 | HTTP 升级 | HTTP/2 |
| 数据格式 | 文本(text/event-stream) | 文本/二进制 | Protobuf(二进制) |
| 自动重连 | 内置 | 需手动实现 | 需手动实现 |
| 浏览器支持 | 原生 EventSource | 原生 WebSocket | 需 gRPC-Web |
| 代理/CDN兼容 | 优秀 | 良好 | 较差 |
| 连接数限制 | 浏览器6个/域名 | 无限制 | 无限制 |
| 序列化效率 | 低(文本) | 中 | 高(Protobuf) |
| 移动端友好 | 高 | 中 | 低 |
Agent 场景适配分析
Agent 流式需求频谱:
单向输出流 双向交互流
(LLM→用户) (用户↔Agent)
│ │
│ ┌───────────┐ │
│ │ SSE 最佳 │ │
│ └───────────┘ │
│ │
│ ┌───────────┐ │
│ │ WebSocket │ │
│ │ 最佳 │ │
│ └───────────┘ │
│ │
│ ┌───────────┐ │
│ │ gRPC 最佳 │ │
│ └───────────┘ │
简单聊天 ←─────────────→ 复杂多Agent
低延迟 ←─────────────→ 高吞吐
Web前端 ←─────────────→ 微服务后端
二、SSE 实现方案
2.1 服务端
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
import json
app = FastAPI()
class AgentStreamEvent:
"""Agent 流式事件类型"""
THINKING = "thinking" # Agent 思考中
TOOL_CALL = "tool_call" # 工具调用
TOOL_RESULT = "tool_result" # 工具结果
CONTENT = "content" # 内容输出
ERROR = "error" # 错误
DONE = "done" # 完成
async def agent_stream_generator(
query: str,
session_id: str
):
"""Agent SSE 流式生成器"""
try:
# 1. 发送思考状态
yield _format_sse(AgentStreamEvent.THINKING, {
"message": "正在分析您的请求...",
"session_id": session_id
})
# 2. Agent 推理(流式 LLM 输出)
async for chunk in agent.think_stream(query):
if chunk.type == "tool_call":
yield _format_sse(AgentStreamEvent.TOOL_CALL, {
"tool": chunk.tool_name,
"args": chunk.tool_args,
"thinking": chunk.reasoning
})
# 3. 工具执行
result = await agent.execute_tool(chunk.tool_call)
yield _format_sse(AgentStreamEvent.TOOL_RESULT, {
"tool": chunk.tool_name,
"result": result.summary,
"duration_ms": result.duration_ms
})
elif chunk.type == "content":
yield _format_sse(AgentStreamEvent.CONTENT, {
"text": chunk.text,
"tokens_so_far": chunk.token_count
})
# 4. 完成
yield _format_sse(AgentStreamEvent.DONE, {
"session_id": session_id,
"total_tokens": agent.total_tokens,
"duration_ms": agent.total_duration_ms
})
except Exception as e:
yield _format_sse(AgentStreamEvent.ERROR, {
"message": str(e),
"session_id": session_id
})
def _format_sse(event_type: str, data: dict) -> str:
return f"event: {event_type}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
@app.post("/api/agent/chat")
async def chat(request: ChatRequest):
return StreamingResponse(
agent_stream_generator(request.query, request.session_id),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no", # Nginx 禁用缓冲
}
)
2.2 客户端
class AgentSSEClient {
private eventSource: EventSource | null = null;
private reconnectAttempts = 0;
private maxReconnects = 3;
connect(query: string, sessionId: string) {
// 使用 fetch POST + ReadableStream(EventSource 仅支持 GET)
fetch('/api/agent/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, sessionId }),
}).then(response => {
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
const read = () => {
reader.read().then(({ done, value }) => {
if (done) return;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split('\n\n');
buffer = events.pop() || '';
events.forEach(raw => this.handleEvent(raw));
read();
});
};
read();
});
}
private handleEvent(raw: string) {
const lines = raw.split('\n');
let event = 'message';
let data = '';
lines.forEach(line => {
if (line.startsWith('event: ')) event = line.slice(7);
if (line.startsWith('data: ')) data = line.slice(6);
});
const parsed = JSON.parse(data);
switch (event) {
case 'thinking':
this.onThinking?.(parsed);
break;
case 'tool_call':
this.onToolCall?.(parsed);
break;
case 'tool_result':
this.onToolResult?.(parsed);
break;
case 'content':
this.onContent?.(parsed.text);
break;
case 'error':
this.onError?.(parsed);
break;
case 'done':
this.onDone?.(parsed);
break;
}
}
}
三、WebSocket 实现方案
3.1 服务端
from fastapi import WebSocket, WebSocketDisconnect
class AgentConnectionManager:
"""Agent WebSocket 连接管理"""
def __init__(self):
self.active: dict[str, WebSocket] = {} # session_id → WebSocket
self.agent_tasks: dict[str, asyncio.Task] = {}
async def connect(self, ws: WebSocket, session_id: str):
await ws.accept()
self.active[session_id] = ws
logger.info(f"WebSocket connected: {session_id}")
async def disconnect(self, session_id: str):
if session_id in self.active:
del self.active[session_id]
if session_id in self.agent_tasks:
self.agent_tasks[session_id].cancel()
del self.agent_tasks[session_id]
async def handle_session(self, ws: WebSocket, session_id: str):
"""处理 WebSocket 会话"""
await self.connect(ws, session_id)
try:
while True:
# 接收客户端消息
message = await ws.receive_json()
if message["type"] == "chat":
# 启动 Agent 任务
task = asyncio.create_task(
self._run_agent(ws, session_id, message["content"])
)
self.agent_tasks[session_id] = task
elif message["type"] == "interrupt":
# 用户中断当前 Agent 执行
if session_id in self.agent_tasks:
self.agent_tasks[session_id].cancel()
await ws.send_json({
"type": "interrupted",
"session_id": session_id
})
elif message["type"] == "feedback":
# 用户实时反馈(Human-in-the-loop)
await self._handle_feedback(session_id, message)
except WebSocketDisconnect:
await self.disconnect(session_id)
async def _run_agent(self, ws: WebSocket, session_id: str, query: str):
"""运行 Agent 并通过 WebSocket 推送更新"""
try:
async for event in agent.run_stream(query):
await ws.send_json({
"type": event.type,
"data": event.data,
"timestamp": time.time()
})
except asyncio.CancelledError:
logger.info(f"Agent task cancelled: {session_id}")
except Exception as e:
await ws.send_json({
"type": "error",
"data": {"message": str(e)}
})
manager = AgentConnectionManager()
@app.websocket("/ws/agent/{session_id}")
async def websocket_endpoint(ws: WebSocket, session_id: str):
await manager.handle_session(ws, session_id)
3.2 客户端
class AgentWSClient {
private ws: WebSocket | null = null;
private messageQueue: string[] = [];
connect(sessionId: string) {
this.ws = new WebSocket(`wss://api.example.com/ws/agent/${sessionId}`);
this.ws.onopen = () => {
// 发送排队消息
this.messageQueue.forEach(msg => this.ws!.send(msg));
this.messageQueue = [];
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
this.ws.onclose = () => {
// 自动重连
setTimeout(() => this.connect(sessionId), 3000);
};
}
sendChat(content: string) {
const msg = JSON.stringify({ type: 'chat', content });
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(msg);
} else {
this.messageQueue.push(msg);
}
}
interrupt() {
this.ws?.send(JSON.stringify({ type: 'interrupt' }));
}
}
四、gRPC 流式方案
4.1 Proto 定义
// agent.proto
syntax = "proto3";
service AgentService {
// 服务端流式:Agent → 客户端
rpc ChatStream(ChatRequest) returns (stream ChatResponse);
// 双向流式:支持实时交互
rpc ChatBidirectional(stream ChatMessage) returns (stream ChatResponse);
}
message ChatRequest {
string session_id = 1;
string query = 2;
map<string, string> metadata = 3;
}
message ChatMessage {
string session_id = 1;
MessageType type = 2; // CHAT, INTERRUPT, FEEDBACK
string content = 3;
}
message ChatResponse {
ResponseType type = 1; // THINKING, TOOL_CALL, CONTENT, DONE, ERROR
string session_id = 2;
bytes data = 3; // JSON 编码的事件数据
int64 timestamp = 4;
}
enum MessageType {
CHAT = 0;
INTERRUPT = 1;
FEEDBACK = 2;
}
enum ResponseType {
THINKING = 0;
TOOL_CALL = 1;
TOOL_RESULT = 2;
CONTENT = 3;
DONE = 4;
ERROR = 5;
}
4.2 服务端实现
import grpc
from concurrent import futures
class AgentServicer(agent_pb2_grpc.AgentServiceServicer):
def ChatStream(self, request, context):
"""服务端流式:逐条推送 Agent 事件"""
try:
for event in agent.run(request.query):
response = agent_pb2.ChatResponse(
type=self._map_event_type(event.type),
session_id=request.session_id,
data=json.dumps(event.data).encode(),
timestamp=int(time.time())
)
yield response
except Exception as e:
yield agent_pb2.ChatResponse(
type=agent_pb2.ERROR,
data=json.dumps({"error": str(e)}).encode()
)
def ChatBidirectional(self, request_iterator, context):
"""双向流式:支持中断和实时反馈"""
session = None
for message in request_iterator:
if message.type == agent_pb2.CHAT:
# 启动 Agent 执行
for event in agent.run(message.content):
if not context.is_active():
break
yield agent_pb2.ChatResponse(
type=self._map_event_type(event.type),
data=json.dumps(event.data).encode()
)
elif message.type == agent_pb2.INTERRUPT:
agent.interrupt()
yield agent_pb2.ChatResponse(
type=agent_pb2.DONE,
data=json.dumps({"reason": "interrupted"}).encode()
)
五、性能基准测试
测试环境
- 服务端:4 vCPU / 16GB RAM / Python 3.12 / FastAPI
- 客户端:1000 并发连接
- 消息大小:平均 200 bytes / 消息
- 持续时间:5 分钟
结果对比
| 指标 | SSE | WebSocket | gRPC Stream |
|---|---|---|---|
| 最大并发连接 | 10,000 | 50,000 | 30,000 |
| 消息延迟 P50 | 12ms | 8ms | 5ms |
| 消息延迟 P95 | 45ms | 25ms | 12ms |
| 消息延迟 P99 | 120ms | 60ms | 30ms |
| 吞吐量 (msg/s) | 50,000 | 200,000 | 150,000 |
| 内存/连接 | 32KB | 48KB | 24KB |
| CPU 利用率 (1k连接) | 35% | 28% | 22% |
| 带宽效率 | 基准 | +15% | +40% |
六、选型决策树
┌─────────────────┐
│ 是否需要双向通信?│
└────────┬────────┘
│
┌──────────────┴──────────────┐
│ 否 │ 是
▼ ▼
┌────────────────┐ ┌──────────────────┐
│ 是否是微服务 │ │ 是否是浏览器前端?│
│ 内部通信? │ └────────┬─────────┘
└───────┬────────┘ │
│ ┌───────┴───────┐
┌───────┴───────┐ │ 否 │ 是
│ 是 │ 否 ▼ ▼
▼ ▼ ┌──────────┐ ┌────────────┐
┌──────┐ ┌────────┐ │ gRPC │ │ WebSocket │
│gRPC │ │ SSE │ │ Stream │ │ │
└──────┘ └────────┘ └──────────┘ └────────────┘
场景推荐
| 场景 | 推荐协议 | 原因 |
|---|---|---|
| Web 聊天界面 | SSE | 原生支持、简单、自动重连 |
| 实时协作编辑 | WebSocket | 双向低延迟 |
| 移动 App | SSE | 移动网络友好、自动重连 |
| 微服务间 Agent 通信 | gRPC | 高效序列化、强类型 |
| 多 Agent 系统 | gRPC | 流式 RPC 适配 Agent 通信 |
| 简单 LLM 问答 | SSE | 单向流足够、实现简单 |
| Human-in-the-loop | WebSocket | 需要双向交互 |
| 大规模推送 | SSE | CDN 兼容、连接效率高 |
七、生产环境关键配置
Nginx SSE 配置
location /api/agent/chat {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off; # 关键:禁用缓冲
proxy_cache off;
proxy_read_timeout 300s; # 长连接超时
chunked_transfer_encoding on;
}
WebSocket 配置
location /ws/agent/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400; # 24小时
proxy_send_timeout 86400;
}
八、流式架构 Checklist
□ 协议选型基于通信方向和客户端类型
□ SSE 禁用代理缓冲(proxy_buffering off)
□ WebSocket 实现心跳和重连机制
□ gRPC 配置 keepalive 和流控
□ 消息序列化使用高效格式(JSON/Protobuf)
□ 背压机制防止慢客户端拖垮服务端
□ 连接超时和最大连接数限制
□ 流式错误不中断连接,通过事件传递
□ 客户端实现优雅降级(流式不可用时回退轮询)
□ 监控流式连接的延迟和消息丢失率
结语
流式响应是 Agent 从"工具"到"伙伴"的关键体验升级。协议选型没有银弹:SSE 简单可靠适合 Web 场景,WebSocket 灵活双向适合交互场景,gRPC 高效强类型适合微服务场景。理解你的通信模式——是单向输出还是双向交互——然后选择最适合的工具。在 Agent 时代,好的流式架构让用户感觉 Agent 在"思考",而不是在"卡住"。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。
