dsh-payload:在 DSH 裏看每次模型調用的真實請求

前言

在 DSH 裏調智能體,最常遇到的一類問題是:模型這一輪到底發了什麼出去。上下文裏被塞進了一堆自動注入的信息,工具調用來回追加,多步之後請求已經和用戶輸入差得很遠。想排查「爲什麼上下文漲這麼快」「這個參數是誰設的」,通常只能去日誌裏翻事件記錄,過程既慢也容易漏。

下面介紹一個插件 shengpj/dsh-payload。它的做法是直接讀客戶端已經持有的會話日誌,離線重建出「產生某條回覆的那次調用」實際發給模型的完整請求,在界面上一步就能看到,不重發請求、不改服務端。插件分類是「模型推理」,目錄頁顯示 GitHub 星標 1,維護者 shengpj,當前版本 1.9.5(出自倉庫 package.json),許可證 MIT。

這是什麼

一句話定位:一個純客戶端的 DSH Web GUI 插件,給每條 assistant 回覆配上一個「查看請求 JSON」的入口,彈框裏展示這次調用的 model、調用參數、messages、tools、token 拆解和上下文來源佔比。

它和「看日誌」的區別是:日誌是事件流,需要自己按 step/start、step/end 去拼;這個插件按調用邊界把事件摺疊成一份請求快照,並且把本輪新增和歷史分開,直接回答「這次多發了什麼」。

入口位置:每條 assistant 回覆下方的操作行裏(和「複製」「好的回答」同一排)多一個 { } 圖標按鈕。注意只有已定稿的回覆纔有這個操作行,正在流式輸出的那條沒有。

核心功能

精確到某一條回覆。 每條 assistant 消息的按鈕指向產生它的那次調用,而不是「這一輪的最後一次」。一輪裏有四十次調用時,也能找到中間某條回覆對應的那一次。

按調用分組,一套入口。 一個 step 就是一次調用,一次調用一份快照。不管一次還是四十次調用,切換入口都只有左側 step 列表,每行顯示 #k、token 和 step n · m 條消息,右側 JSON 佔滿剩餘寬度。界面形狀不隨調用次數變,不存在「少的時候用卡片、多的時候換列表」兩套心智。

本輪與歷史分開。 messages 裏本輪(這條回覆所屬的 Turn)新產生的消息默認展開,靠一條品牌色豎線標出;更早的歷史合併成 歷史消息 (n) 一行摺疊,數組標題上同時給出 本輪 n / 歷史 n 兩個計數。摺疊只是顯示層,下標仍是數組裏的真實下標,複製出來的 JSON 一字不差。

新增消息就地標記。 同一輪裏第 2 次及以後的調用,messages 中「這次纔出現」的消息直接在 JSON 裏標出來:左側換成綠色實線軌道,行底色鋪一層綠,行首掛「新增」膠囊,新增段之前有一條虛線分隔,寫着「上一次調用已有 1933 條 · 以下爲本次新增 18 條」。判定方式是拿兩次調用的消息數組做前綴比較(prefix 之外的即新增),所以「內容相同但順序挪了」不會被誤判成新增。發生變化的事實用膠囊標在消息條數旁邊:新增 18、消失 4(有消息消失,通常意味着發生了壓縮)、原地改寫、參數變化。一輪的第一次調用沒有標記,因爲「上一次調用」還不存在。

工具調用卡片。 tool_calls 數組裏的每條調用行顯示 工具名(參數摘要) 加狀態徽標(成功 · 21ms / 失敗 · 1.3s / 未返回),懸停看原始參數 JSON,點徽標展開完整的參數與結果正文。狀態、耗時、結果字節都來自本輪日誌裏配對的 tool/call + tool/result,不是解析參數猜的。本次請求裏出現但本輪日誌沒有記錄的調用(歷史被截斷時會有)降級成 未返回,不會憑空消失。

上下文來源佔比。 彈框底部把 messages 按來源分成五類並給出條數與佔比:你的輸入、harness 自動注入(時間上下文、運行時上下文、技能目錄、系統提示、後臺任務回報等)、模型回覆、工具往返。點某一類才展開它由哪些小類組成。旁邊標出「本輪新增」佔多少,用來回答「上下文爲什麼漲這麼快」。佔比按消息字節算,不是 token。

