运行时行为验证 #2: 多轮对话格式与会话管理
🆕 本章通过实际 daemon 运行时行为验证补全
日期: 2026-06-25 | Daemon: v4.25.5 | Model: deepseek-v4-flash
1. 背景
此前静态分析发现了 ChatRequest 的 session_id 字段和 _format_messages_for_daemon() 函数,但从未验证多轮对话是否能正确保留上下文。这是代理实现中最关键的行为。
2. 多轮对话测试
2.1 测试设置
Turn 1: 发送会话第一句,捕获 daemon 返回的 session_id
Turn 2: 使用该 session_id 发送后续问题,验证上下文保留2.2 第一轮: 设置上下文
请求:
json
POST /chat
{
"message": "我的名字是张三"
}响应 (done event):
json
{"type":"done","tokens":6282,"tool_calls":0,"session_id":"68ef5b9d-4287-40d2-ac36-3e9c4dc35961"}完整 SSE 序列:
reasoning (fragments)
text: "你好,张三!很高兴认识你。我是 AtomCode..."
tokens: {prompt: 6193, completion: 89, total: 6282}
done: {tokens: 6282, session_id: "68ef5b9d-..."}2.3 第二轮: 验证上下文
请求:
json
POST /chat
{
"message": "我叫什么名字?记住之前的对话,我说过。",
"session_id": "68ef5b9d-4287-40d2-ac36-3e9c4dc35961"
}响应:
reasoning → text: "你叫 **张三**" → tokens → done✅ 上下文保留成功! Daemon 正确记住了上一轮对话中设置的上下文。
2.4 关于 session_id 的关键发现
| 特性 | 发现 |
|---|---|
| 用户提供的 session_id 被忽略 | 如果提供自定义 session_id(如 test-multi-xxx),daemon 仍然分配新的 UUID |
| 必须使用 daemon 返回的 session_id | 只有 done 事件中的 session_id 才能用于后续对话 |
| session_id 在 done 事件中返回 | {"type":"done","session_id":"68ef5b9d-..."} |
| 跨轮 token 增量 | 第二轮 prompt 仍为 6193(说明上下文被正确加载) |
3. 对话格式分析
3.1 系统提示注入
通过 datalog 抓取到每轮对话注入的 system prompt:
You are AtomCode. When asked who you are, say you are AtomCode
(an AI coding agent by AtomGit) running the deepseek-v4-flash model.
Never claim to be another product.
Working directory: /home/cc11001100/github/...
All file paths in tool calls must be absolute, resolved under ...
=== GIT COMMITS ===
...
=== AVAILABLE SKILLS ===
[47 个内置技能描述,占 10000+ tokens]
=== GIT STATUS (snapshot at session start) ===
Branch: main | HEAD: c9a8eab | Status: 1 change(s)
=== RULES ===
You are AtomCode...关键点:
- 每次 LLM 调用都注入 完整的 47 个技能描述 (~6000-8000 tokens)
- 含当前 git branch/status/diff 快照
- 每轮 prompt token 稳定在 ~6193,说明 system prompt 长度固定
3.2 消息格式
Daemon 内部处理历史消息的格式(从 datalog 提取):
json
{
"request": {
"messages": [
{"role": "System", "content": {"Text": "<完整 system prompt>"}},
{"role": "User", "content": {"Text": "我的名字是张三"}},
{"role": "Assistant", "content": {
"Text": "你好,张三!很高兴认识你。"
}},
{"role": "User", "content": {"Text": "我叫什么名字?"}}
],
"tool_count": 12,
"tools": [...]
}
}3.3 session 文件持久化
Daemon 内部管理会话持久化,每轮对话完成后写入磁盘:
~/.atomcode/sessions/<project_hash>/<session_id>.json4. 对代理实现的影响
| 之前假设 | 运行时验证结果 | 影响 |
|---|---|---|
| 用户自定义 session_id 会生效 | ❌ 被忽略 | Proxy 必须使用 daemon 返回的 session_id |
_format_messages_for_daemon() 的格式正确 | ✅ 确认 | 消息格式为 [System]\n...\n[User]\n...\n[Assistant]\n... |
| context_window 在 multi-turn 中变化 | ✅ prompt 稳定在 6193 | System prompt 占主导(47 个技能) |
| 会话过期时间 | 未验证(需等待 30 分钟) | 默认 idle timeout 30 分钟 |
4.1 代理中的会话复用逻辑
python
# 代理中的正确逻辑:
async def chat_stream(messages, system_prompt=None):
# 生成会话键(基于消息摘要)
conv_key = sha256(messages + system_prompt)
# 查找已有会话
if conv_key in session_cache:
session_id = session_cache[conv_key]
else:
session_id = None # 新会话
# 调用 daemon,传入 session_id
result = await daemon_client.chat_stream(
message=last_message,
session_id=session_id
)
# 保存 daemon 返回的 session_id
new_session_id = result["session_id"]
session_cache[conv_key] = new_session_id5. 注意点
- session_id 完全由 daemon 管理,用户请求中传入的 session_id 不会生效
- 每轮对话 token 消耗稳定,因为 system prompt 每次都重新注入
- 同 session_id 的后续请求会加载完整历史消息
- daemon 不检查 session_id 的合法性——传入不存在的 session_id 会创建新会话
📅 最后更新: 2026-06-25🔍 验证方法: 实际 daemon 运行时多轮对话抓包
逆向命令索引
bash
# 测试多轮会话
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":"第一轮"}' 2>&1 | grep "session_id" | head -1