运行时行为验证 #4: 流式 vs 非流式响应
🆕 本章通过实际 daemon 运行时行为验证补全
日期: 2026-06-25 | Daemon: v4.25.5
1. 背景
此前代理实现中假设 daemon 支持 stream: true/false 参数来控制返回格式(SSE 流 vs 完整 JSON)。运行时验证推翻了这个假设。
2. 关键发现: Daemon 始终返回 SSE
2.1 测试条件
请求(带 stream: false 参数):
json
POST /chat
{
"message": "回复一句话:hi",
"stream": false
}实际响应: daemon 忽略 stream 参数,始终返回 SSE 流格式:
data: {"type":"reasoning","content":"The user just said..."}
data: {"type":"text","content":"..."}
data: {"type":"tokens","prompt":6193,"completion":88,"total":6281}
data: {"type":"done","tokens":6281,"tool_calls":0,"session_id":"3212c498-..."}结论: Daemon 的 /chat 端点是纯 SSE 协议,不支持非流式。 ChatRequest 结构体中没有 stream 字段。
2.2 对比验证
| 请求参数 | 响应格式 |
|---|---|
{"message":"hi"} | SSE 流 |
{"message":"hi","stream":false} | SSE 流(同样) |
{"message":"hi","stream":true} | SSE 流(同样) |
Daemon 不接受任何控制响应格式的参数。
3. 完整的 SSE 格式规范
3.1 事件分隔
data: {JSON}\n\n每个事件以 data: 前缀开头,以两个换行符 \n\n 分隔。
3.2 事件类型一览
| 类型 | 方向 | 出现次数 | 说明 |
|---|---|---|---|
reasoning | daemon → client | 1+ 次 | 模型推理过程文本,以碎片方式发送 |
tokens | daemon → client | 1+ 次 | Token 统计(每次推理后发送一轮) |
tool_start | daemon → client | 0-N 次 | 工具调用开始 |
tool_result | daemon → client | 0-N 次 | 工具执行结果 |
text | daemon → client | 0-N 次 | 最终文本回复 |
error | daemon → client | 0-1 次 | 错误事件 |
done | daemon → client | 1 次 | 会话结束(最后一个事件) |
3.3 完整事件流的几种模式
模式 1: 纯文本回复(无工具调用)
reasoning → reasoning → ... → reasoning
tokens: {prompt, completion, total}
text → text → ... → text
tokens: {prompt, completion, total}
done: {tokens, tool_calls: 0, session_id}模式 2: 单工具调用
reasoning → ...
tokens: {...}
tool_start: {id, name, arguments}
tool_result: {id, name, output, success, duration_ms}
reasoning → ...
text → ...
tokens: {...}
done: {tokens, tool_calls: 1, session_id}模式 3: 多工具调用
reasoning → ...
tokens: {...}
tool_start #1 → tool_result #1
tool_start #2 → tool_result #2
reasoning → ...
text → ...
tokens: {...}
done: {tokens, tool_calls: 2, session_id}4. 对代理实现的影响
| 之前假设 | 运行时验证结果 | 影响 |
|---|---|---|
支持 stream: false 返回 JSON | ❌ 不支持 | Proxy 必须始终以 SSE 模式调用 daemon |
| 非流式请求直接返回完整消息 | ❌ 不存在 | Proxy 需要自己聚合 SSE 事件为完整响应 |
| 代理需要分别处理流式和非流式 | ✅ 统一为 SSE | Proxy 逻辑可简化——始终处理 SSE 流 |
4.1 聚合逻辑(Proxy 中实现)
python
async def chat_completion_non_stream(messages, session_id=None):
"""将 SSE 流聚合并非流式 OpenAI 响应"""
text_parts = []
reasoning_parts = []
tool_calls = []
final_tokens = None
session_id = None
async for event in daemon_chat_stream(messages, session_id):
if event.type == "text":
text_parts.append(event.content)
elif event.type == "reasoning":
reasoning_parts.append(event.content)
elif event.type == "tool_start":
tool_calls.append({
"id": event.id,
"type": "function",
"function": {
"name": event.name,
"arguments": event.arguments # 已是 JSON 字符串
}
})
elif event.type == "tokens":
final_tokens = event
elif event.type == "done":
session_id = event.session_id
# 构建 OpenAI 格式完整响应
return {
"id": "chatcmpl-atomcode",
"object": "chat.completion",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "".join(text_parts),
"reasoning_content": "".join(reasoning_parts),
"tool_calls": tool_calls if tool_calls else None
},
"finish_reason": "tool_calls" if tool_calls else "stop"
}],
"usage": {
"prompt_tokens": final_tokens.prompt if final_tokens else 0,
"completion_tokens": final_tokens.completion if final_tokens else 0,
"total_tokens": final_tokens.total if final_tokens else 0
}
}5. 流式 SSE → OpenAI Chunk 转换
5.1 转换规则
| Daemon SSE 事件 | OpenAI SSE Chunk |
|---|---|
{"type":"text","content":"..."} | {"choices":[{"delta":{"content":"..."}}]} |
{"type":"reasoning","content":"..."} | {"choices":[{"delta":{"reasoning_content":"..."}}]} |
{"type":"tool_start","id":"...","name":"...","arguments":"..."} | {"choices":[{"delta":{"tool_calls":[{"index":N,"id":"...","type":"function","function":{"name":"...","arguments":"..."}}]}}]} |
{"type":"tokens","prompt":N,"completion":N,"total":N} | {"usage":{...}} (最后发送) |
{"type":"done","session_id":"..."} | data: [DONE] |
5.2 关键差异
| 维度 | Daemon SSE | OpenAI SSE |
|---|---|---|
| 前缀 | data: | data: |
| 分隔 | \n\n | \n\n |
| 事件格式 | 纯 JSON 字段 | OpenAI Chat Completion Chunk |
| 流结束信号 | {"type":"done"} | data: [DONE] |
| Token 统计 | tokens 事件中 | usage 字段(最后一个 chunk) |
| 工具参数 | JSON 字符串 | 流式累加字符串 |
📅 最后更新: 2026-06-25🔍 验证方法: 实际 daemon 运行时 curl 对比测试
逆向命令索引
bash
# 验证 daemon 始终返回 SSE
curl -s -X POST http://localhost:13456/chat \
-H "Authorization: Bearer $(cat ~/.atomcode/auth.toml | grep access_token | cut -d'"' -f2)" \
-H "Content-Type: application/json" \
-d '{"message":"hi","stream":false}' 2>&1 | head -5
# 确认输出始终是 data: {...} SSE 格式