本輪時間線。 一輪有多步時,頭部多一條時間線,每步一段,寬度按該步實際消耗的 token(輸入 + 緩存 + 輸出)算,點哪段切到哪次調用,右端是這一輪的總 token。它和 step 列表分工不重複:列表回答「選第幾次調用」,時間線只回答「佔比」。

請求與響應分離。 右側 JSON 區右上角有 請求 | 響應 切換。響應側同樣可摺疊、可複製,包含 結束原因(要求調用工具 / 正常結束 / 失敗 / 被中斷 等)、失敗重試的每一次嘗試(code + status + message,成功的那次不計入重試)、首字節 / 總時長 / 輸出字符 / 推理字符 / 響應體 字節,以及按 OpenAI chat-completion 形態重建的響應體(choices[0].message + finish_reason + usage,帶推理內容時額外給出 reasoning_content)。這一步徹底失敗、沒有任何回覆時,響應側直接寫「日誌裏沒有這次調用的回覆記錄」,並列出失敗嘗試,不僞造一份空回覆;此時 響應 按鈕變灰。

JSON 與對話互鏈。 messages 裏每條消息的行上常駐一個 對話 ↗ 標籤,點它關掉彈框並把對話滾動到這條消息自己的氣泡(精確到 data-chat-node-key,氣泡在摺疊的輪次裏沒掛載時退回滾到該輪 data-chat-turn)。反方向同理:從某條回覆的按鈕打開彈框時,那份 JSON 會直接停在這條回覆對應的那一行並高亮,不用在幾百條消息裏找。

複製與導航。 複製當前那一次調用,或「複製全部調用」導出成請求數組。JSON 面板右側是常駐加寬的滾動條,頭部另有「↑ 頂部 / ↓ 末尾」按鈕,切換調用或展開收起後自動回到頂部。Esc 或點擊遮罩關閉彈框,彈框高度固定 88vh,只有 JSON 區自己滾動。

安裝與啓用

插件是一個 Cordis bundle(cordis.patch.yml + 一個純客戶端 client.js),裝進 DSH profile 後刷新界面即可,不需要重裝服務端。

方式一(從 GitHub 安裝,讓 DSH 裏的 agent 執行,或自己調 plugin_manager 的 install_bundle):

git+https://github.com/shengpj/dsh-payload.git#<commit>

<commit> 用 40 位 SHA,當前值用下面這條命令取:

git ls-remote https://github.com/shengpj/dsh-payload.git refs/heads/main

方式二(走插件市場索引)。倉庫裏帶了一份 market/index.json(DSH 插件市場 format 1)。啓用 plugin-market 後把這個地址加成索引:

https://raw.githubusercontent.com/shengpj/dsh-payload/main/market/index.json

之後在 Discover 裏搜 dsh-payload 就能一鍵安裝。索引裏的版本指向 commit-pinned 的 git 源,裝到的是倉庫裏對應的那一版。發新版本時用 node tools/build-market-index.mjs --commit <40 位 SHA> --version <新版本號> 往索引里加一條,舊版本條目會保留。

方式三(改代碼時更方便):把倉庫拉到本地,然後用 link:<絕對路徑> 安裝這個目錄,後續改動不需要重裝。三者裝好後的效果一致——在 profile 的 bundles 列表裏能看到 dsh-payload,刷新界面(桌面端 Ctrl+R,改不動就退出重開),回覆下方就出現圖標。

典型用法

1、找到某條 assistant 回覆,把鼠標移到操作行的 { } 上,氣泡顯示「查看請求 JSON」。
2、點擊,彈框打開並直接停在產生這條回覆的那次調用上。
3、messages 默認只展開本輪新產生的消息,更早的歷史收在 歷史消息 (n) 這一行裏,數組標題上的 本輪 n / 歷史 n 告訴你各有多少條。
4、若這條回覆所在的 Turn 調用了很多次,用左側 step 列表切換 step。右側 JSON 右上角是 請求 | 響應:前者是發出去的東西,後者是收回了什麼。
5、tool_calls 裏的行是工具調用卡片:徽標給出成敗與往返耗時,懸停看原始參數,點徽標展開參數與結果正文。
6、「複製」拿走當前這一屏的內容(請求側是請求,響應側是重建出來的響應體),「複製全部調用」拿走整輪所有請求組成的數組。
7、想知道模型回了什麼,點 響應:結束原因 說明它爲什麼停下,重試 列出這一步失敗過的嘗試,下面是重建後的 chat-completion JSON。

