dsh-plugin-model-tuning 2.0.1 → 2.0.2
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 +47 -1
- package/client.js +80 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
- 系统提示角色兼容开关:`自动` / `system` / `developer`(仅 `llm-pi-ai`,
|
|
21
21
|
写入模型条目的 `compat.supportsDeveloperRole`;遇到 kimi/DeepSeek 等
|
|
22
22
|
报 `role 'developer' is not allowed` 时选 `system`);
|
|
23
|
+
- 思考格式兼容开关:`自动` / `自适应 thinking`(仅 `llm-pi-ai` 且路由 `api: anthropic-messages`,
|
|
24
|
+
写入模型条目的 `compat.forceAdaptiveThinking`;见下节「Anthropic 思考格式」。
|
|
25
|
+
该模型一个推理档位都没勾选时不显示——那时请求里根本没有 `thinking`);
|
|
23
26
|
- 生图模型勾选(把该模型写入「绘图」页签的模型列表,见「绘图页签」);
|
|
24
27
|
- 顶部搜索框按模型名 / ID / 描述 / provider 过滤;
|
|
25
28
|
- 修改即时保存(`remote.settings.mutate`;模型目录来自 `remote.session.modelCatalog()`)。
|
|
@@ -136,6 +139,44 @@ harness 的请求模态是**闭合集合 `text | image`**:`dsh-llm` 的 `Model
|
|
|
136
139
|
> 「系统提示角色」开关依赖 DSH 适配层 `dsh-llm-pi-ai` 透传 `compat.supportsDeveloperRole`
|
|
137
140
|
>(`0.1.0-rc.6` 默认不透传,需按本仓库 `deepseek-harness` 的 catalog.ts 补丁同步,或等上游合入)。
|
|
138
141
|
|
|
142
|
+
## Anthropic 思考格式(自适应 vs 预算式)
|
|
143
|
+
|
|
144
|
+
「模型参数」页的**思考格式**开关解决的是这一条报错:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
400 {"error":{"message":"claude-opus-5-5 requires adaptive thinking; omit thinking or use
|
|
148
|
+
thinking.type=adaptive and output_config.effort","type":"invalid_request_error"}}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- **原因**:pi-ai 只有在模型的 `compat.forceAdaptiveThinking === true` 时才发
|
|
152
|
+
`thinking: {type:"adaptive"}` + `output_config: {effort:"…"}`;否则发旧的预算式
|
|
153
|
+
`thinking: {type:"enabled", budget_tokens:N}`。只接受自适应思考的模型(Claude Opus 4.6 及更新、
|
|
154
|
+
Sonnet 5、Fable 5、Kimi K3 等)会直接 400 拒掉预算式请求。
|
|
155
|
+
- **为什么手写路由会中招**:pi-ai 内置目录给它自己带的模型 ID 标了这个标记,
|
|
156
|
+
而 `dsh-llm-pi-ai` 只在「模型 ID 命中该 provider 自己的目录条目」时才继承目录 `compat`
|
|
157
|
+
(`resolveModelCompat` 要求 `base.api === api`)。手写路由(例如 `providers.ironman-claude`
|
|
158
|
+
这种目录里不存在的 key)没有任何目录条目可继承,`claude-opus-5-5` 这类目录里其实认识、
|
|
159
|
+
但挂在 `anthropic` 名下的 ID 也一样继承不到——于是请求退化成预算式。
|
|
160
|
+
- **怎么修**:在该模型的「思考格式」里选**自适应 thinking**,页面把
|
|
161
|
+
`compat.forceAdaptiveThinking: true` 写进模型条目(与 `系统提示角色` 同一套写法),
|
|
162
|
+
下一次请求即生效,无需重启。Claude 4.5 及更早只认预算式,保持「自动」。
|
|
163
|
+
- **另一条路(不动插件)**:把这条路由改成目录里已有的 key(例如 `providers.anthropic`
|
|
164
|
+
只覆盖 `baseURL` / `apiKeyEnv`,用 `models:` 收窄到要用的几个 ID),模型 ID 因此命中目录条目,
|
|
165
|
+
自适应标记、`supportsTemperature` 等兼容位都会自动继承。代价是 provider ID 变了,
|
|
166
|
+
老会话里记录的路由名会对不上,所以通常仍是逐个模型打勾更省事。
|
|
167
|
+
|
|
168
|
+
> **已知边界**:思考强度选 `off` 时,`dsh-llm-pi-ai` 会把声明里 `off: null` 的那一项**移出**
|
|
169
|
+
> `thinkingLevelMap`,pi-ai 因此发 `thinking: {type:"disabled"}`(只有把 `off` 钉成 `null` 才会
|
|
170
|
+
> 整个省略该参数——那是内置目录条目的写法),本开关管不到这一段。若网关连 `disabled` 也拒绝,
|
|
171
|
+
> 把该模型的「可选推理档位」全部取消(写成 `reasoningEfforts: false`,即视为无推理能力),
|
|
172
|
+
> 请求里就完全不会出现 `thinking`。
|
|
173
|
+
|
|
174
|
+
> **耦合点**:字段名 `forceAdaptiveThinking` 与「只在该协议的白名单内」都由上游决定
|
|
175
|
+
> ——`dsh-llm-pi-ai` 的 `anthropic-messages` compat 门(`0.1.7-alpha.2` 起已含该字段,
|
|
176
|
+
> `884f7b9c41` 于 2026-08-18 引入)会拒收白名单外的字段,pi-ai 的
|
|
177
|
+
> `dist/api/anthropic-messages.js` 是唯一读它的地方。`tests/adaptive-thinking.test.mjs`
|
|
178
|
+
> 在有 `DSH_TEST_DEPLOY_ROOT` 时直接问该部署的 pi-ai 要请求体,上游改名会在这里失败而不是在会话里变成 400。
|
|
179
|
+
|
|
139
180
|
## 与 DSH 版本的耦合点(0.1.7-alpha.2)
|
|
140
181
|
|
|
141
182
|
- **provider 目录必须取 `llm.listConfigurableProviders()`**(返回 `LlmConfigurableProvider`
|
|
@@ -155,15 +196,20 @@ node --test tests/title-route.test.mjs # 标题路由解析与 Config
|
|
|
155
196
|
node --test tests/title-purpose.test.mjs # 用途页读写规划 + locale 回归
|
|
156
197
|
node --test tests/image-link.test.mjs # 生图链接写入规划(纯函数)
|
|
157
198
|
node --test tests/image.test.mjs # 绘图传输/工具/Remote(本地 HTTP,不外联)
|
|
199
|
+
node --test tests/adaptive-thinking.test.mjs # 思考格式开关:白名单 + 写入规划(+ 部署耦合守卫)
|
|
158
200
|
DSH_TEST_DEPLOY_ROOT=<隔离部署> node --test tests/title-provider.test.mjs # 真 helper + 假 ctx
|
|
159
201
|
```
|
|
160
202
|
|
|
161
203
|
- `title-provider.test.mjs` 用**假 ctx + 真共享策略**(只 mock `ctx.llm.stream`)验证唯一注册、
|
|
162
204
|
继承/指定路由、首条消息选择、辅助请求记录、失败与取消路径。`tests/_host.mjs` 要求
|
|
163
205
|
`DSH_TEST_DEPLOY_ROOT` 指向隔离的 0.1.7-alpha.2 部署。
|
|
206
|
+
- `adaptive-thinking.test.mjs` 的前半段是纯函数(开关只出现在 `anthropic-messages` 路由、
|
|
207
|
+
写入落在模型条目的 `compat` 上,目录路由落到 `modelOverrides`);后半段只在有
|
|
208
|
+
`DSH_TEST_DEPLOY_ROOT` 时运行,把该部署的 pi-ai 拦在假 `fetch` 前,直接读出请求体,
|
|
209
|
+
证明「打勾 → `thinking.type=adaptive` + `output_config.effort`,不打勾 → 预算式」。
|
|
164
210
|
- **这些测试不是实机验证**。实机验收:标题从会话日志读 `session/title-llm-request` 的 `route`
|
|
165
211
|
与最终 `session/title` 的 `source.model`;绘图确认「模型参数」勾选 → 绘图页出现条目 →
|
|
166
|
-
|
|
212
|
+
直接绘图面板出图;思考格式在模型下拉里选一档后看会话日志里那次请求的请求体。
|
|
167
213
|
|
|
168
214
|
## 安装 / 卸载
|
|
169
215
|
|
package/client.js
CHANGED
|
@@ -142,6 +142,27 @@ window.__ModuleLoader__.load({
|
|
|
142
142
|
const MODALITY_CHOICES = ["auto", "text", "text-image"];
|
|
143
143
|
const MODALITY_LABELS = { auto: "自动", text: "纯文本", "text-image": "文本 + 图像" };
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* The compat switch that selects Anthropic's adaptive thinking request.
|
|
147
|
+
*
|
|
148
|
+
* pi-ai sends `thinking: {type:"adaptive"}` plus `output_config.effort` only
|
|
149
|
+
* for a model whose `compat.forceAdaptiveThinking` is true; without it the
|
|
150
|
+
* request carries the legacy budget form (`thinking.type="enabled"` with
|
|
151
|
+
* `budget_tokens`). A model that requires adaptive thinking answers that
|
|
152
|
+
* legacy request with HTTP 400 — "requires adaptive thinking; omit thinking
|
|
153
|
+
* or use thinking.type=adaptive and output_config.effort". The installed
|
|
154
|
+
* catalog states the flag for the model ids it ships; a route this profile
|
|
155
|
+
* declares by hand has no catalog entry to inherit it from, so the switch
|
|
156
|
+
* has to be written on the model entry here.
|
|
157
|
+
*/
|
|
158
|
+
const ADAPTIVE_THINKING_COMPAT_KEY = "forceAdaptiveThinking";
|
|
159
|
+
/**
|
|
160
|
+
* The one wire protocol whose compat takes that switch. `llm-pi-ai` refuses
|
|
161
|
+
* a compat field the model's resolved api does not offer, so the control is
|
|
162
|
+
* shown only on a route whose own configuration names this protocol.
|
|
163
|
+
*/
|
|
164
|
+
const ADAPTIVE_THINKING_API = "anthropic-messages";
|
|
165
|
+
|
|
145
166
|
function formatCapacity(n) {
|
|
146
167
|
if (n == null || typeof n !== "number" || !Number.isFinite(n)) return "";
|
|
147
168
|
if (n % 1000000 === 0) return String(n / 1000000) + "M";
|
|
@@ -327,6 +348,25 @@ window.__ModuleLoader__.load({
|
|
|
327
348
|
return undefined;
|
|
328
349
|
}
|
|
329
350
|
|
|
351
|
+
/**
|
|
352
|
+
* The compat key this page may write for one route, or null when the route
|
|
353
|
+
* cannot take it.
|
|
354
|
+
*
|
|
355
|
+
* The route's own `api` is the only place the protocol is known before a
|
|
356
|
+
* model resolves: a hand-declared route spells it out, while a catalog route
|
|
357
|
+
* omits it and resolves each model's protocol from the installed catalog.
|
|
358
|
+
* The control therefore appears exactly where the switch is both needed (no
|
|
359
|
+
* catalog entry to inherit the flag from) and accepted.
|
|
360
|
+
* @param settingsNs - the provider's settings namespace.
|
|
361
|
+
* @param profile - the route's own settings section.
|
|
362
|
+
* @returns the adaptive-thinking compat key, or null.
|
|
363
|
+
*/
|
|
364
|
+
function adaptiveThinkingCompatKey(settingsNs, profile) {
|
|
365
|
+
if (settingsNs !== "llm-pi-ai") return null;
|
|
366
|
+
if (!profile || typeof profile !== "object") return null;
|
|
367
|
+
return profile.api === ADAPTIVE_THINKING_API ? ADAPTIVE_THINKING_COMPAT_KEY : null;
|
|
368
|
+
}
|
|
369
|
+
|
|
330
370
|
function computeWriteOps(entry, nsView, modelId, field, value) {
|
|
331
371
|
const ns = entry.settingsNs;
|
|
332
372
|
const settingsPath = entry.settingsPath || [];
|
|
@@ -895,6 +935,7 @@ window.__ModuleLoader__.load({
|
|
|
895
935
|
const nsView = entry ? nsByNs.get(entry.settingsNs) : undefined;
|
|
896
936
|
const providerProfile = profileOf(nsView, entry ? entry.settingsPath : []);
|
|
897
937
|
const reasoningEditable = !!(entry && entry.settingsNs === "llm-pi-ai");
|
|
938
|
+
const adaptiveKey = entry ? adaptiveThinkingCompatKey(entry.settingsNs, providerProfile) : null;
|
|
898
939
|
const routeDefaultInput = providerProfile && Array.isArray(providerProfile.defaultInput)
|
|
899
940
|
? providerProfile.defaultInput
|
|
900
941
|
: null;
|
|
@@ -904,11 +945,15 @@ window.__ModuleLoader__.load({
|
|
|
904
945
|
const devRole = entry && entry.settingsNs === "llm-pi-ai"
|
|
905
946
|
? readCompatField(nsView, entry.settingsPath, m.id, "supportsDeveloperRole")
|
|
906
947
|
: undefined;
|
|
948
|
+
const adaptiveThinking = adaptiveKey
|
|
949
|
+
? readCompatField(nsView, entry.settingsPath, m.id, adaptiveKey)
|
|
950
|
+
: undefined;
|
|
907
951
|
const modalityField = entry ? modalityFieldFor(entry.settingsNs) : undefined;
|
|
908
952
|
const rawInput = modalityField
|
|
909
953
|
? readModelField(nsView, entry.settingsPath, m.id, modalityField)
|
|
910
954
|
: undefined;
|
|
911
955
|
const image = computeImageModelState(view, providerProfile, m.id);
|
|
956
|
+
const efforts = (m.reasoning && Array.isArray(m.reasoning.efforts)) ? m.reasoning.efforts.map((e) => ({ id: e.id, name: e.name })) : [];
|
|
912
957
|
return {
|
|
913
958
|
id: m.id,
|
|
914
959
|
name: m.name,
|
|
@@ -916,6 +961,10 @@ window.__ModuleLoader__.load({
|
|
|
916
961
|
contextWindow: ctx != null ? ctx : null,
|
|
917
962
|
maxTokens: mx != null ? mx : null,
|
|
918
963
|
supportsDeveloperRole: devRole,
|
|
964
|
+
adaptiveThinking: adaptiveThinking === true,
|
|
965
|
+
// The flag only changes a request that carries thinking at all,
|
|
966
|
+
// so it is offered where a level can be selected.
|
|
967
|
+
adaptiveThinkingEditable: adaptiveKey !== null && efforts.length > 0,
|
|
919
968
|
modalityField: modalityField || null,
|
|
920
969
|
modality: modalityField ? modalityChoice(rawInput) : null,
|
|
921
970
|
imageAvailable: image.available,
|
|
@@ -925,7 +974,7 @@ window.__ModuleLoader__.load({
|
|
|
925
974
|
imageApiKeyEnv: image.apiKeyEnv || null,
|
|
926
975
|
declared: !!(entry && entry.declared),
|
|
927
976
|
routeDefaultInput: routeDefaultInput,
|
|
928
|
-
efforts
|
|
977
|
+
efforts,
|
|
929
978
|
defaultEffort: (m.reasoning && m.reasoning.defaultEffort != null) ? m.reasoning.defaultEffort : null,
|
|
930
979
|
};
|
|
931
980
|
});
|
|
@@ -1024,11 +1073,12 @@ window.__ModuleLoader__.load({
|
|
|
1024
1073
|
throw new Error("取值必须是正整数");
|
|
1025
1074
|
}
|
|
1026
1075
|
payload = value;
|
|
1027
|
-
} else if (kind === "supportsDeveloperRole") {
|
|
1076
|
+
} else if (kind === "supportsDeveloperRole" || kind === "adaptiveThinking") {
|
|
1028
1077
|
if (value !== null && typeof value !== "boolean") {
|
|
1029
1078
|
throw new Error("取值必须是布尔值");
|
|
1030
1079
|
}
|
|
1031
|
-
const
|
|
1080
|
+
const compatKey = kind === "supportsDeveloperRole" ? "supportsDeveloperRole" : ADAPTIVE_THINKING_COMPAT_KEY;
|
|
1081
|
+
const write = computeCompatWriteOps(entry, nsView, model, compatKey, value);
|
|
1032
1082
|
if (write) {
|
|
1033
1083
|
const res = await api.settings.mutate({ ns: write.ns, ops: write.ops });
|
|
1034
1084
|
if (!res.result.ok) throw new Error(res.result.error.message);
|
|
@@ -1176,6 +1226,9 @@ window.__ModuleLoader__.load({
|
|
|
1176
1226
|
: null,
|
|
1177
1227
|
model.imageLinked
|
|
1178
1228
|
? React.createElement("span", { className: "mcfg-chip mcfg-chip-on" }, "生图模型")
|
|
1229
|
+
: null,
|
|
1230
|
+
model.adaptiveThinking
|
|
1231
|
+
? React.createElement("span", { className: "mcfg-chip mcfg-chip-on" }, "自适应思考")
|
|
1179
1232
|
: null
|
|
1180
1233
|
)
|
|
1181
1234
|
),
|
|
@@ -1215,6 +1268,7 @@ window.__ModuleLoader__.load({
|
|
|
1215
1268
|
React.createElement("input", {
|
|
1216
1269
|
type: "radio",
|
|
1217
1270
|
name: "devrole-" + model.id,
|
|
1271
|
+
disabled: !editable,
|
|
1218
1272
|
checked: role === "auto"
|
|
1219
1273
|
? model.supportsDeveloperRole === undefined
|
|
1220
1274
|
: (role === "developer" ? model.supportsDeveloperRole === true : model.supportsDeveloperRole === false),
|
|
@@ -1236,6 +1290,29 @@ window.__ModuleLoader__.load({
|
|
|
1236
1290
|
"自动:跟随网关检测;如遇 role 'developer' is not allowed,请选 system"
|
|
1237
1291
|
)
|
|
1238
1292
|
) : null,
|
|
1293
|
+
model.adaptiveThinkingEditable ? React.createElement("div", { className: "mcfg-reasoning" },
|
|
1294
|
+
React.createElement("span", { className: "mcfg-label" }, "思考格式(Anthropic 兼容开关)"),
|
|
1295
|
+
React.createElement("div", { className: "mcfg-levels" },
|
|
1296
|
+
["auto", "adaptive"].map((choice) =>
|
|
1297
|
+
React.createElement("label", { key: choice, className: "mcfg-level" },
|
|
1298
|
+
React.createElement("input", {
|
|
1299
|
+
type: "radio",
|
|
1300
|
+
name: "adaptive-" + model.id,
|
|
1301
|
+
disabled: !editable,
|
|
1302
|
+
checked: choice === "auto" ? !model.adaptiveThinking : model.adaptiveThinking,
|
|
1303
|
+
onChange: () => applyChange(provider.provider, model.id, "adaptiveThinking", choice === "adaptive" ? true : null),
|
|
1304
|
+
}),
|
|
1305
|
+
React.createElement("span", null, choice === "auto" ? "自动(预算式 thinking)" : "自适应 thinking")
|
|
1306
|
+
)
|
|
1307
|
+
)
|
|
1308
|
+
),
|
|
1309
|
+
React.createElement("div", { className: "mcfg-note" },
|
|
1310
|
+
"上面选中档位后,请求体由这里决定:自适应写 compat." + ADAPTIVE_THINKING_COMPAT_KEY
|
|
1311
|
+
+ ",发出 thinking.type=adaptive 与 output_config.effort;Claude Opus 4.6 及更新、Sonnet 5、"
|
|
1312
|
+
+ "Fable 5 等只接受这种,报 400 requires adaptive thinking 就切到它。"
|
|
1313
|
+
+ "Claude 4.5 及更早只认预算式,保持「自动」。"
|
|
1314
|
+
)
|
|
1315
|
+
) : null,
|
|
1239
1316
|
model.modalityField ? React.createElement("div", { className: "mcfg-reasoning" },
|
|
1240
1317
|
React.createElement("span", { className: "mcfg-label" }, "输入模态(写入 " + model.modalityField + ")"),
|
|
1241
1318
|
React.createElement("div", { className: "mcfg-levels" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-model-tuning",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"keywords": ["dsh-plugin", "deepseek-harness", "dsh", "model", "settings", "reasoning", "context", "session-title", "image-generation"],
|
|
6
6
|
"publishConfig": { "registry": "https://registry.npmjs.org" },
|