跳转至

20 · 实战三:逐请求追踪、双语言调用与反例验收

前两章已经跑过0–5检查点。本章教你检查实际数据与失败分支,判断改动是否有效。实验固定rc.2 CLI/SDK;无密钥路径使用本地Messages SSE服务,真实loop、工具、日志和存储都运行。

20.1 从干净目录重建

unzip deepseek-agent-labs.zip
cd examples
npm ci --ignore-scripts
npm run check
npm run build
node dist/checkpoints.js 0
node dist/checkpoints.js 1
node dist/checkpoints.js 2
node dist/checkpoints.js 3
node dist/checkpoints.js 4
node dist/checkpoints.js 5

实际验证使用Node24.14.0/npm11.9.0;build生成dist,测试不会自动替你编译。每阶段支持第三个位置参数指定产物目录,例如 node dist/checkpoints.js 1 ./my-trace。默认artifacts目录是读者本地数据,不包含在实验发行包中。

Python另行准备,Node先跑通后再执行:

python3 -m venv .venv
.venv/bin/python -m pip install -r python/requirements.txt
export DSH_COURSE_PYTHON="$PWD/.venv/bin/python"
npm test
npm run demo
npm run demo:python

Debian/Ubuntu若 python3 -m venv 报ensurepip unavailable,可安装与解释器匹配的python3-venv包;已有uv时,也可改用 uv venv .venv,再 uv pip install --python .venv/bin/python -r python/requirements.txt。这是环境准备问题,不应改用未配对的PyPI runtime绕过。

Windows解释器通常是 .venv/Scripts/python.exe;本次没有Windows/macOS实测。npm ci固定依赖解析,不等于验证了所有平台或依赖行为。

20.2 每次请求究竟增加了什么

用任何JSON查看器打开阶段1的requests.json。也可在examples复制:

node --input-type=module - <<'JS'
import { readFile } from 'node:fs/promises'
const requests = JSON.parse(await readFile('artifacts/checkpoint-1/requests.json', 'utf8'))
for (const [index, request] of requests.entries()) {
  console.log('REQUEST', index + 1)
  console.log(JSON.stringify(request.body.messages, null, 2))
}
JS

三次请求的关键截面如下,省略不变的system、tools、模型配置和协议扩展;完整数据保存在文件。

请求 已有消息 本次fixture响应 新增证据
1 用户问题 search_notes,call-1,query=Session 尚无资料
2 问题+call-1+call-1结果 read_note,call-2,id=session 只有ID和24字预览
3 前述消息+call-2+call-2结果 答案与[session] 完整事实首次存在

第二请求的结果块:

{"type":"tool_result","tool_use_id":"call-1","content":[{"type":"text","text":"{\"matches\":[{\"id\":\"session\",\"preview\":\"Session 保存请求与工具的事件日志。搜索摘\"}]}"}],"is_error":false}

第三请求新增结果块:

{"type":"tool_result","tool_use_id":"call-2","content":[{"type":"text","text":"[session] Session 保存请求与工具的事件日志。搜索摘要只用于选择资料。完整证据:失败 attempt 只留诊断,不加入下一次模型历史。"}],"is_error":false}

这两个user-role结果块是Messages协议表达。DSH的逻辑日志使用tool-role message与toolCallId,见06章。切勿把provider序列化形态反推成Session的内部数据类型。

20.3 对着请求找日志,不靠固定seq猜测

node --input-type=module - <<'JS'
import { readFile } from 'node:fs/promises'
const events = JSON.parse(await readFile('artifacts/checkpoint-1/events.json', 'utf8'))
for (const event of events) {
  const data = event.data
  console.log(event.seq, event.type,
    data.callId ?? data.message?.toolCallId ?? data.reason?.kind ?? '')
}
JS

找到 assistant/message里的tool-call,接着找到同ID的tool/call和tool/result。前者是模型提出的动作,后两者是执行意图与结果。结果在下一step构造request前已经提交;如果它没有进入下一请求,要查surface/adapter,而不是先怀疑SDK。

本次三请求通常只有一条request/header,因为provider/model/schema没变。不能以header数当HTTP数。HTTP日志是请求发送证据,step/start是准入步骤证据,assistant/attempt是模型失败结算证据,各自回答不同问题。

