@huai_xin/dsh-workbuddy 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -323,6 +323,49 @@ node test/live/policy-probe.mjs # 两版白名单与准入结果对照
323
323
  - **模型不可用**:`code 11102 model [...] service info not found`(例如目录里列了但 CLI 渠道不服务的 `glm-4.6` 等)→ `INVALID_REQUEST`,不重试。
324
324
  - **额度/限流**:其他 429 → `RATE_LIMIT`;401/403 → 先续期再重试一次;5xx → `SERVER`。
325
325
 
326
+ ### 流式回答怎么算"正常结束"(2026-10-22 起)
327
+
328
+ 判据是机械的:**只有带 `finish_reason`(且流走到 `data: [DONE]`)或交付了工具调用,才算正常结束**;其余一律视为异常,并按下表处理。这样处理的起因是一次真实故障排查:模型"话说一半就停"看起来像正常结束,实际可能是连接被掐断,而旧实现把它当成了 `stop` → 既不报错也不重试。
329
+
330
+ | 上游怎么结束 | 插件上报 | harness 会做什么 | 日志里能看到 |
331
+ | --- | --- | --- | --- |
332
+ | 没有 `finish_reason`、也没有 `[DONE]`(连接被掐断) | `TRANSPORT` 错误 | **自动退避重试**(默认策略含 `TRANSPORT`) | `… 的响应在 finish_reason/[DONE] 之前中断(收到 … kB,用时 …s,最后一段数据在 …s 前,中断时正在输出思考;可重试的传输错误)` |
333
+ | 同样的断流,但断在工具调用参数中间 | `TRANSPORT` 错误 | 自动重试 | `… 的工具调用参数在传输中被截断(…)` |
334
+ | `[DONE]` 但全程没有 `finish_reason` | `stop`(+ 记一笔) | 正常结束 | `… 的响应以 [DONE] 收尾但没有 finish_reason,按 stop 处理(输出 N tokens)` |
335
+ | 输出打满预算 / 工具参数被输出上限截断 | `max-tokens` | 由 harness 决定续跑 | `… 的输出达到上限,本步被截断(输出 N tokens)` |
336
+ | 正常 `stop` / `tool_calls` | 原样 | 正常 | 无 |
337
+
338
+ 重试本身由 harness 执行,插件只是把它记进日志:
339
+
340
+ ```
341
+ [workbuddy] intl: 第 2/5 次重试(TRANSPORT) — 上游没有给出结束原因就断开了响应…;2.1s 后重试
342
+ ```
343
+
344
+ 另外每个 finish 帧都带一个 `response` 诊断块(`provider` / `model` / `responseId` / `stopReason` / `outputTokens` / `blocks`),会话数据诊断里会原样显示,方便一眼看出"这一步是谁答的、上游叫它什么 id"。它不是 harness 的契约字段,harness 不会据此改变行为。
345
+
346
+ 排查用探针:
347
+
348
+ ```powershell
349
+ node test/live/stream-cut-probe.mjs cn --selftest # 离线验证"断流"分类
350
+ node test/live/stream-cut-probe.mjs intl 10 --long # 真实压测断流率(免费模型零消耗)
351
+ node test/live/find-message.mjs "关键词" <会话前缀> # 按文本定位某一步,并打出前后事件
352
+ node test/live/dump-stream.mjs <会话前缀> <seq,…> # 打印那一步存下来的原始流帧
353
+ node test/live/sampling-probe.mjs intl 4 # 同一提示在各 temperature 下的"提前 stop"率
354
+ ```
355
+
356
+ ### 采样参数(默认不发,可按需覆盖)
357
+
358
+ 插件默认**不发送** `temperature` / `top_p` / `top_k`(harness 没暴露温度设置,`options.temperature` 实际为空),因此走上游默认采样。`stop` 本质是模型采到 EOS,偏热的默认值更容易在"现在我来改……:"这种地方收尾,所以留了两个覆盖开关:
359
+
360
+ ```powershell
361
+ $env:WORKBUDDY_TEMPERATURE = '0.3'
362
+ $env:WORKBUDDY_TOP_P = '0.95'
363
+ ```
364
+
365
+ 优先级:**调用方显式传入的 `temperature`** > 环境变量 > 不发 ✓(默认行为对所有人不变 ✓)。改完需重启 DSH ✓。
366
+
367
+ > 实测(2026-10-22,海外版免费档 `deepseek-v4.1-flash`):一个明确要求调用工具的短提示,在"不发 / 0.2 / 0.5 / 1.0"四档各跑 4 次,**16/16 都正确调用了工具** —— 单纯温度解释不了真实会话里的提前收尾(那次输入有 213k 缓存 token,`effort=max` 却只有 0 个思考 token,更像长上下文下的例行续写)。这个开关是留给真实流量验证用的,默认不改输出风格。
368
+
326
369
  ## 许可
327
370
 
328
371
  MIT,见 [`LICENSE`](LICENSE)。再次说明:本项目为第三方非官方插件,与腾讯 / WorkBuddy 无关联。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huai_xin/dsh-workbuddy",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "WorkBuddy model provider for DeepSeek Harness (international + China deployments)",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/adapter.js CHANGED
@@ -651,7 +651,20 @@ export class WorkBuddyAdapter {
651
651
  function: { name: tool.name, description: tool.description, parameters: tool.parameters }
652
652
  }))
653
653
  if (tools !== undefined && tools.length > 0) body.tools = tools
654
- if (options.temperature !== undefined) body.temperature = options.temperature
654
+ // Sampling is whatever the caller asked for, else the deployment default —
655
+ // except for an explicit override. `stop` is the model sampling its
656
+ // end-of-sequence token, so on a hot default it can end a turn right after
657
+ // "现在我来改……:" instead of calling the tool, and nothing in the harness
658
+ // exposes a temperature to tune that. These two variables let an operator try a
659
+ // cooler profile without changing the default behaviour:
660
+ // WORKBUDDY_TEMPERATURE=0.3 WORKBUDDY_TOP_P=0.95
661
+ // Measured 2026-10-22: a short tool-using prompt called the tool 16/16 times at
662
+ // the default and at 0.2/0.5/1.0, so sampling alone does not explain the early
663
+ // stops; the override exists to test the long-context case on real traffic.
664
+ const temperature = options.temperature ?? envNumber('WORKBUDDY_TEMPERATURE')
665
+ if (temperature !== undefined) body.temperature = temperature
666
+ const topP = envNumber('WORKBUDDY_TOP_P')
667
+ if (topP !== undefined) body.top_p = topP
655
668
  if (options.stop !== undefined && options.stop.length > 0) body.stop = options.stop
656
669
  const maxTokens = positiveInt(options.maxTokens) ?? entry?.maxTokens
657
670
  if (maxTokens !== undefined) body.max_tokens = maxTokens
@@ -1027,6 +1040,14 @@ function positiveInt(value) {
1027
1040
  return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined
1028
1041
  }
1029
1042
 
1043
+ /** A numeric override from the environment, or `undefined` when unset/unusable. */
1044
+ function envNumber(name) {
1045
+ const raw = process.env[name]
1046
+ if (typeof raw !== 'string' || raw.trim().length === 0) return undefined
1047
+ const value = Number(raw)
1048
+ return Number.isFinite(value) ? value : undefined
1049
+ }
1050
+
1030
1051
  /**
1031
1052
  * Describe a stream that ended without its terminator.
1032
1053
  *