Skip to content

运行时行为验证 #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 事件类型一览

类型方向出现次数说明
reasoningdaemon → client1+ 次模型推理过程文本,以碎片方式发送
tokensdaemon → client1+ 次Token 统计(每次推理后发送一轮)
tool_startdaemon → client0-N 次工具调用开始
tool_resultdaemon → client0-N 次工具执行结果
textdaemon → client0-N 次最终文本回复
errordaemon → client0-1 次错误事件
donedaemon → client1 次会话结束(最后一个事件)

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 事件为完整响应
代理需要分别处理流式和非流式✅ 统一为 SSEProxy 逻辑可简化——始终处理 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 SSEOpenAI 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 格式

基于 VitePress 构建 · AtomCode v4.25.3 逆向工程分析文档