完整HTTP body还包含DeepSeek日志扩展及包清单;查聊天历史时看system/messages,查网络发送边界时看完整body。产物只用本地固定资料,不包含真实API key;把自己的资料加入后,应按实际内容管理这些文件。

20.4 三个容易骗过happy path的反例

注册但拒绝。 阶段3仍公布read_note,fixture仍提出调用;pre gate拒绝,provider读取计数测试为0,tool_result.is_error=true,下一请求无全文。turn completed,但taskStatus为insufficient-evidence。说明“正常结束”不是“回答了问题”。

读到证据但没预算回答。 阶段4的read_note成功,evidenceRead为true;第三HTTP未发,最终文本为空,turn blocked。检查点要求budget-exhausted,不会把已经取到资料当交付完成。

假答案缺少前置证据。 checkpoints.test.mjs向fixture发一个错误tool_use_id,哪怕content写着evidence,服务仍返回400而不发送成功答案。fixture校验的是指定结果块,不是仅在完整请求任意位置搜到某个词。这让“删掉read步骤却还得到预写正确答案”成为失败。

此外原有测试保留真实bash越权尝试(标记文件未出现)、HTTP401(assistant/attempt和error结局)、同runtime同Session第二轮(请求含上一回答)、无效参数、取消前不dispatch、post block不回滚与异步gate等待。

20.5 Python是同一运行时的另一宿主

npm run demo:python使用同一JSON provider和两个工具。Node启动HTTP fixture并准备patch,然后用execFile启动 python/notes-agent.py;Python通过公开dsh_bin/profile/patches/dsh_home参数启动同版npm CLI。

rc.2官方Python SDK五个文件原样保留于 python/source_vendor,SOURCE.json记录完整commit/字节哈希与MIT许可。未把PyPI的旧runtime混入;没有私有argv或fake SDK。Python env与父环境合并,所以Node先给Python一个环境白名单。

预期结果是3次请求、2个工具结果、finishReason completed、Python SDK: source session.。这一路fixture也检查call-1摘要与call-2全文。Python提供finish_reason便利字段,TS通过turn/end读取;两种语言不能照搬属性名。

20.6 正常关闭与冷读的证据范围

阶段5在close之后调用官方JSONL provider,只读打开同Session。cold-read.json保存header和完整逻辑events,并逐seq核对SDK事件。它回答“正常退出后,这段事实还在吗”。它没有执行kill-9,没有模拟断电,没有从SDK冷resume,也没有验证所有旧格式迁移。

因为正常SDK run结束的idle不是flush回执,检查点先关闭runtime。生产应用若需要运行中承诺持久提交,应使用明确的flush屏障并处理失败;不能为了出一个文件就任意sleep一秒,然后宣称耐久性已经保证。

20.7 测试是证据索引,不是质量分数

测试组 关键行为 需要读的产物/断言
contracts 生命周期、schema、gate、取消、输出value/render provider读计数、工具schema、canonical value、错误结果
sdk-runtime 实际CLI/协议/loop与历史 真实请求、标记文件、turn原因、同Session内容
checkpoints 六阶段差异与冷读 requests/events/summary/cold-read;错callId反例
python-runtime 官方源码Python SDK到同版CLI 请求数、工具结果、finish reason

测试原输出与本轮作者检查留在research/astra;独立审查由25章记录。旧下载重建报告只证明旧版artifact,不自动覆盖本轮新文件。未测分支见24章;测试多一项只有在它能排除一个真实误判时才有价值。

20.8 用真实模型检验另一类问题

export DEEPSEEK_API_KEY='填写你自己的密钥'
npm run ask -- '解释Session与SDK的关系,注明笔记来源'

这条命令会调用你配置的实际provider,本轮没有执行。默认endpoint为 https://api.deepseek.com/anthropic。进入真实评估前,先准备“已知答案、无资料、冲突资料、错误ID、受限资料、预算不足”六类任务。记录工具选择、全文读取、引用是否支持结论、错误完成率与真实usage。协议fixture证明运行机制,真实评估才回答模型是否会在不被脚本安排的情况下完成任务。