dsh-llm-verifier 0.1.0
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/LICENSE +22 -0
- package/README.md +218 -0
- package/cordis.patch.yml +4 -0
- package/lib/caller-BgqCctCh.js +324 -0
- package/lib/caller-CGlgZ-Su.js +324 -0
- package/lib/caller.js +2 -0
- package/lib/client.js +385 -0
- package/lib/core.js +163 -0
- package/lib/index.js +1031 -0
- package/lib/types/cache.d.ts +27 -0
- package/lib/types/cache.js +82 -0
- package/lib/types/caller.d.ts +48 -0
- package/lib/types/caller.js +125 -0
- package/lib/types/client.d.ts +4 -0
- package/lib/types/client.js +66 -0
- package/lib/types/config.d.ts +35 -0
- package/lib/types/config.js +57 -0
- package/lib/types/core.d.ts +38 -0
- package/lib/types/core.js +177 -0
- package/lib/types/engine.d.ts +79 -0
- package/lib/types/engine.js +146 -0
- package/lib/types/images.d.ts +3 -0
- package/lib/types/images.js +45 -0
- package/lib/types/index.d.ts +11 -0
- package/lib/types/index.js +42 -0
- package/lib/types/session.d.ts +23 -0
- package/lib/types/session.js +67 -0
- package/lib/types/top-logprobs.d.ts +24 -0
- package/lib/types/top-logprobs.js +97 -0
- package/package.json +110 -0
- package/src/cache.ts +88 -0
- package/src/caller.test.ts +34 -0
- package/src/caller.ts +138 -0
- package/src/client.tsx +48 -0
- package/src/config.ts +84 -0
- package/src/core.test.ts +67 -0
- package/src/core.ts +204 -0
- package/src/engine.ts +109 -0
- package/src/images.ts +33 -0
- package/src/index.ts +48 -0
- package/src/parity.test.ts +71 -0
- package/src/session.test.ts +22 -0
- package/src/session.ts +75 -0
- package/src/top-logprobs.ts +97 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-llm-verifier",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/client.tsx
|
|
11
|
+
const NS = "llm-verifier";
|
|
12
|
+
const shell = {
|
|
13
|
+
display: "flex",
|
|
14
|
+
flexDirection: "column",
|
|
15
|
+
gap: 18,
|
|
16
|
+
padding: "8px 4px 32px",
|
|
17
|
+
color: "var(--dsw-text-primary)"
|
|
18
|
+
};
|
|
19
|
+
const card = {
|
|
20
|
+
display: "flex",
|
|
21
|
+
flexDirection: "column",
|
|
22
|
+
gap: 0,
|
|
23
|
+
padding: "16px 16px 0",
|
|
24
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))",
|
|
25
|
+
borderRadius: 12,
|
|
26
|
+
background: "var(--dsw-alias-bg-module, rgba(20, 31, 57, 0.42))",
|
|
27
|
+
overflow: "hidden"
|
|
28
|
+
};
|
|
29
|
+
const sectionTitle = {
|
|
30
|
+
display: "flex",
|
|
31
|
+
gap: 10,
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
padding: "0 0 12px",
|
|
34
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))"
|
|
35
|
+
};
|
|
36
|
+
const row = {
|
|
37
|
+
display: "grid",
|
|
38
|
+
gridTemplateColumns: "minmax(150px, 1fr) minmax(220px, 1.4fr)",
|
|
39
|
+
gap: 18,
|
|
40
|
+
alignItems: "center",
|
|
41
|
+
padding: "14px 0",
|
|
42
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))"
|
|
43
|
+
};
|
|
44
|
+
const selectStyle = {
|
|
45
|
+
width: "100%",
|
|
46
|
+
minHeight: 38,
|
|
47
|
+
padding: "0 12px",
|
|
48
|
+
borderRadius: 10,
|
|
49
|
+
color: "var(--dsw-text-primary)",
|
|
50
|
+
background: "var(--dsw-surface-sunken)",
|
|
51
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))"
|
|
52
|
+
};
|
|
53
|
+
function record(value) {
|
|
54
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
55
|
+
}
|
|
56
|
+
function values(view) {
|
|
57
|
+
const v = record(view.value);
|
|
58
|
+
return {
|
|
59
|
+
provider: String(v.provider ?? ""),
|
|
60
|
+
model: String(v.model ?? ""),
|
|
61
|
+
...typeof v.reasoningEffort === "string" ? { reasoningEffort: v.reasoningEffort } : {},
|
|
62
|
+
maxTokens: Number(v.maxTokens ?? 32768),
|
|
63
|
+
maxConcurrency: Number(v.maxConcurrency ?? 8),
|
|
64
|
+
maxRetries: Number(v.maxRetries ?? 3),
|
|
65
|
+
timeoutMs: Number(v.timeoutMs ?? 3e5),
|
|
66
|
+
cacheMaxEntries: Number(v.cacheMaxEntries ?? 1e4),
|
|
67
|
+
estimatedInputUsdPerMillion: Number(v.estimatedInputUsdPerMillion ?? 0),
|
|
68
|
+
estimatedOutputUsdPerMillion: Number(v.estimatedOutputUsdPerMillion ?? 0)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function message(error) {
|
|
72
|
+
return error instanceof Error ? error.message : String(error);
|
|
73
|
+
}
|
|
74
|
+
function Label({ title, help }) {
|
|
75
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
76
|
+
style: { fontWeight: 600 },
|
|
77
|
+
children: title
|
|
78
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
79
|
+
style: {
|
|
80
|
+
fontSize: 12,
|
|
81
|
+
color: "var(--dsw-text-secondary)",
|
|
82
|
+
marginTop: 3
|
|
83
|
+
},
|
|
84
|
+
children: help
|
|
85
|
+
})] });
|
|
86
|
+
}
|
|
87
|
+
function VerifierSettings({ api }) {
|
|
88
|
+
const [loaded, setLoaded] = (0, react.useState)(null);
|
|
89
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
90
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
91
|
+
const [error, setError] = (0, react.useState)(null);
|
|
92
|
+
const [saved, setSaved] = (0, react.useState)(false);
|
|
93
|
+
const load = async () => {
|
|
94
|
+
setError(null);
|
|
95
|
+
try {
|
|
96
|
+
const [m, s] = await Promise.all([api.llm.models({}), api.settings.describe({})]);
|
|
97
|
+
if (!m.result.ok) throw new Error(m.result.error.message);
|
|
98
|
+
if (!s.result.ok) throw new Error(s.result.error.message);
|
|
99
|
+
const view = s.result.value.namespaces.find((x) => x.ns === NS);
|
|
100
|
+
if (!view) throw new Error("Verifier settings namespace is not registered. Restart the DSH host.");
|
|
101
|
+
const next = {
|
|
102
|
+
groups: m.result.value.groups,
|
|
103
|
+
settings: view,
|
|
104
|
+
writable: s.result.value.writable,
|
|
105
|
+
failures: m.result.value.failures.map((f) => f.name + ": " + f.message)
|
|
106
|
+
};
|
|
107
|
+
setLoaded(next);
|
|
108
|
+
setDraft(values(view));
|
|
109
|
+
} catch (e) {
|
|
110
|
+
setError(message(e));
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
(0, react.useEffect)(() => {
|
|
114
|
+
load();
|
|
115
|
+
}, []);
|
|
116
|
+
const models = (0, react.useMemo)(() => loaded?.groups.find((g) => g.id === draft?.provider)?.models ?? [], [loaded, draft?.provider]);
|
|
117
|
+
const efforts = models.find((m) => m.id === draft?.model)?.reasoning?.efforts ?? [];
|
|
118
|
+
const patch = (key, value) => setDraft((v) => v ? {
|
|
119
|
+
...v,
|
|
120
|
+
[key]: value
|
|
121
|
+
} : v);
|
|
122
|
+
const save = async () => {
|
|
123
|
+
if (!loaded || !draft) return;
|
|
124
|
+
setBusy(true);
|
|
125
|
+
setSaved(false);
|
|
126
|
+
setError(null);
|
|
127
|
+
try {
|
|
128
|
+
const section = {
|
|
129
|
+
...record(loaded.settings.user),
|
|
130
|
+
...draft
|
|
131
|
+
};
|
|
132
|
+
if (!draft.reasoningEffort) delete section.reasoningEffort;
|
|
133
|
+
const res = await api.settings.update({
|
|
134
|
+
ns: NS,
|
|
135
|
+
patch: section,
|
|
136
|
+
expectedRevision: loaded.settings.revision
|
|
137
|
+
});
|
|
138
|
+
if (!res.result.ok) throw new Error(res.result.error.message);
|
|
139
|
+
setLoaded((v) => v ? {
|
|
140
|
+
...v,
|
|
141
|
+
settings: res.result.value
|
|
142
|
+
} : v);
|
|
143
|
+
setDraft(values(res.result.value));
|
|
144
|
+
setSaved(true);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
setError(message(e));
|
|
147
|
+
} finally {
|
|
148
|
+
setBusy(false);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
if (!loaded || !draft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
152
|
+
style: shell,
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: "LLM Verifier" }),
|
|
155
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: error ?? "正在读取 DSH 模型和设置…" }),
|
|
156
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
157
|
+
onClick: () => void load(),
|
|
158
|
+
children: "重试"
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
const numeric = (key, min = 0) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
163
|
+
type: "number",
|
|
164
|
+
min,
|
|
165
|
+
value: String(draft[key]),
|
|
166
|
+
onChange: (e) => patch(key, Number(e.target.value))
|
|
167
|
+
});
|
|
168
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
169
|
+
style: shell,
|
|
170
|
+
children: [
|
|
171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
172
|
+
style: { margin: "0 0 6px" },
|
|
173
|
+
children: "LLM Verifier"
|
|
174
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
175
|
+
style: {
|
|
176
|
+
margin: 0,
|
|
177
|
+
color: "var(--dsw-text-secondary)"
|
|
178
|
+
},
|
|
179
|
+
children: "选择任意已在 DSH「模型」页配置并启用的模型作为独立裁判。设置实时生效。"
|
|
180
|
+
})] }),
|
|
181
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
182
|
+
style: card,
|
|
183
|
+
children: [
|
|
184
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
185
|
+
style: sectionTitle,
|
|
186
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "done" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "裁判模型" })]
|
|
187
|
+
}),
|
|
188
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
189
|
+
style: row,
|
|
190
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
191
|
+
title: "供应商",
|
|
192
|
+
help: "只显示当前 DSH 中可路由的供应商"
|
|
193
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
194
|
+
style: selectStyle,
|
|
195
|
+
value: draft.provider,
|
|
196
|
+
onChange: (e) => {
|
|
197
|
+
const provider = e.target.value;
|
|
198
|
+
const first = loaded.groups.find((g) => g.id === provider)?.models[0];
|
|
199
|
+
setDraft({
|
|
200
|
+
...draft,
|
|
201
|
+
provider,
|
|
202
|
+
...first ? {
|
|
203
|
+
model: first.id,
|
|
204
|
+
reasoningEffort: first.reasoning?.defaultEffort
|
|
205
|
+
} : {}
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
children: loaded.groups.map((g) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
209
|
+
value: g.id,
|
|
210
|
+
children: [
|
|
211
|
+
g.name,
|
|
212
|
+
" · ",
|
|
213
|
+
g.id
|
|
214
|
+
]
|
|
215
|
+
}, g.id))
|
|
216
|
+
})]
|
|
217
|
+
}),
|
|
218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
219
|
+
style: row,
|
|
220
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
221
|
+
title: "模型",
|
|
222
|
+
help: "模型目录来自 DSH adapter,选择结果会持久化"
|
|
223
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
224
|
+
style: selectStyle,
|
|
225
|
+
value: draft.model,
|
|
226
|
+
onChange: (e) => {
|
|
227
|
+
const model = e.target.value;
|
|
228
|
+
const found = models.find((m) => m.id === model);
|
|
229
|
+
setDraft({
|
|
230
|
+
...draft,
|
|
231
|
+
model,
|
|
232
|
+
...found?.reasoning?.defaultEffort ? { reasoningEffort: found.reasoning.defaultEffort } : { reasoningEffort: void 0 }
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
children: models.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
236
|
+
value: m.id,
|
|
237
|
+
children: [
|
|
238
|
+
m.name,
|
|
239
|
+
" · ",
|
|
240
|
+
m.id
|
|
241
|
+
]
|
|
242
|
+
}, m.id))
|
|
243
|
+
})]
|
|
244
|
+
}),
|
|
245
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
246
|
+
style: row,
|
|
247
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
248
|
+
title: "推理强度",
|
|
249
|
+
help: "由所选模型 adapter 声明;留空使用模型默认值"
|
|
250
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
251
|
+
style: selectStyle,
|
|
252
|
+
value: draft.reasoningEffort ?? "",
|
|
253
|
+
onChange: (e) => patch("reasoningEffort", e.target.value || void 0),
|
|
254
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
255
|
+
value: "",
|
|
256
|
+
children: "模型默认"
|
|
257
|
+
}), efforts.map((e) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
258
|
+
value: e.id,
|
|
259
|
+
children: e.name
|
|
260
|
+
}, e.id))]
|
|
261
|
+
})]
|
|
262
|
+
}),
|
|
263
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
264
|
+
style: row,
|
|
265
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
266
|
+
title: "最大输出 Token",
|
|
267
|
+
help: "每个裁判请求的输出上限"
|
|
268
|
+
}), numeric("maxTokens", 1)]
|
|
269
|
+
})
|
|
270
|
+
]
|
|
271
|
+
}),
|
|
272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
273
|
+
style: card,
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
276
|
+
style: sectionTitle,
|
|
277
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "执行控制" })
|
|
278
|
+
}),
|
|
279
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
280
|
+
style: row,
|
|
281
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
282
|
+
title: "最大并发",
|
|
283
|
+
help: "所有 verifier 工具共享的请求并发上限"
|
|
284
|
+
}), numeric("maxConcurrency", 1)]
|
|
285
|
+
}),
|
|
286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
287
|
+
style: row,
|
|
288
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
289
|
+
title: "最多重试",
|
|
290
|
+
help: "短暂网络、限流和服务端错误的重试次数"
|
|
291
|
+
}), numeric("maxRetries", 0)]
|
|
292
|
+
}),
|
|
293
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
294
|
+
style: row,
|
|
295
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
296
|
+
title: "请求超时(毫秒)",
|
|
297
|
+
help: "单个模型请求的超时时间"
|
|
298
|
+
}), numeric("timeoutMs", 1)]
|
|
299
|
+
}),
|
|
300
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
301
|
+
style: row,
|
|
302
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
303
|
+
title: "缓存条目上限",
|
|
304
|
+
help: "持久评分缓存保留的最大条目数"
|
|
305
|
+
}), numeric("cacheMaxEntries", 1)]
|
|
306
|
+
})
|
|
307
|
+
]
|
|
308
|
+
}),
|
|
309
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
310
|
+
style: card,
|
|
311
|
+
children: [
|
|
312
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
313
|
+
style: sectionTitle,
|
|
314
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "费用估算(每百万 Token,USD)" })
|
|
315
|
+
}),
|
|
316
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
317
|
+
style: row,
|
|
318
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
319
|
+
title: "输入价格",
|
|
320
|
+
help: "仅用于结果中的 estimatedCostUsd"
|
|
321
|
+
}), numeric("estimatedInputUsdPerMillion", 0)]
|
|
322
|
+
}),
|
|
323
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
324
|
+
style: row,
|
|
325
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
|
|
326
|
+
title: "输出价格",
|
|
327
|
+
help: "仅用于结果中的 estimatedCostUsd"
|
|
328
|
+
}), numeric("estimatedOutputUsdPerMillion", 0)]
|
|
329
|
+
})
|
|
330
|
+
]
|
|
331
|
+
}),
|
|
332
|
+
loaded.failures.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
333
|
+
style: {
|
|
334
|
+
...card,
|
|
335
|
+
borderColor: "var(--dsw-alias-state-warn-primary, #d9a441)",
|
|
336
|
+
paddingBottom: 16
|
|
337
|
+
},
|
|
338
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "部分模型目录读取失败" }), loaded.failures.map((x) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: x }, x))]
|
|
339
|
+
}),
|
|
340
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
341
|
+
style: { color: "var(--dsw-danger)" },
|
|
342
|
+
children: error
|
|
343
|
+
}),
|
|
344
|
+
saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
345
|
+
style: { color: "var(--dsw-success)" },
|
|
346
|
+
children: "已保存,后续 verifier 调用将使用新设置。"
|
|
347
|
+
}),
|
|
348
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
349
|
+
style: {
|
|
350
|
+
display: "flex",
|
|
351
|
+
gap: 10
|
|
352
|
+
},
|
|
353
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
354
|
+
disabled: busy || !loaded.writable,
|
|
355
|
+
onClick: () => void save(),
|
|
356
|
+
children: busy ? "保存中…" : "保存设置"
|
|
357
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
358
|
+
variant: "outline",
|
|
359
|
+
disabled: busy,
|
|
360
|
+
onClick: () => void load(),
|
|
361
|
+
children: "重新载入"
|
|
362
|
+
})]
|
|
363
|
+
})
|
|
364
|
+
]
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const inject = ["slots", "connection"];
|
|
368
|
+
function apply(ctx) {
|
|
369
|
+
const connection = ctx.get("connection");
|
|
370
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
371
|
+
name: "settings.section",
|
|
372
|
+
id: "llm-verifier",
|
|
373
|
+
order: 35,
|
|
374
|
+
label: "LLM Verifier",
|
|
375
|
+
inject: () => ({ api: connection.api })
|
|
376
|
+
}, VerifierSettings));
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
exports.apply = apply;
|
|
380
|
+
exports.inject = inject;
|
|
381
|
+
return module.exports;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
//# sourceMappingURL=client.js.map
|
package/lib/core.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
//#region src/core.ts
|
|
2
|
+
const GRANULARITY = 20;
|
|
3
|
+
const LETTERS = Array.from({ length: 20 }, (_, index) => String.fromCharCode(65 + index));
|
|
4
|
+
const SCALE_DESCRIPTION = [
|
|
5
|
+
"Rate how likely the agent correctly solved the task on a 20-point scale using letters A through T:",
|
|
6
|
+
" A = clearly and completely succeeded with verified output (best)",
|
|
7
|
+
" B-D = succeeded with only minor issues",
|
|
8
|
+
" E-G = above average, mostly correct with some issues",
|
|
9
|
+
" H-J = uncertain, leans toward success",
|
|
10
|
+
" K-M = uncertain, leans toward failure",
|
|
11
|
+
" N-P = below average, significant issues remain",
|
|
12
|
+
" Q-S = failed with some partial progress",
|
|
13
|
+
" T = clearly and completely failed (worst)"
|
|
14
|
+
].join("\n");
|
|
15
|
+
const DEFAULT_CRITERIA = [
|
|
16
|
+
{
|
|
17
|
+
id: "specification",
|
|
18
|
+
name: "Specification Adherence",
|
|
19
|
+
description: "Re-read the task description and check exact requirements: file paths, output formats, naming, and explicit constraints. Penalize a solution that solves a similar but different problem."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "output_match",
|
|
23
|
+
name: "Output Match",
|
|
24
|
+
description: "Find the final verification command and compare its actual stdout/stderr to the required output. Reward only evidence literally visible in observed output; do not trust narration."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "error_signals",
|
|
28
|
+
name: "Error Signal Detection",
|
|
29
|
+
description: "Scan especially later steps for unresolved errors, tracebacks, non-zero exits, command-not-found, missing files, compilation failures, and test failures. Score only unresolved error evidence."
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
const DEFAULT_GROUND_TRUTH_NOTE = "**IMPORTANT:** Focus on observed tool and terminal output as ground truth. Do NOT trust the agent's self-assessment or claims of success.";
|
|
33
|
+
function normalizeScoreLetter(token) {
|
|
34
|
+
let value = token.trim();
|
|
35
|
+
if (value.startsWith(">")) value = value.slice(1).trim();
|
|
36
|
+
return /^([A-T])$/i.exec(value)?.[1]?.toUpperCase();
|
|
37
|
+
}
|
|
38
|
+
function letterValue(letter) {
|
|
39
|
+
return 20 - (letter.charCodeAt(0) - 65);
|
|
40
|
+
}
|
|
41
|
+
function findTagLogprobs(tokens, positions, tag) {
|
|
42
|
+
if (tokens.length === 0 || positions.length === 0) return void 0;
|
|
43
|
+
for (const suffix of [tag, tag.slice(0, -1)]) {
|
|
44
|
+
let found;
|
|
45
|
+
let text = "";
|
|
46
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
47
|
+
text += tokens[index];
|
|
48
|
+
if (text.trimEnd().endsWith(suffix) && index + 1 < positions.length) found = positions[index + 1];
|
|
49
|
+
}
|
|
50
|
+
if (found !== void 0) return found;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function extractScore(completion, tag) {
|
|
54
|
+
const alternatives = findTagLogprobs(completion.tokens, completion.positions, tag);
|
|
55
|
+
const probabilities = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const alternative of alternatives ?? []) {
|
|
57
|
+
const letter = normalizeScoreLetter(alternative.token);
|
|
58
|
+
if (letter === void 0 || !Number.isFinite(alternative.logprob)) continue;
|
|
59
|
+
const value = letterValue(letter);
|
|
60
|
+
probabilities.set(value, Math.max(probabilities.get(value) ?? 0, Math.exp(alternative.logprob)));
|
|
61
|
+
}
|
|
62
|
+
if (probabilities.size > 0) {
|
|
63
|
+
let probability = 0;
|
|
64
|
+
let expectation = 0;
|
|
65
|
+
for (const [value, p] of probabilities) {
|
|
66
|
+
probability += p;
|
|
67
|
+
expectation += value * p;
|
|
68
|
+
}
|
|
69
|
+
if (probability > 0) return (expectation / probability - 1) / 19;
|
|
70
|
+
}
|
|
71
|
+
const name = tag.slice(1, -1);
|
|
72
|
+
const regex = new RegExp("<" + name + ">\\s*(.+?)\\s*</" + name + ">", "gi");
|
|
73
|
+
let last = null;
|
|
74
|
+
for (let match = regex.exec(completion.text); match !== null; match = regex.exec(completion.text)) last = match;
|
|
75
|
+
const letter = normalizeScoreLetter(last?.[1] ?? "");
|
|
76
|
+
return letter === void 0 ? .5 : (letterValue(letter) - 1) / 19;
|
|
77
|
+
}
|
|
78
|
+
function buildPairwisePrompt(problem, traceA, traceB, criterion, groundTruthNote = DEFAULT_GROUND_TRUTH_NOTE) {
|
|
79
|
+
return [
|
|
80
|
+
"You are an expert evaluator of AI coding agents. You will see a task description and two agent trajectories, then evaluate them on ONE specific criterion, stated at the end.",
|
|
81
|
+
groundTruthNote,
|
|
82
|
+
"**Task:**\n" + problem,
|
|
83
|
+
"**Trajectory A:**\n" + traceA,
|
|
84
|
+
"**Trajectory B:**\n" + traceB,
|
|
85
|
+
"**Rating Scale:**\n" + SCALE_DESCRIPTION,
|
|
86
|
+
"**Evaluation Guideline — " + criterion.name + ":**\n" + criterion.description,
|
|
87
|
+
"Score each trajectory ONLY on this specific criterion (\"" + criterion.name + "\"). Ignore other aspects that are not relevant to it.",
|
|
88
|
+
"Reason it through first, then END your reply with exactly these two lines and nothing after them. Replace each placeholder with a single letter A-T, keeping the spaces around the letter exactly as shown:\n<score_A> LETTER_A_TO_T </score_A>\n<score_B> LETTER_A_TO_T </score_B>",
|
|
89
|
+
"Begin your analysis now."
|
|
90
|
+
].join("\n\n");
|
|
91
|
+
}
|
|
92
|
+
function buildProgressPrompt(problem, steps, checkpoints) {
|
|
93
|
+
const trajectory = steps.map((step, index) => "=== Agent Step " + (index + 1) + " ===\n" + step.trim()).join("\n\n");
|
|
94
|
+
const tags = checkpoints.map((_, index) => "<c" + (index + 1) + ">LETTER</c" + (index + 1) + ">").join("\n");
|
|
95
|
+
return [
|
|
96
|
+
"You are a strict, skeptical evaluator of agent task attempts. Trust observed output — NOT the agent's narration.",
|
|
97
|
+
"**Task instruction:**\n" + problem.trim(),
|
|
98
|
+
"**Agent trajectory (" + steps.length + " agent steps):**\n" + trajectory,
|
|
99
|
+
"Score whether the agent's CURRENT state at each checkpoint would actually satisfy the task's hidden grader.",
|
|
100
|
+
"Use A through T where A = certainly NO, H-M = uncertain, N-S = leans YES, and T = essentially certain YES with matching observed verification.",
|
|
101
|
+
"Effort and confident narration are not evidence. A state without real verification should not exceed K. Scores may decrease after regressions.",
|
|
102
|
+
"The checkpoints are:\n" + checkpoints.map((step, index) => " Checkpoint " + (index + 1) + " = state right after Agent Step " + step).join("\n"),
|
|
103
|
+
"Output EXACTLY these lines and nothing else:\n" + tags
|
|
104
|
+
].join("\n\n");
|
|
105
|
+
}
|
|
106
|
+
/** Progress uses A=NO..T=YES, the reverse of pairwise success scoring. */
|
|
107
|
+
function extractProgressScore(completion, tag) {
|
|
108
|
+
return 1 - extractScore(completion, tag);
|
|
109
|
+
}
|
|
110
|
+
function bradleyTerry(rewardA, rewardB) {
|
|
111
|
+
return 1 / (1 + Math.exp(-(rewardA - rewardB)));
|
|
112
|
+
}
|
|
113
|
+
function seededRandom(seed) {
|
|
114
|
+
let state = seed >>> 0;
|
|
115
|
+
return () => {
|
|
116
|
+
state = state + 1831565813 >>> 0;
|
|
117
|
+
let value = state;
|
|
118
|
+
value = Math.imul(value ^ value >>> 15, value | 1);
|
|
119
|
+
value ^= value + Math.imul(value ^ value >>> 7, value | 61);
|
|
120
|
+
return ((value ^ value >>> 14) >>> 0) / 4294967296;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function ringCycle(count, seed = 0) {
|
|
124
|
+
if (count <= 1) return [];
|
|
125
|
+
const permutation = Array.from({ length: count }, (_, index) => index);
|
|
126
|
+
const random = seededRandom(seed);
|
|
127
|
+
for (let index = count - 1; index > 0; index -= 1) {
|
|
128
|
+
const other = Math.floor(random() * (index + 1));
|
|
129
|
+
[permutation[index], permutation[other]] = [permutation[other], permutation[index]];
|
|
130
|
+
}
|
|
131
|
+
return permutation.map((candidate, index) => [candidate, permutation[(index + 1) % count]]);
|
|
132
|
+
}
|
|
133
|
+
function pivotRoundPairs(count, pivots) {
|
|
134
|
+
const pivotSet = new Set(pivots);
|
|
135
|
+
const pairs = [];
|
|
136
|
+
for (let candidate = 0; candidate < count; candidate += 1) if (!pivotSet.has(candidate)) for (const pivot of pivots) pairs.push([candidate, pivot]);
|
|
137
|
+
const sorted = [...pivots].sort((a, b) => a - b);
|
|
138
|
+
for (let left = 0; left < sorted.length; left += 1) for (let right = left + 1; right < sorted.length; right += 1) pairs.push([sorted[left], sorted[right]]);
|
|
139
|
+
return pairs;
|
|
140
|
+
}
|
|
141
|
+
function accumulatePairs(pairs, rewards, wins, counts) {
|
|
142
|
+
for (const [a, b] of pairs) {
|
|
143
|
+
const reward = rewards.get(a + "," + b) ?? [.5, .5];
|
|
144
|
+
const probability = bradleyTerry(reward[0], reward[1]);
|
|
145
|
+
wins[a] = (wins[a] ?? 0) + probability;
|
|
146
|
+
counts[a] = (counts[a] ?? 0) + 1;
|
|
147
|
+
wins[b] = (wins[b] ?? 0) + 1 - probability;
|
|
148
|
+
counts[b] = (counts[b] ?? 0) + 1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function topPivots(wins, counts, requested) {
|
|
152
|
+
return Array.from({ length: wins.length }, (_, index) => index).sort((a, b) => (wins[b] ?? 0) / (counts[b] || 1) - (wins[a] ?? 0) / (counts[a] || 1) || a - b).slice(0, Math.min(requested, wins.length));
|
|
153
|
+
}
|
|
154
|
+
function rankScores(wins, counts) {
|
|
155
|
+
return Array.from({ length: wins.length }, (_, index) => ({
|
|
156
|
+
index,
|
|
157
|
+
score: (wins[index] ?? 0) / (counts[index] || 1)
|
|
158
|
+
})).sort((a, b) => b.score - a.score || a.index - b.index);
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, GRANULARITY, LETTERS, SCALE_DESCRIPTION, accumulatePairs, bradleyTerry, buildPairwisePrompt, buildProgressPrompt, extractProgressScore, extractScore, normalizeScoreLetter, pivotRoundPairs, rankScores, ringCycle, seededRandom, topPivots };
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=core.js.map
|