You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
record types: ['chunk', 'reasoning-chunks', 'chunk', 'tool-call-chunks', 'chunk', 'tool-call-chunks', 'chunk', 'chunk']
has text-chunks: False
has raw text-delta chunk: False
# 取任一会话日志,对比两种算法
zstd -dc ~/.dsh/sessions/<workspace>/<session-id>/session.v3.jsonl.zstd | python3 - <<'PY'import sys, jsondef is_token_delta(c): t = c.get("type") if t in ("text-delta", "reasoning-delta"): return c.get("text", "") != "" if t == "tool-call-delta": return c.get("argumentsDelta", "") != "" or c.get("name") is not None return Falsedef run_first_token_time(run): if run.get("type") == "tool-call-chunks" and run.get("name") is not None: return run.get("time0") members = run.get("texts") or run.get("args") or [] if not members: return None dt, t = run.get("dt") or [], run.get("time0") for i, frag in enumerate(members): if i: t += dt[i - 1] if i - 1 < len(dt) else 0 if frag != "": return t return Nonedef trajectory_algo(stream): # 当前实现 for r in stream: if r.get("type") == "chunk" and is_token_delta(r.get("chunk", {})): return r.get("time") return Nonedef correct_algo(stream): # dsh-llm / client-connection 实现 for r in stream: if r.get("type") == "chunk": if is_token_delta(r.get("chunk", {})): return r.get("time") else: t = run_first_token_time(r) if t is not None: return t return Nonetraj = correct = total = 0for line in sys.stdin: try: o = json.loads(line) except Exception: continue if o.get("type") != "assistant/message": continue st = o.get("data", {}).get("stream") if not st: continue total += 1 traj += trajectory_algo(st) is not None correct += correct_algo(st) is not Noneprint(f"total={total} trajectory_algo_ok={traj} correct_algo_ok={correct}")# 预期输出:trajectory_algo_ok=0 correct_algo_ok=total(total 随会话内容增长)PY
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
摘要
Trajectory(轨迹)面板的 Assistant timing 面板在**打开已结束的会话(历史回放)**时,
首 token 延迟、生成、吞吐量三项恒显示 "首 token 时间不可用"。同一份会话数据,聊天统计条(turn footer)与
sessionStats投影的 TTFT 均正常。根因是轨迹面板自有的流折叠只识别裸 delta chunk,而持久化的
assistant/message.stream一律以**压缩批(packed run)**格式存储,从未包含裸 delta。
影响
复现
实际:
首 token 延迟/生成/吞吐量均显示首 token 时间不可用期望:三者显示具体数值(如
5.4s、12.1s、42.3 tok/s)环境
0.1.5-alpha.1@deepseek-ai/dsh-client-ui-trajectory0.1.5-alpha.1@deepseek-ai/dsh-llm0.1.5-alpha.1@deepseek-ai/dsh-session-stats0.1.5-alpha.1@deepseek-ai/dsh-api-session-controller0.1.5-alpha.1dsh --profile web,浏览器访问(loopback 与反代均复现)根因分析
数据链路
轨迹面板的首 token 时间来自
state.firstTokenTime,其唯一赋值点在dsh-client-ui-trajectory/lib/client.js:updateChunk的唯一调用来源是客户端合成的assistant/live-chunk事件:而该合成事件在
dsh-api-session-controller/lib/client.js中仅在baseline.activeAttempt存在时才由压缩批展开产生:
服务端只在流式进行中提供
activeAttempt(
dsh-api-session-controller/lib/index.js第 1253 行,snapshot()里...this.activeAttempt === void 0 ? {} : { activeAttempt: {...} })。因此历史回放时
state.firstTokenTime恒为undefined。持久化格式:只有压缩批,没有裸 delta
assistant/message事件的data.stream一律为压缩批记录。实测一条真实记录:其中
chunk记录仅承载block-start/block-end/usage/finish,没有任何
text-delta/reasoning-delta。遍历本机全部历史会话确认:
assistant/message.stream从未包含过裸 delta。(更早的 v0 格式里出现过
text-delta,但它属于assistant/chunk事件,不是assistant/message.stream,故不属于"格式回归",该缺陷在当前架构下一直存在。)
provider 无关性
打包发生在 harness 内部的统一组件
dsh-llm的BlockAssembler.push()(
dsh-llm/lib/index.js第 1119 行起):即:任何 provider 的裸 delta 经 assembler 后一律成为压缩批。
打包是 harness 的存储层行为,与适配器实现无关,故该缺陷对所有 provider 生效。
(报告者本机有 stream 的会话恰好全部来自自建适配器,
但上述代码路径表明官方 provider 走同一 assembler,结论不依赖观测样本。)
为什么三项一起失效
三个渲染函数共用
firstTokenTime作为前置门槛(
dsh-client-ui-trajectory/lib/client.js):timing对象在settleMessage中构造(第 776 行起),直接透传该状态:对照组:同一数据在别处工作正常
dsh-session-statsassistantStreamFirstTokenTime(来自dsh-llm)dsh-client-connection(聊天统计条折叠)expandAssistantStreamdsh-client-ui-trajectoryisTokenDeltadsh-client-connection/lib/client.js第 3057/3063 行即为正确范例:同文件第 1549 行有一份客户端本地的
expandAssistantStream实现(含
validateRecord/validateRun,含time0 + dt差分时间重建),证明该解码逻辑在浏览器侧完全可实现,不违反客户端 bundle 的跨插件值导入约束。
实测数据
在真实会话日志(440 条带 stream 的
assistant/message;会话仍在增长,数字随会话内容增加)上对比两种算法:按修复算法计算出的实际数值(同一会话前 5 步):
即:数据完好,仅未被解码。
建议修复
在
dsh-client-ui-trajectory的settleMessage中,为firstTokenTime增加"从
event.data.stream回退解码"的路径,与dsh-session-stats/dsh-client-connection对齐:实现要点:
import@deepseek-ai/dsh-llm—— 客户端 bundle 有跨插件值导入约束。按
dsh-client-connection的做法在包内本地实现等价解码(压缩批 → 成员时间 =
time0逐项累加dt[i-1];首个非空 text/reasoning 成员,或带
name的 tool-call 批的time0)。state.firstTokenTime的优先级,使实时路径行为不变。assistant/attempt事件(同格式),以防重试步的 timing 缺失。附:最小验证脚本
All reactions