dsh-llm-verifier 0.1.2 → 0.1.4
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 +13 -6
- package/lib/caller-BXrUTAoL.js +324 -0
- package/lib/caller.js +1 -1
- package/lib/client.js +275 -114
- package/lib/index.js +301 -53
- package/lib/types/auto.d.ts +26 -0
- package/lib/types/auto.js +86 -0
- package/lib/types/client.js +16 -11
- package/lib/types/config.d.ts +15 -0
- package/lib/types/config.js +21 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +42 -10
- package/package.json +1 -1
- package/src/auto.test.ts +69 -0
- package/src/auto.ts +113 -0
- package/src/client.tsx +57 -27
- package/src/config.ts +34 -1
- package/src/index.ts +45 -12
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { a as TopLogprobCapabilityCache, i as emptyUsage, n as addUsage, r as callVerifier, t as RequestLimiter } from "./caller-
|
|
1
|
+
import { a as TopLogprobCapabilityCache, i as emptyUsage, n as addUsage, r as callVerifier, t as RequestLimiter } from "./caller-BXrUTAoL.js";
|
|
2
2
|
import { DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, GRANULARITY, LETTERS, SCALE_DESCRIPTION, accumulatePairs, bradleyTerry, buildPairwisePrompt, buildProgressPrompt, extractProgressScore, extractScore, normalizeScoreLetter, pivotRoundPairs, rankScores, ringCycle, seededRandom, topPivots } from "./core.js";
|
|
3
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
4
5
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
5
6
|
import z from "schemastery";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -10,6 +11,17 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
10
11
|
const VERIFIER_SETTINGS_NAMESPACE = settingsNamespace("llm-verifier");
|
|
11
12
|
const Config = z.object({
|
|
12
13
|
enabled: z.boolean().default(true),
|
|
14
|
+
autoVerifyMode: z.union([
|
|
15
|
+
"manual",
|
|
16
|
+
"smart",
|
|
17
|
+
"strict"
|
|
18
|
+
]).default("smart"),
|
|
19
|
+
autoVerifyThreshold: z.number().min(0).max(1).default(.65),
|
|
20
|
+
autoVerifyRepeats: z.number().step(1).min(1).default(1),
|
|
21
|
+
autoVerifyMinToolCalls: z.number().step(1).min(1).default(3),
|
|
22
|
+
autoVerifyMaxChars: z.number().step(1).min(1e3).default(8e4),
|
|
23
|
+
autoVerifyMaxPerTask: z.number().step(1).min(1).default(2),
|
|
24
|
+
autoVerifyMaxPerSession: z.number().step(1).min(1).default(8),
|
|
13
25
|
provider: z.string().default("deepseek-official"),
|
|
14
26
|
model: z.string().default("deepseek-v4-flash"),
|
|
15
27
|
reasoningEffort: z.string(),
|
|
@@ -28,7 +40,20 @@ function resolveConfig(config = {}) {
|
|
|
28
40
|
const model = (config.model ?? "deepseek-v4-flash").trim();
|
|
29
41
|
if (!provider) throw new Error("llm-verifier: provider must be non-empty");
|
|
30
42
|
if (!model) throw new Error("llm-verifier: model must be non-empty");
|
|
43
|
+
const autoVerifyMode = config.autoVerifyMode ?? "smart";
|
|
44
|
+
if (![
|
|
45
|
+
"manual",
|
|
46
|
+
"smart",
|
|
47
|
+
"strict"
|
|
48
|
+
].includes(autoVerifyMode)) throw new Error("llm-verifier: autoVerifyMode must be manual, smart, or strict");
|
|
49
|
+
const autoVerifyThreshold = config.autoVerifyThreshold ?? .65;
|
|
50
|
+
if (!Number.isFinite(autoVerifyThreshold) || autoVerifyThreshold < 0 || autoVerifyThreshold > 1) throw new Error("llm-verifier: autoVerifyThreshold must be between 0 and 1");
|
|
31
51
|
const values = {
|
|
52
|
+
autoVerifyRepeats: config.autoVerifyRepeats ?? 1,
|
|
53
|
+
autoVerifyMinToolCalls: config.autoVerifyMinToolCalls ?? 3,
|
|
54
|
+
autoVerifyMaxChars: config.autoVerifyMaxChars ?? 8e4,
|
|
55
|
+
autoVerifyMaxPerTask: config.autoVerifyMaxPerTask ?? 2,
|
|
56
|
+
autoVerifyMaxPerSession: config.autoVerifyMaxPerSession ?? 8,
|
|
32
57
|
maxTokens: config.maxTokens ?? 32768,
|
|
33
58
|
timeoutMs: config.timeoutMs ?? 3e5,
|
|
34
59
|
maxConcurrency: config.maxConcurrency ?? 8,
|
|
@@ -36,6 +61,7 @@ function resolveConfig(config = {}) {
|
|
|
36
61
|
cacheMaxEntries: config.cacheMaxEntries ?? 1e4
|
|
37
62
|
};
|
|
38
63
|
for (const [name, value] of Object.entries(values)) if (!Number.isSafeInteger(value) || value <= 0) throw new Error("llm-verifier: " + name + " must be a positive safe integer");
|
|
64
|
+
if (values.autoVerifyMaxChars < 1e3) throw new Error("llm-verifier: autoVerifyMaxChars must be at least 1000");
|
|
39
65
|
const maxRetries = config.maxRetries ?? 3;
|
|
40
66
|
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) throw new Error("llm-verifier: maxRetries must be a non-negative safe integer");
|
|
41
67
|
const cacheDir = (config.cacheDir ?? ".dsh-verifier-cache").trim();
|
|
@@ -46,6 +72,8 @@ function resolveConfig(config = {}) {
|
|
|
46
72
|
const reasoningEffort = config.reasoningEffort?.trim();
|
|
47
73
|
return {
|
|
48
74
|
enabled: config.enabled ?? true,
|
|
75
|
+
autoVerifyMode,
|
|
76
|
+
autoVerifyThreshold,
|
|
49
77
|
provider,
|
|
50
78
|
model,
|
|
51
79
|
...reasoningEffort ? { reasoningEffort } : {},
|
|
@@ -514,6 +542,167 @@ async function extractSession(agent, loadImage, options = {}) {
|
|
|
514
542
|
};
|
|
515
543
|
}
|
|
516
544
|
//#endregion
|
|
545
|
+
//#region src/auto.ts
|
|
546
|
+
const PASSIVE_TOOLS = /* @__PURE__ */ new Set([
|
|
547
|
+
"read",
|
|
548
|
+
"read_image",
|
|
549
|
+
"glob",
|
|
550
|
+
"grep",
|
|
551
|
+
"web_search",
|
|
552
|
+
"ssh_list",
|
|
553
|
+
"job_list",
|
|
554
|
+
"job_output",
|
|
555
|
+
"list_agents",
|
|
556
|
+
"get_goal",
|
|
557
|
+
"skill",
|
|
558
|
+
"mcp__codegraph__codegraph_explore"
|
|
559
|
+
]);
|
|
560
|
+
const VERIFIER_TOOLS = /* @__PURE__ */ new Set([
|
|
561
|
+
"verifier_compare",
|
|
562
|
+
"verifier_select",
|
|
563
|
+
"verifier_track",
|
|
564
|
+
"verifier_current_session"
|
|
565
|
+
]);
|
|
566
|
+
const CONSEQUENTIAL_TOOLS = /* @__PURE__ */ new Set([
|
|
567
|
+
"edit",
|
|
568
|
+
"write",
|
|
569
|
+
"pwsh",
|
|
570
|
+
"bash",
|
|
571
|
+
"run_code",
|
|
572
|
+
"codex_image_generate",
|
|
573
|
+
"ssh_exec",
|
|
574
|
+
"ssh_upload",
|
|
575
|
+
"ssh_download",
|
|
576
|
+
"ssh_tunnel",
|
|
577
|
+
"ssh_cluster",
|
|
578
|
+
"job_kill",
|
|
579
|
+
"workbench_session_delete",
|
|
580
|
+
"create_goal",
|
|
581
|
+
"update_goal"
|
|
582
|
+
]);
|
|
583
|
+
function latestDirectUserSeq(events) {
|
|
584
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
585
|
+
const event = events[index];
|
|
586
|
+
if (event?.type === "user/message" && event.data.source.kind === "user") return event.seq;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function isConsequential(name) {
|
|
590
|
+
if (CONSEQUENTIAL_TOOLS.has(name)) return true;
|
|
591
|
+
if (PASSIVE_TOOLS.has(name) || VERIFIER_TOOLS.has(name)) return false;
|
|
592
|
+
return /(?:edit|write|patch|apply|deploy|upload|delete|remove|kill|exec|shell|command|migration|database|tunnel|cluster)/iu.test(name);
|
|
593
|
+
}
|
|
594
|
+
function analyzeAutoTask(events, policy) {
|
|
595
|
+
const taskStartSeq = latestDirectUserSeq(events);
|
|
596
|
+
if (taskStartSeq === void 0) return {
|
|
597
|
+
taskStartSeq: 0,
|
|
598
|
+
toolCalls: 0,
|
|
599
|
+
completedToolResults: 0,
|
|
600
|
+
consequentialToolCalls: 0,
|
|
601
|
+
hasManualSessionVerification: false,
|
|
602
|
+
eligible: false,
|
|
603
|
+
reason: "no-direct-user-task"
|
|
604
|
+
};
|
|
605
|
+
const relevant = events.filter((event) => event.seq >= taskStartSeq);
|
|
606
|
+
const calls = relevant.filter((event) => event.type === "tool/call");
|
|
607
|
+
const toolCalls = calls.filter((event) => !VERIFIER_TOOLS.has(event.data.name)).length;
|
|
608
|
+
const completedToolResults = relevant.filter((event) => event.type === "tool/result" && event.data.error === void 0 && event.data.message.content.every((block) => block.isError !== true)).length;
|
|
609
|
+
const consequentialToolCalls = calls.filter((event) => isConsequential(event.data.name)).length;
|
|
610
|
+
const hasManualSessionVerification = calls.some((event) => event.data.name === "verifier_current_session");
|
|
611
|
+
if (policy.mode === "manual") return {
|
|
612
|
+
taskStartSeq,
|
|
613
|
+
toolCalls,
|
|
614
|
+
completedToolResults,
|
|
615
|
+
consequentialToolCalls,
|
|
616
|
+
hasManualSessionVerification,
|
|
617
|
+
eligible: false,
|
|
618
|
+
reason: "manual-mode"
|
|
619
|
+
};
|
|
620
|
+
if (hasManualSessionVerification) return {
|
|
621
|
+
taskStartSeq,
|
|
622
|
+
toolCalls,
|
|
623
|
+
completedToolResults,
|
|
624
|
+
consequentialToolCalls,
|
|
625
|
+
hasManualSessionVerification,
|
|
626
|
+
eligible: false,
|
|
627
|
+
reason: "already-verified"
|
|
628
|
+
};
|
|
629
|
+
if (consequentialToolCalls === 0) return {
|
|
630
|
+
taskStartSeq,
|
|
631
|
+
toolCalls,
|
|
632
|
+
completedToolResults,
|
|
633
|
+
consequentialToolCalls,
|
|
634
|
+
hasManualSessionVerification,
|
|
635
|
+
eligible: false,
|
|
636
|
+
reason: "no-consequential-work"
|
|
637
|
+
};
|
|
638
|
+
if (completedToolResults === 0) return {
|
|
639
|
+
taskStartSeq,
|
|
640
|
+
toolCalls,
|
|
641
|
+
completedToolResults,
|
|
642
|
+
consequentialToolCalls,
|
|
643
|
+
hasManualSessionVerification,
|
|
644
|
+
eligible: false,
|
|
645
|
+
reason: "no-completed-evidence"
|
|
646
|
+
};
|
|
647
|
+
if (policy.mode === "smart" && toolCalls < policy.minToolCalls) return {
|
|
648
|
+
taskStartSeq,
|
|
649
|
+
toolCalls,
|
|
650
|
+
completedToolResults,
|
|
651
|
+
consequentialToolCalls,
|
|
652
|
+
hasManualSessionVerification,
|
|
653
|
+
eligible: false,
|
|
654
|
+
reason: "insufficient-tool-evidence"
|
|
655
|
+
};
|
|
656
|
+
return {
|
|
657
|
+
taskStartSeq,
|
|
658
|
+
toolCalls,
|
|
659
|
+
completedToolResults,
|
|
660
|
+
consequentialToolCalls,
|
|
661
|
+
hasManualSessionVerification,
|
|
662
|
+
eligible: true,
|
|
663
|
+
reason: policy.mode + "-eligible"
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
var AutoVerificationBudget = class {
|
|
667
|
+
states = /* @__PURE__ */ new Map();
|
|
668
|
+
claim(agent, evidence, policy) {
|
|
669
|
+
if (!evidence.eligible) return false;
|
|
670
|
+
const id = String(agent.id);
|
|
671
|
+
const state = this.states.get(id) ?? {
|
|
672
|
+
taskStartSeq: evidence.taskStartSeq,
|
|
673
|
+
taskAttempts: 0,
|
|
674
|
+
sessionAttempts: 0,
|
|
675
|
+
lastEvaluatedSeq: -1
|
|
676
|
+
};
|
|
677
|
+
if (state.taskStartSeq !== evidence.taskStartSeq) {
|
|
678
|
+
state.taskStartSeq = evidence.taskStartSeq;
|
|
679
|
+
state.taskAttempts = 0;
|
|
680
|
+
state.lastEvaluatedSeq = -1;
|
|
681
|
+
}
|
|
682
|
+
const lastSeq = agent.session.events.at(-1)?.seq ?? -1;
|
|
683
|
+
if (lastSeq <= state.lastEvaluatedSeq || state.taskAttempts >= policy.maxPerTask || state.sessionAttempts >= policy.maxPerSession) {
|
|
684
|
+
this.states.set(id, state);
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
state.taskAttempts += 1;
|
|
688
|
+
state.sessionAttempts += 1;
|
|
689
|
+
state.lastEvaluatedSeq = lastSeq;
|
|
690
|
+
this.states.set(id, state);
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
release(agent) {
|
|
694
|
+
this.states.delete(String(agent.id));
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
function automaticFeedback(score, baselineScore, winner, threshold) {
|
|
698
|
+
const percent = (value) => (value * 100).toFixed(1) + "%";
|
|
699
|
+
return [
|
|
700
|
+
"[Automatic verifier gate]",
|
|
701
|
+
`The independent verifier did not clear this task for completion: evidence score ${percent(score)}, baseline ${percent(baselineScore)}, verdict ${winner}, required ${percent(threshold)}.`,
|
|
702
|
+
"Re-open the task requirements, inspect the actual tool outputs for unresolved errors or missing proof, make any necessary corrections, and run a directly relevant verification command before concluding. Do not merely restate that the task is complete."
|
|
703
|
+
].join("\n");
|
|
704
|
+
}
|
|
705
|
+
//#endregion
|
|
517
706
|
//#region src/statistics.ts
|
|
518
707
|
const VERIFIER_TOOL_NAMES = [
|
|
519
708
|
"verifier_compare",
|
|
@@ -927,6 +1116,7 @@ function rpcFailure(message) {
|
|
|
927
1116
|
};
|
|
928
1117
|
}
|
|
929
1118
|
function apply(ctx, config = {}) {
|
|
1119
|
+
const services = ctx;
|
|
930
1120
|
const entry = resolveConfig(config);
|
|
931
1121
|
let limiter = new RequestLimiter(entry.maxConcurrency);
|
|
932
1122
|
const current = installVerifierSettings(ctx, entry, () => {
|
|
@@ -936,6 +1126,7 @@ function apply(ctx, config = {}) {
|
|
|
936
1126
|
const cache = new ScoreCache(cacheFile, entry.cacheMaxEntries);
|
|
937
1127
|
const statistics = new StatisticsStore(resolveStatisticsFile(cacheFile));
|
|
938
1128
|
const topLogprobCapabilities = new TopLogprobCapabilityCache();
|
|
1129
|
+
const autoBudget = new AutoVerificationBudget();
|
|
939
1130
|
const engine = async () => {
|
|
940
1131
|
const selected = current();
|
|
941
1132
|
await ctx.llm.resolveCallConfig({
|
|
@@ -949,7 +1140,7 @@ function apply(ctx, config = {}) {
|
|
|
949
1140
|
...selected,
|
|
950
1141
|
ctx,
|
|
951
1142
|
llm: ctx.llm,
|
|
952
|
-
attachments:
|
|
1143
|
+
attachments: services.attachments,
|
|
953
1144
|
topLogprobCapabilities,
|
|
954
1145
|
limiter
|
|
955
1146
|
}, selected.maxConcurrency, cache, {
|
|
@@ -967,7 +1158,7 @@ function apply(ctx, config = {}) {
|
|
|
967
1158
|
const requireEnabled = () => {
|
|
968
1159
|
if (!current().enabled) throw new Error("llm-verifier: verifier tools are disabled — enable them in Settings → LLM Verifier");
|
|
969
1160
|
};
|
|
970
|
-
const record = async (toolName,
|
|
1161
|
+
const record = async (toolName, agent, operation) => {
|
|
971
1162
|
const startedAt = Date.now();
|
|
972
1163
|
let selected = current();
|
|
973
1164
|
try {
|
|
@@ -979,7 +1170,7 @@ function apply(ctx, config = {}) {
|
|
|
979
1170
|
};
|
|
980
1171
|
await statistics.record({
|
|
981
1172
|
toolName,
|
|
982
|
-
...
|
|
1173
|
+
...agent ? { sessionId: String(agent.id) } : {},
|
|
983
1174
|
startedAt,
|
|
984
1175
|
success: true,
|
|
985
1176
|
provider: selected.provider,
|
|
@@ -991,7 +1182,7 @@ function apply(ctx, config = {}) {
|
|
|
991
1182
|
const details = errorDetails(error);
|
|
992
1183
|
await statistics.record({
|
|
993
1184
|
toolName,
|
|
994
|
-
...
|
|
1185
|
+
...agent ? { sessionId: String(agent.id) } : {},
|
|
995
1186
|
startedAt,
|
|
996
1187
|
success: false,
|
|
997
1188
|
...details,
|
|
@@ -1002,7 +1193,45 @@ function apply(ctx, config = {}) {
|
|
|
1002
1193
|
throw error;
|
|
1003
1194
|
}
|
|
1004
1195
|
};
|
|
1005
|
-
|
|
1196
|
+
const verifySession = async (agent, options, signal) => record("verifier_current_session", agent, async () => {
|
|
1197
|
+
const extracted = await extractSession(agent, async (ref) => {
|
|
1198
|
+
const stored = await services.attachments.readImage(ref, signal);
|
|
1199
|
+
return {
|
|
1200
|
+
data: stored.data,
|
|
1201
|
+
mediaType: stored.ref.mediaType
|
|
1202
|
+
};
|
|
1203
|
+
}, {
|
|
1204
|
+
fromSeq: options.fromSeq,
|
|
1205
|
+
toSeq: options.toSeq,
|
|
1206
|
+
includeAssistantText: options.includeAssistantText,
|
|
1207
|
+
redactPatterns: options.redactPatterns,
|
|
1208
|
+
maxChars: options.maxChars
|
|
1209
|
+
});
|
|
1210
|
+
const { verifier, selected } = await engine();
|
|
1211
|
+
const compared = await verifier.compare({
|
|
1212
|
+
problem: extracted.problem,
|
|
1213
|
+
candidateA: extracted.trace,
|
|
1214
|
+
candidateB: "(No useful work or verification was performed.)",
|
|
1215
|
+
repeats: positive(options.repeats, 2, "repeats"),
|
|
1216
|
+
images: extracted.images
|
|
1217
|
+
}, signal);
|
|
1218
|
+
return {
|
|
1219
|
+
result: {
|
|
1220
|
+
sessionId: extracted.sessionId,
|
|
1221
|
+
problem: extracted.problem,
|
|
1222
|
+
score: compared.scoreA,
|
|
1223
|
+
baselineScore: compared.scoreB,
|
|
1224
|
+
winner: compared.winner,
|
|
1225
|
+
fromSeq: extracted.fromSeq,
|
|
1226
|
+
toSeq: extracted.toSeq,
|
|
1227
|
+
omittedCharacters: extracted.omittedCharacters,
|
|
1228
|
+
calls: compared.calls,
|
|
1229
|
+
stats: compared.stats
|
|
1230
|
+
},
|
|
1231
|
+
selected
|
|
1232
|
+
};
|
|
1233
|
+
});
|
|
1234
|
+
ctx.effect(() => services.connection.rpc.handle("/llm-verifier", async (endpoint, payload) => {
|
|
1006
1235
|
if (endpoint !== "statistics") return rpcFailure("unknown llm-verifier endpoint");
|
|
1007
1236
|
if (typeof payload !== "object" || payload === null) return rpcFailure("statistics payload must be an object");
|
|
1008
1237
|
const row = payload;
|
|
@@ -1019,6 +1248,55 @@ function apply(ctx, config = {}) {
|
|
|
1019
1248
|
return rpcFailure(error instanceof Error ? error.message : String(error));
|
|
1020
1249
|
}
|
|
1021
1250
|
}, { authority: "loopback" }), "llm-verifier: statistics rpc");
|
|
1251
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1252
|
+
autoBudget.release(agent);
|
|
1253
|
+
});
|
|
1254
|
+
ctx.on("agent/turn-stopping", async ({ agent, signal }) => {
|
|
1255
|
+
const selected = current();
|
|
1256
|
+
if (!selected.enabled || selected.autoVerifyMode === "manual" || signal.aborted) return;
|
|
1257
|
+
const evidence = analyzeAutoTask(agent.session.events, {
|
|
1258
|
+
mode: selected.autoVerifyMode,
|
|
1259
|
+
minToolCalls: selected.autoVerifyMinToolCalls,
|
|
1260
|
+
maxPerTask: selected.autoVerifyMaxPerTask,
|
|
1261
|
+
maxPerSession: selected.autoVerifyMaxPerSession
|
|
1262
|
+
});
|
|
1263
|
+
if (!autoBudget.claim(agent, evidence, {
|
|
1264
|
+
mode: selected.autoVerifyMode,
|
|
1265
|
+
minToolCalls: selected.autoVerifyMinToolCalls,
|
|
1266
|
+
maxPerTask: selected.autoVerifyMaxPerTask,
|
|
1267
|
+
maxPerSession: selected.autoVerifyMaxPerSession
|
|
1268
|
+
})) return;
|
|
1269
|
+
try {
|
|
1270
|
+
const result = await verifySession(agent, {
|
|
1271
|
+
fromSeq: evidence.taskStartSeq,
|
|
1272
|
+
includeAssistantText: true,
|
|
1273
|
+
maxChars: selected.autoVerifyMaxChars,
|
|
1274
|
+
repeats: selected.autoVerifyRepeats
|
|
1275
|
+
}, signal);
|
|
1276
|
+
if (!(result.winner === "A" && result.score >= selected.autoVerifyThreshold)) agent.steer(createUserMessage({
|
|
1277
|
+
content: [{
|
|
1278
|
+
type: "text",
|
|
1279
|
+
text: automaticFeedback(result.score, result.baselineScore, result.winner, selected.autoVerifyThreshold)
|
|
1280
|
+
}],
|
|
1281
|
+
source: {
|
|
1282
|
+
kind: "plugin",
|
|
1283
|
+
plugin: "dsh-llm-verifier"
|
|
1284
|
+
}
|
|
1285
|
+
}));
|
|
1286
|
+
} catch (error) {
|
|
1287
|
+
ctx.logger.warn("llm-verifier automatic session verification failed: " + (error instanceof Error ? error.message : String(error)));
|
|
1288
|
+
if (selected.autoVerifyMode === "strict" && !signal.aborted) agent.steer(createUserMessage({
|
|
1289
|
+
content: [{
|
|
1290
|
+
type: "text",
|
|
1291
|
+
text: "[Automatic verifier gate]\nThe required strict verification could not run: " + (error instanceof Error ? error.message : String(error)) + "\nInspect the task evidence and run a directly relevant verification command before concluding."
|
|
1292
|
+
}],
|
|
1293
|
+
source: {
|
|
1294
|
+
kind: "plugin",
|
|
1295
|
+
plugin: "dsh-llm-verifier"
|
|
1296
|
+
}
|
|
1297
|
+
}));
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1022
1300
|
ctx.tools.register(defineTool({
|
|
1023
1301
|
name: "verifier_compare",
|
|
1024
1302
|
description: "Use autonomously when exactly two substantive answers, patches, plans, or execution trajectories need an independent evidence-based comparison and the choice is consequential or uncertain. Do not use for trivial deterministic questions or when there is only one candidate. Uses the verifier model selected in DSH Settings, with top-logprob A–T expectations when supported and explicit-tag fallback otherwise.",
|
|
@@ -1087,7 +1365,7 @@ function apply(ctx, config = {}) {
|
|
|
1087
1365
|
timeoutMs: entry.timeoutMs * 20,
|
|
1088
1366
|
async execute(args, exec) {
|
|
1089
1367
|
requireEnabled();
|
|
1090
|
-
return record("verifier_compare", exec, async () => {
|
|
1368
|
+
return record("verifier_compare", exec.agent, async () => {
|
|
1091
1369
|
const { verifier, selected } = await engine();
|
|
1092
1370
|
return {
|
|
1093
1371
|
result: await verifier.compare({
|
|
@@ -1105,7 +1383,7 @@ function apply(ctx, config = {}) {
|
|
|
1105
1383
|
}));
|
|
1106
1384
|
ctx.tools.register(defineTool({
|
|
1107
1385
|
name: "verifier_select",
|
|
1108
|
-
description: "Use autonomously when three or more substantive candidate answers, patches, plans, or trajectories must be ranked and an independent choice is valuable. Use verifier_compare for exactly two candidates; do not generate extra candidates merely to invoke this tool.
|
|
1386
|
+
description: "Use autonomously when three or more substantive candidate answers, patches, plans, or trajectories must be ranked and an independent choice is valuable. Use verifier_compare for exactly two candidates; do not generate extra candidates merely to invoke this tool. Deterministic orchestrators should call this directly once they have three or more real candidates.",
|
|
1109
1387
|
parameters: {
|
|
1110
1388
|
problem: {
|
|
1111
1389
|
type: "string",
|
|
@@ -1175,7 +1453,7 @@ function apply(ctx, config = {}) {
|
|
|
1175
1453
|
timeoutMs: entry.timeoutMs * 100,
|
|
1176
1454
|
async execute(args, exec) {
|
|
1177
1455
|
requireEnabled();
|
|
1178
|
-
return record("verifier_select", exec, async () => {
|
|
1456
|
+
return record("verifier_select", exec.agent, async () => {
|
|
1179
1457
|
const { verifier, selected } = await engine();
|
|
1180
1458
|
return {
|
|
1181
1459
|
result: await verifier.select({
|
|
@@ -1194,7 +1472,7 @@ function apply(ctx, config = {}) {
|
|
|
1194
1472
|
}));
|
|
1195
1473
|
ctx.tools.register(defineTool({
|
|
1196
1474
|
name: "verifier_track",
|
|
1197
|
-
description: "Use autonomously for a genuinely multi-step task when progress at explicit checkpoints is uncertain or needs evidence-based measurement.
|
|
1475
|
+
description: "Use autonomously for a genuinely multi-step task when progress at explicit checkpoints is uncertain or needs evidence-based measurement. Deterministic goal/workflow orchestrators should call this directly when real checkpoints already exist. Do not use for a single completed answer or invent checkpoints.",
|
|
1198
1476
|
parameters: {
|
|
1199
1477
|
problem: {
|
|
1200
1478
|
type: "string",
|
|
@@ -1254,7 +1532,7 @@ function apply(ctx, config = {}) {
|
|
|
1254
1532
|
timeoutMs: entry.timeoutMs * 20,
|
|
1255
1533
|
async execute(args, exec) {
|
|
1256
1534
|
requireEnabled();
|
|
1257
|
-
return record("verifier_track", exec, async () => {
|
|
1535
|
+
return record("verifier_track", exec.agent, async () => {
|
|
1258
1536
|
const { verifier, selected } = await engine();
|
|
1259
1537
|
return {
|
|
1260
1538
|
result: await verifier.track(args.problem, args.steps, args.checkpoints, positive(args.repeats, 2, "repeats"), exec.signal, await images(args.images, exec.signal)),
|
|
@@ -1265,7 +1543,7 @@ function apply(ctx, config = {}) {
|
|
|
1265
1543
|
}));
|
|
1266
1544
|
ctx.tools.register(defineTool({
|
|
1267
1545
|
name: "verifier_current_session",
|
|
1268
|
-
description: "
|
|
1546
|
+
description: "Explicitly verify the current DSH session. Smart/strict policy can also invoke this gate automatically at the turn-stopping lifecycle boundary after consequential work with real tool evidence. Extracts the session, applies redaction and bounds, then sends the evidence to the configured verifier model.",
|
|
1269
1547
|
parameters: {
|
|
1270
1548
|
from_seq: { type: "integer" },
|
|
1271
1549
|
to_seq: { type: "integer" },
|
|
@@ -1342,50 +1620,20 @@ function apply(ctx, config = {}) {
|
|
|
1342
1620
|
timeoutMs: entry.timeoutMs * 20,
|
|
1343
1621
|
async execute(args, exec) {
|
|
1344
1622
|
requireEnabled();
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
fromSeq: args.from_seq,
|
|
1356
|
-
toSeq: args.to_seq,
|
|
1357
|
-
includeAssistantText: args.include_assistant_text,
|
|
1358
|
-
redactPatterns: args.redact_patterns,
|
|
1359
|
-
maxChars: args.max_chars
|
|
1360
|
-
});
|
|
1361
|
-
const { verifier, selected } = await engine();
|
|
1362
|
-
const compared = await verifier.compare({
|
|
1363
|
-
problem: extracted.problem,
|
|
1364
|
-
candidateA: extracted.trace,
|
|
1365
|
-
candidateB: "(No useful work or verification was performed.)",
|
|
1366
|
-
repeats: positive(args.repeats, 2, "repeats"),
|
|
1367
|
-
images: extracted.images
|
|
1368
|
-
}, exec.signal);
|
|
1369
|
-
return {
|
|
1370
|
-
result: {
|
|
1371
|
-
sessionId: extracted.sessionId,
|
|
1372
|
-
problem: extracted.problem,
|
|
1373
|
-
score: compared.scoreA,
|
|
1374
|
-
baselineScore: compared.scoreB,
|
|
1375
|
-
winner: compared.winner,
|
|
1376
|
-
fromSeq: extracted.fromSeq,
|
|
1377
|
-
toSeq: extracted.toSeq,
|
|
1378
|
-
omittedCharacters: extracted.omittedCharacters,
|
|
1379
|
-
calls: compared.calls,
|
|
1380
|
-
stats: compared.stats
|
|
1381
|
-
},
|
|
1382
|
-
selected
|
|
1383
|
-
};
|
|
1384
|
-
});
|
|
1623
|
+
const agent = exec.agent ?? ctx.agents.currentInitiator();
|
|
1624
|
+
if (agent === void 0) throw new Error("llm-verifier: verifier_current_session requires an agent-owned tool call");
|
|
1625
|
+
return verifySession(agent, {
|
|
1626
|
+
fromSeq: args.from_seq,
|
|
1627
|
+
toSeq: args.to_seq,
|
|
1628
|
+
includeAssistantText: args.include_assistant_text,
|
|
1629
|
+
redactPatterns: args.redact_patterns,
|
|
1630
|
+
maxChars: args.max_chars,
|
|
1631
|
+
repeats: args.repeats
|
|
1632
|
+
}, exec.signal);
|
|
1385
1633
|
}
|
|
1386
1634
|
}));
|
|
1387
1635
|
}
|
|
1388
1636
|
//#endregion
|
|
1389
|
-
export { Config, DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, GRANULARITY, LETTERS, RequestLimiter, SCALE_DESCRIPTION, ScoreCache, StatisticsStore, VERIFIER_TOOL_NAMES, VerifierEngine, accumulatePairs, apply, bradleyTerry, buildPairwisePrompt, buildProgressPrompt, callVerifier, emptyRunStats, errorDetails, extractProgressScore, extractScore, inject, name, normalizeCriteria, normalizeScoreLetter, pivotRoundPairs, rankScores, resolveCacheFile, resolveStatisticsFile, ringCycle, seededRandom, stableHash, topPivots };
|
|
1637
|
+
export { AutoVerificationBudget, Config, DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, GRANULARITY, LETTERS, RequestLimiter, SCALE_DESCRIPTION, ScoreCache, StatisticsStore, VERIFIER_TOOL_NAMES, VerifierEngine, accumulatePairs, analyzeAutoTask, apply, automaticFeedback, bradleyTerry, buildPairwisePrompt, buildProgressPrompt, callVerifier, emptyRunStats, errorDetails, extractProgressScore, extractScore, inject, name, normalizeCriteria, normalizeScoreLetter, pivotRoundPairs, rankScores, resolveCacheFile, resolveStatisticsFile, ringCycle, seededRandom, stableHash, topPivots };
|
|
1390
1638
|
|
|
1391
1639
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
2
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
3
|
+
export type AutoVerifyMode = 'manual' | 'smart' | 'strict';
|
|
4
|
+
export interface AutoVerifyPolicy {
|
|
5
|
+
mode: AutoVerifyMode;
|
|
6
|
+
minToolCalls: number;
|
|
7
|
+
maxPerTask: number;
|
|
8
|
+
maxPerSession: number;
|
|
9
|
+
}
|
|
10
|
+
export interface AutoTaskEvidence {
|
|
11
|
+
taskStartSeq: number;
|
|
12
|
+
toolCalls: number;
|
|
13
|
+
completedToolResults: number;
|
|
14
|
+
consequentialToolCalls: number;
|
|
15
|
+
hasManualSessionVerification: boolean;
|
|
16
|
+
eligible: boolean;
|
|
17
|
+
reason: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function analyzeAutoTask(events: readonly SessionEvent[], policy: AutoVerifyPolicy): AutoTaskEvidence;
|
|
20
|
+
export declare class AutoVerificationBudget {
|
|
21
|
+
private readonly states;
|
|
22
|
+
claim(agent: Agent, evidence: AutoTaskEvidence, policy: AutoVerifyPolicy): boolean;
|
|
23
|
+
release(agent: Agent): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function automaticFeedback(score: number, baselineScore: number, winner: 'A' | 'B' | 'tie', threshold: number): string;
|
|
26
|
+
//# sourceMappingURL=auto.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const PASSIVE_TOOLS = new Set([
|
|
2
|
+
'read', 'read_image', 'glob', 'grep', 'web_search', 'ssh_list', 'job_list',
|
|
3
|
+
'job_output', 'list_agents', 'get_goal', 'skill', 'mcp__codegraph__codegraph_explore',
|
|
4
|
+
]);
|
|
5
|
+
const VERIFIER_TOOLS = new Set([
|
|
6
|
+
'verifier_compare', 'verifier_select', 'verifier_track', 'verifier_current_session',
|
|
7
|
+
]);
|
|
8
|
+
const CONSEQUENTIAL_TOOLS = new Set([
|
|
9
|
+
'edit', 'write', 'pwsh', 'bash', 'run_code', 'codex_image_generate',
|
|
10
|
+
'ssh_exec', 'ssh_upload', 'ssh_download', 'ssh_tunnel', 'ssh_cluster',
|
|
11
|
+
'job_kill', 'workbench_session_delete', 'create_goal', 'update_goal',
|
|
12
|
+
]);
|
|
13
|
+
function latestDirectUserSeq(events) {
|
|
14
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
15
|
+
const event = events[index];
|
|
16
|
+
if (event?.type === 'user/message' && event.data.source.kind === 'user')
|
|
17
|
+
return event.seq;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
function isConsequential(name) {
|
|
22
|
+
if (CONSEQUENTIAL_TOOLS.has(name))
|
|
23
|
+
return true;
|
|
24
|
+
if (PASSIVE_TOOLS.has(name) || VERIFIER_TOOLS.has(name))
|
|
25
|
+
return false;
|
|
26
|
+
return /(?:edit|write|patch|apply|deploy|upload|delete|remove|kill|exec|shell|command|migration|database|tunnel|cluster)/iu.test(name);
|
|
27
|
+
}
|
|
28
|
+
export function analyzeAutoTask(events, policy) {
|
|
29
|
+
const taskStartSeq = latestDirectUserSeq(events);
|
|
30
|
+
if (taskStartSeq === undefined)
|
|
31
|
+
return { taskStartSeq: 0, toolCalls: 0, completedToolResults: 0, consequentialToolCalls: 0, hasManualSessionVerification: false, eligible: false, reason: 'no-direct-user-task' };
|
|
32
|
+
const relevant = events.filter(event => event.seq >= taskStartSeq);
|
|
33
|
+
const calls = relevant.filter((event) => event.type === 'tool/call');
|
|
34
|
+
const toolCalls = calls.filter(event => !VERIFIER_TOOLS.has(event.data.name)).length;
|
|
35
|
+
const completedToolResults = relevant.filter(event => event.type === 'tool/result' && event.data.error === undefined && event.data.message.content.every(block => block.isError !== true)).length;
|
|
36
|
+
const consequentialToolCalls = calls.filter(event => isConsequential(event.data.name)).length;
|
|
37
|
+
const hasManualSessionVerification = calls.some(event => event.data.name === 'verifier_current_session');
|
|
38
|
+
if (policy.mode === 'manual')
|
|
39
|
+
return { taskStartSeq, toolCalls, completedToolResults, consequentialToolCalls, hasManualSessionVerification, eligible: false, reason: 'manual-mode' };
|
|
40
|
+
if (hasManualSessionVerification)
|
|
41
|
+
return { taskStartSeq, toolCalls, completedToolResults, consequentialToolCalls, hasManualSessionVerification, eligible: false, reason: 'already-verified' };
|
|
42
|
+
if (consequentialToolCalls === 0)
|
|
43
|
+
return { taskStartSeq, toolCalls, completedToolResults, consequentialToolCalls, hasManualSessionVerification, eligible: false, reason: 'no-consequential-work' };
|
|
44
|
+
if (completedToolResults === 0)
|
|
45
|
+
return { taskStartSeq, toolCalls, completedToolResults, consequentialToolCalls, hasManualSessionVerification, eligible: false, reason: 'no-completed-evidence' };
|
|
46
|
+
if (policy.mode === 'smart' && toolCalls < policy.minToolCalls)
|
|
47
|
+
return { taskStartSeq, toolCalls, completedToolResults, consequentialToolCalls, hasManualSessionVerification, eligible: false, reason: 'insufficient-tool-evidence' };
|
|
48
|
+
return { taskStartSeq, toolCalls, completedToolResults, consequentialToolCalls, hasManualSessionVerification, eligible: true, reason: policy.mode + '-eligible' };
|
|
49
|
+
}
|
|
50
|
+
export class AutoVerificationBudget {
|
|
51
|
+
states = new Map();
|
|
52
|
+
claim(agent, evidence, policy) {
|
|
53
|
+
if (!evidence.eligible)
|
|
54
|
+
return false;
|
|
55
|
+
const id = String(agent.id);
|
|
56
|
+
const previous = this.states.get(id);
|
|
57
|
+
const state = previous ?? { taskStartSeq: evidence.taskStartSeq, taskAttempts: 0, sessionAttempts: 0, lastEvaluatedSeq: -1 };
|
|
58
|
+
if (state.taskStartSeq !== evidence.taskStartSeq) {
|
|
59
|
+
state.taskStartSeq = evidence.taskStartSeq;
|
|
60
|
+
state.taskAttempts = 0;
|
|
61
|
+
state.lastEvaluatedSeq = -1;
|
|
62
|
+
}
|
|
63
|
+
const lastSeq = agent.session.events.at(-1)?.seq ?? -1;
|
|
64
|
+
if (lastSeq <= state.lastEvaluatedSeq || state.taskAttempts >= policy.maxPerTask || state.sessionAttempts >= policy.maxPerSession) {
|
|
65
|
+
this.states.set(id, state);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
state.taskAttempts += 1;
|
|
69
|
+
state.sessionAttempts += 1;
|
|
70
|
+
state.lastEvaluatedSeq = lastSeq;
|
|
71
|
+
this.states.set(id, state);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
release(agent) {
|
|
75
|
+
this.states.delete(String(agent.id));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function automaticFeedback(score, baselineScore, winner, threshold) {
|
|
79
|
+
const percent = (value) => (value * 100).toFixed(1) + '%';
|
|
80
|
+
return [
|
|
81
|
+
'[Automatic verifier gate]',
|
|
82
|
+
`The independent verifier did not clear this task for completion: evidence score ${percent(score)}, baseline ${percent(baselineScore)}, verdict ${winner}, required ${percent(threshold)}.`,
|
|
83
|
+
'Re-open the task requirements, inspect the actual tool outputs for unresolved errors or missing proof, make any necessary corrections, and run a directly relevant verification command before concluding. Do not merely restate that the task is complete.',
|
|
84
|
+
].join('\n');
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=auto.js.map
|