數據來源與 token 口徑

插件不重發請求、不新增服務端接口,完全離線重建,數據全部來自 Client 已經持有的會話日誌:

需要的字段 日誌來源
調用邊界(每次調用一條) step/start + step/end 的 {turn, step}
model、provider 該 step 的 assistant/message.data.message.source,缺失時退回 request/header 的 header.config
temperature / maxTokens 等調用參數 request/header 的 header.config,稀疏,需向前繼承
tools request/header 的 header.tools,同樣需向前繼承
messages 該 step 首個響應事件之前的所有 surface 事件
本輪 / 歷史的切分 這條回覆所屬 Turn 的 turn/start.seq
token 用量 該 step 的 assistant/message.data.usage
工具調用的狀態/耗時/結果 tool/call 與 tool/result 按 callId 配對

request/header 只在調用配置發生變化時才寫一條(reason 爲 initial / change / resume / series),不是每次調用都有,所以分組錨點用 step/start,配置從不超過該 step 的最近一條 header 繼承。

token 拆解的字段口徑如下,全部來自 provider 上報的 assistant/message.data.usage:

字段 含義
inputTokens 本次實際發出的 prompt tokens 中未命中緩存的部分
cacheReadTokens 命中 prompt cache 的 tokens
outputTokens 模型生成的 tokens
totalTokens provider 的合計,等於 inputTokens + cacheReadTokens + outputTokens

命中率按 cacheReadTokens / (inputTokens + cacheReadTokens) 計算。三個容易混的量在 README 裏被明確區分:「輸入」chip 是未命中緩存的 prompt tokens,不是 JSON 大小;「緩存」chip 是命中緩存的部分,輸入加緩存纔是完整 prompt;「N 條消息」「請求體 X MB」是消息條數與重建 JSON 的字節數,字節不等於 token。

README 還給了一個實測差異的例子:某次調用 input 1009 / cache 80,640 / output 1,425 / total 83,074,而日誌重建出 1078 條消息、3.70 MB、約 754k tokens,provider 只按 81,649 prompt tokens 計費,差 9.2 倍。原因是請求發出前會先經上下文壓縮把舊區間摺疊成摘要,日誌保留原文、模型只讀摘要。因此 token 一律以 assistant/message.data.usage 爲準,條數和字節只作「這份快照有多大」的參考。相鄰兩次調用有時只差 2 條,是因爲一步的產物正好是「一條 assistant 消息(帶 tool_calls)+ 一條對應的 tool 結果」。

適用場景與注意

適合在 DSH 圖形界面裏做智能體行爲的事後排查:上下文爲什麼漲、哪一步花掉了最多的 token、工具調用是成功還是失敗、模型爲什麼結束、參數是哪次調用變掉的。它不是一個跨端的日誌工具。

幾點需要注意:

  • 只支持 DSH 的圖形界面客戶端,TUI/CLI 下沒有入口。插件是純客戶端實現,沒有 Host 半邊的服務註冊。
  • 插件以當前 DSH 進程的權限運行,安裝前應當檢查源碼與許可證(MIT,見倉庫 LICENSE)。
  • 首次打開某個 Turn 時需要把會話窗口往前翻完(loadOlder()),翻頁次數和會話長度成正比,同一 Turn 的結果會緩存,再開很快。重建本身只摺疊一次窗口,170 次調用的 Turn 在 Node 里約 100 ms。
  • provider 沒上報 usage 的調用會顯示「未記錄到用量」,其餘部分照常展示。
  • 已驗證環境是 DSH 0.2.1-rc.3 桌面端。

結尾

dsh-payload 的價值在於把「這次請求到底是什麼」從一個要翻日誌拼答案的問題,變成一個彈框點開就有的問題。如果你在 DSH 裏做智能體調試,值得裝上試一輪長對話,重點看上下文來源佔比和本輪時間線這兩塊。

  • 社區目錄頁:https://www.skillhub.cn/plugins/shengpj/dsh-payload
  • 倉庫:https://github.com/shengpj/dsh-payload
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