dsh-llm-verifier 0.3.0 → 0.4.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/README.md +133 -9
- package/lib/auto-B5V49e-A.js +1517 -0
- package/lib/auto-B5V49e-A.js.map +1 -0
- package/lib/{caller-DJd_FlrQ.js → caller-DR_C7ueP.js} +154 -20
- package/lib/caller-DR_C7ueP.js.map +1 -0
- package/lib/caller.js +2 -2
- package/lib/client.js +804 -29
- package/lib/client.js.map +1 -1
- package/lib/core.js +226 -3
- package/lib/core.js.map +1 -1
- package/lib/index.js +1014 -1337
- package/lib/index.js.map +1 -1
- package/lib/replay.js +157 -0
- package/lib/replay.js.map +1 -0
- package/lib/types/auto.js +1 -1
- package/lib/types/auto.js.map +1 -1
- package/lib/types/caller.d.ts +65 -0
- package/lib/types/caller.d.ts.map +1 -1
- package/lib/types/caller.js +113 -5
- package/lib/types/caller.js.map +1 -1
- package/lib/types/client-i18n.d.ts +163 -6
- package/lib/types/client-i18n.d.ts.map +1 -1
- package/lib/types/client-i18n.js +137 -26
- package/lib/types/client-i18n.js.map +1 -1
- package/lib/types/client.d.ts +30 -0
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/client.js +94 -9
- package/lib/types/client.js.map +1 -1
- package/lib/types/config.d.ts +33 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +17 -2
- package/lib/types/config.js.map +1 -1
- package/lib/types/core.d.ts +84 -0
- package/lib/types/core.d.ts.map +1 -1
- package/lib/types/core.js +222 -2
- package/lib/types/core.js.map +1 -1
- package/lib/types/criteria.d.ts +33 -0
- package/lib/types/criteria.d.ts.map +1 -0
- package/lib/types/criteria.js +67 -0
- package/lib/types/criteria.js.map +1 -0
- package/lib/types/decisions.d.ts +84 -0
- package/lib/types/decisions.d.ts.map +1 -0
- package/lib/types/decisions.js +209 -0
- package/lib/types/decisions.js.map +1 -0
- package/lib/types/engine.d.ts +54 -1
- package/lib/types/engine.d.ts.map +1 -1
- package/lib/types/engine.js +175 -12
- package/lib/types/engine.js.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +392 -38
- package/lib/types/index.js.map +1 -1
- package/lib/types/replay.d.ts +88 -0
- package/lib/types/replay.d.ts.map +1 -0
- package/lib/types/replay.js +136 -0
- package/lib/types/replay.js.map +1 -0
- package/lib/types/router.d.ts +29 -5
- package/lib/types/router.d.ts.map +1 -1
- package/lib/types/router.js +283 -46
- package/lib/types/router.js.map +1 -1
- package/lib/types/statistics.d.ts +10 -1
- package/lib/types/statistics.d.ts.map +1 -1
- package/lib/types/statistics.js +35 -7
- package/lib/types/statistics.js.map +1 -1
- package/lib/types/top-logprobs.d.ts +36 -0
- package/lib/types/top-logprobs.d.ts.map +1 -1
- package/lib/types/top-logprobs.js +41 -14
- package/lib/types/top-logprobs.js.map +1 -1
- package/package.json +6 -1
- package/scripts/eval-replay.mjs +118 -0
- package/src/auto.ts +1 -1
- package/src/caller.test.ts +62 -1
- package/src/caller.ts +124 -4
- package/src/client-i18n.ts +137 -25
- package/src/client.test.ts +21 -0
- package/src/client.tsx +145 -7
- package/src/config.test.ts +32 -0
- package/src/config.ts +49 -2
- package/src/core.test.ts +95 -0
- package/src/core.ts +217 -2
- package/src/criteria.test.ts +60 -0
- package/src/criteria.ts +76 -0
- package/src/decisions.test.ts +150 -0
- package/src/decisions.ts +244 -0
- package/src/engine.test.ts +131 -0
- package/src/engine.ts +167 -10
- package/src/index.test.ts +278 -8
- package/src/index.ts +373 -37
- package/src/replay.test.ts +82 -0
- package/src/replay.ts +155 -0
- package/src/router.test.ts +240 -1
- package/src/router.ts +298 -48
- package/src/statistics.test.ts +49 -1
- package/src/statistics.ts +44 -7
- package/src/top-logprobs.test.ts +28 -0
- package/src/top-logprobs.ts +56 -8
- package/lib/caller-DJd_FlrQ.js.map +0 -1
package/src/caller.test.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import { RequestLimiter, callVerifier } from './caller.ts'
|
|
2
|
+
import { RequestLimiter, callVerifier, generateCandidate, generationClient } from './caller.ts'
|
|
3
3
|
import { TopLogprobCapabilityCache } from './top-logprobs.ts'
|
|
4
4
|
|
|
5
5
|
function chunks(text = '<score_A> A </score_A>') { return [{ type: 'block-start', index: 0, blockType: 'text' }, { type: 'text-delta', index: 0, text }, { type: 'block-end', index: 0, block: { type: 'text', text } }, { type: 'usage', usage: { inputTokens: 7, cacheReadTokens: 3, outputTokens: 4, reasoningTokens: 2 } }, { type: 'finish', reason: { kind: 'stop' } }] as any[] }
|
|
6
|
+
/** The same stream, but the model stopped because it ran into the output ceiling. */
|
|
7
|
+
function truncatedChunks(text = 'a draft that ran out of room') { const value = chunks(text); value[value.length - 1] = { type: 'finish', reason: { kind: 'max-tokens' } }; return value }
|
|
6
8
|
function ctx(settingsValue?: unknown) { return { get(name: string) { if (name === 'settings' && settingsValue !== undefined) return { get: () => settingsValue }; if (name === 'credentials') return { resolve: async () => ({ value: 'secret' }) }; return undefined } } as any }
|
|
7
9
|
function config(stream: (options: any) => AsyncIterable<any>, saveImage = vi.fn(), context = ctx(), overrides: Record<string, any> = {}) { return { ctx: context, llm: { stream } as any, attachments: { saveImage } as any, topLogprobCapabilities: new TopLogprobCapabilityCache(), provider: 'openai', model: 'gpt-5', reasoningEffort: 'high', maxTokens: 100, temperature: 0.2, timeoutMs: 1000, maxRetries: 2, retryBaseDelayMs: 1, ...overrides } }
|
|
8
10
|
async function* streamOf(items: any[]) { for (const item of items) yield item }
|
|
@@ -243,3 +245,62 @@ describe('imageRefs eviction on rejection (FIX 3)', () => {
|
|
|
243
245
|
expect(saveImage).toHaveBeenCalledTimes(2)
|
|
244
246
|
})
|
|
245
247
|
})
|
|
248
|
+
|
|
249
|
+
describe('best-of-N generation seam', () => {
|
|
250
|
+
it('re-points a judge client at the session model and forces diversity sampling', () => {
|
|
251
|
+
const base = config(async function* () { yield* streamOf(chunks()) }, vi.fn(), ctx(), { reasoningEffort: 'high', temperature: 0.2, maxTokens: 100 })
|
|
252
|
+
const target = generationClient(base, { provider: 'session-p', model: 'session-m' })
|
|
253
|
+
expect(target.provider).toBe('session-p')
|
|
254
|
+
expect(target.model).toBe('session-m')
|
|
255
|
+
expect(target.temperature).toBe(1)
|
|
256
|
+
expect(target.maxTokens).toBe(16384)
|
|
257
|
+
// The base is a judge: its reasoning effort must NOT leak into the drafts, because the
|
|
258
|
+
// session model's own effort is the one the drafts have to be produced with.
|
|
259
|
+
expect(target.reasoningEffort).toBeUndefined()
|
|
260
|
+
expect(target.limiter).toBeUndefined()
|
|
261
|
+
// Transport identity is inherited, so generation uses the same timeout and retry budget.
|
|
262
|
+
expect(target.timeoutMs).toBe(base.timeoutMs)
|
|
263
|
+
expect(target.maxRetries).toBe(base.maxRetries)
|
|
264
|
+
expect(target.retryBaseDelayMs).toBe(base.retryBaseDelayMs)
|
|
265
|
+
expect(generationClient(base, { provider: 'p', model: 'm', reasoningEffort: 'low' }).reasoningEffort).toBe('low')
|
|
266
|
+
// A caller can override the ceiling for one attempt, which is what makes it testable.
|
|
267
|
+
expect(generationClient(base, { provider: 'p', model: 'm' }, 4096).maxTokens).toBe(4096)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('returns plain text, complete, without asking for or parsing an A-T verdict', async () => {
|
|
271
|
+
let seen: any
|
|
272
|
+
const base = config(async function* (options: any) { seen = options; yield* streamOf(chunks('a complete draft, with no score tags at all')) })
|
|
273
|
+
const result = await generateCandidate(base, { provider: 'p', model: 'm' }, 'draft this')
|
|
274
|
+
expect(result.text).toBe('a complete draft, with no score tags at all')
|
|
275
|
+
expect(result.truncated).toBe(false)
|
|
276
|
+
expect(result.usage.inputTokens).toBe(7)
|
|
277
|
+
// No probability channel is involved: generation is not a scored call.
|
|
278
|
+
expect(result.scoringMode).toBe('explicit-tag')
|
|
279
|
+
expect(result.tokens).toEqual([])
|
|
280
|
+
expect(seen.temperature).toBe(1)
|
|
281
|
+
expect(seen.maxTokens).toBe(16384)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('keeps a truncated draft instead of discarding a paid generation', async () => {
|
|
285
|
+
// Regression from the first real end-to-end acceptance: all three drafts of a
|
|
286
|
+
// "function + 12 test cases + a note" task hit the ceiling, callExplicitTag's max-tokens error
|
|
287
|
+
// (correct for a judge) propagated through the generator, and the tool returned zero candidates.
|
|
288
|
+
const ceilings: Array<number | undefined> = []
|
|
289
|
+
const base = config(async function* (options: any) { ceilings.push(options.maxTokens); yield* streamOf(truncatedChunks('a draft that ran out of room')) })
|
|
290
|
+
const result = await generateCandidate(base, { provider: 'p', model: 'm' }, 'draft this')
|
|
291
|
+
expect(result.truncated).toBe(true)
|
|
292
|
+
expect(result.text).toBe('a draft that ran out of room')
|
|
293
|
+
expect(ceilings).toEqual([16384])
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('keeps the judge path fail-closed on a truncated answer', async () => {
|
|
297
|
+
// The tolerance above must not leak into scoring: a truncated verdict has no usable tags.
|
|
298
|
+
const judge = config(async function* () { yield* streamOf(truncatedChunks('<score_A> A </score_A>')) }, vi.fn(), ctx(), { maxRetries: 0 })
|
|
299
|
+
await expect(callVerifier(judge, 'prompt')).rejects.toThrow(/max tokens/u)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('fails closed on an empty draft instead of returning one', async () => {
|
|
303
|
+
const base = config(async function* () { yield* streamOf(chunks(' ')) }, vi.fn(), ctx(), { maxRetries: 0 })
|
|
304
|
+
await expect(generateCandidate(base, { provider: 'p', model: 'm' }, 'draft this')).rejects.toThrow(/produced no text/u)
|
|
305
|
+
})
|
|
306
|
+
})
|
package/src/caller.ts
CHANGED
|
@@ -117,7 +117,23 @@ async function retrying<T>(config: VerifierClientConfig, signal: AbortSignal | u
|
|
|
117
117
|
/** One attachment per image object per process: retries must not duplicate attachments. */
|
|
118
118
|
const imageRefs = new WeakMap<VerifierImage, Promise<unknown>>()
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
/**
|
|
121
|
+
* One plain-text completion through the DSH stream.
|
|
122
|
+
*
|
|
123
|
+
* Shared by the explicit-tag judge path and by best-of-N generation, which differ in exactly one
|
|
124
|
+
* place: a `max-tokens` finish. A truncated JUDGE answer is unusable — its verdict tags may never
|
|
125
|
+
* have been emitted — so it stays a hard error. A truncated DRAFT is still a candidate the operator
|
|
126
|
+
* already paid for, and the judge can see that the text stops mid-sentence and score it accordingly,
|
|
127
|
+
* so generation receives the text plus a `truncated` flag.
|
|
128
|
+
* @param config - resolved verifier client configuration.
|
|
129
|
+
* @param prompt - the rendered prompt.
|
|
130
|
+
* @param signal - the attempt's deadline signal.
|
|
131
|
+
* @param images - attachments to send, if any.
|
|
132
|
+
* @param attempt - 1-based attempt number for usage accounting.
|
|
133
|
+
* @param tolerateTruncation - return the text of a max-tokens finish instead of throwing.
|
|
134
|
+
* @returns The completion (with `truncated`) when it produced text.
|
|
135
|
+
*/
|
|
136
|
+
async function callTextCompletion(config: VerifierClientConfig, prompt: string, signal: AbortSignal | undefined, images: readonly VerifierImage[] | undefined, attempt: number, tolerateTruncation: boolean): Promise<VerifierCompletion & { truncated: boolean }> {
|
|
121
137
|
const content: ContentBlock[] = [{ type: 'text', text: prompt }]
|
|
122
138
|
for (const image of images ?? []) {
|
|
123
139
|
let pending = imageRefs.get(image)
|
|
@@ -147,13 +163,24 @@ async function callExplicitTag(config: VerifierClientConfig, prompt: string, sig
|
|
|
147
163
|
signal,
|
|
148
164
|
})
|
|
149
165
|
for await (const chunk of config.llm.stream(options)) assembler.push(chunk)
|
|
150
|
-
|
|
151
|
-
if (
|
|
166
|
+
let truncated = false
|
|
167
|
+
if (assembler.finish.kind === 'max-tokens') {
|
|
168
|
+
if (!tolerateTruncation) throw new Error('llm-verifier: model call failed: ' + failureMessage(assembler.finish))
|
|
169
|
+
truncated = true
|
|
170
|
+
} else {
|
|
171
|
+
const failed = failureMessage(assembler.finish)
|
|
172
|
+
if (failed !== undefined) throw new Error('llm-verifier: model call failed: ' + failed)
|
|
173
|
+
}
|
|
152
174
|
const text = assembler.blocks().filter((block): block is Extract<ContentBlock, { type: 'text' }> => block.type === 'text').map(block => block.text).join('')
|
|
153
175
|
if (!text.trim()) throw new Error('llm-verifier: selected DSH model produced no text')
|
|
154
176
|
// DSH adapters expose provider-neutral text/usage but not top-logprob candidates.
|
|
155
177
|
// extractScore() therefore uses the model's explicit final A–T tags.
|
|
156
|
-
return { text, tokens: [], positions: [], scoringMode: 'explicit-tag', usage: usage(attempt, assembler.usage) }
|
|
178
|
+
return { text, tokens: [], positions: [], scoringMode: 'explicit-tag', usage: usage(attempt, assembler.usage), truncated }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Explicit-tag judge completion: fail-closed, so a truncated answer is an error and never a score. */
|
|
182
|
+
async function callExplicitTag(config: VerifierClientConfig, prompt: string, signal: AbortSignal | undefined, images: readonly VerifierImage[] | undefined, attempt: number): Promise<VerifierCompletion> {
|
|
183
|
+
return callTextCompletion(config, prompt, signal, images, attempt, false)
|
|
157
184
|
}
|
|
158
185
|
|
|
159
186
|
export class RequestLimiter {
|
|
@@ -205,6 +232,99 @@ export async function callVerifier(config: VerifierClientConfig, prompt: string,
|
|
|
205
232
|
return config.limiter === undefined ? invoke() : config.limiter.run(invoke, signal)
|
|
206
233
|
}
|
|
207
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Best-of-N generation sampling constants.
|
|
237
|
+
*
|
|
238
|
+
* Deliberately NOT the judge's temperature: best-of-N only pays off when the drafts actually
|
|
239
|
+
* differ, and the judge's low default (0.2) would produce N near-copies — the tool would then
|
|
240
|
+
* spend N times the money choosing between the same answer.
|
|
241
|
+
*/
|
|
242
|
+
export const GENERATION_TEMPERATURE = 1
|
|
243
|
+
/**
|
|
244
|
+
* Output ceiling for one draft.
|
|
245
|
+
*
|
|
246
|
+
* 4096 was a guess in the plan, and the first real end-to-end acceptance disproved it: a
|
|
247
|
+
* "function + 12 test cases + a note" task truncated ALL THREE drafts, so the tool returned
|
|
248
|
+
* nothing at all. The measured cause is reasoning tokens: on a trivial 2-draft run the session
|
|
249
|
+
* model (deepseek-official/deepseek-flash) spent 16363 reasoning tokens out of 17254 output
|
|
250
|
+
* tokens — roughly 8k of reasoning PER DRAFT, which eats any 4096 budget before the answer starts.
|
|
251
|
+
*
|
|
252
|
+
* 16384 is about 2x that measured per-draft spend, and it is also the largest ceiling that keeps
|
|
253
|
+
* a pairwise judge prompt inside the plugin's own evidence limit: two drafts at 16384 tokens are
|
|
254
|
+
* roughly 130k characters against a 240k explicit-evidence ceiling (EXPLICIT_MAX_TOTAL_CHARS).
|
|
255
|
+
* `maxTokens` is a ceiling rather than a reservation, so a draft that needs less room costs
|
|
256
|
+
* exactly what it did before. A task whose answer genuinely needs more is too large for this tool,
|
|
257
|
+
* and its truncation is REPORTED instead of being silently returned.
|
|
258
|
+
*/
|
|
259
|
+
export const GENERATION_MAX_TOKENS = 16384
|
|
260
|
+
|
|
261
|
+
export interface GenerationTarget {
|
|
262
|
+
provider: string
|
|
263
|
+
model: string
|
|
264
|
+
reasoningEffort?: string
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** One draft: plain text, never scored, plus whether it ran into the output ceiling. */
|
|
268
|
+
export interface GeneratedCandidate extends VerifierCompletion { truncated: boolean }
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Re-point an existing verifier client at the model that should draft the candidates.
|
|
272
|
+
*
|
|
273
|
+
* Everything transport-shaped (context, llm runtime, attachments, capability memory, limiter,
|
|
274
|
+
* timeout, retry budget) is inherited; only the route and the two generation-specific scalars
|
|
275
|
+
* are replaced. `reasoningEffort` comes from the target and is never inherited from the base,
|
|
276
|
+
* because the base is a judge: a leftover judge effort would silently draft with the wrong model
|
|
277
|
+
* settings.
|
|
278
|
+
* @param base - any configured verifier client (the primary judge is the cheapest source).
|
|
279
|
+
* @param target - the model that writes the drafts.
|
|
280
|
+
* @param maxTokens - output ceiling for this attempt.
|
|
281
|
+
* @returns A client config safe to pass to {@link callGeneratedText}.
|
|
282
|
+
*/
|
|
283
|
+
export function generationClient(base: VerifierClientConfig, target: GenerationTarget, maxTokens = GENERATION_MAX_TOKENS): VerifierClientConfig {
|
|
284
|
+
return {
|
|
285
|
+
ctx: base.ctx,
|
|
286
|
+
llm: base.llm,
|
|
287
|
+
attachments: base.attachments,
|
|
288
|
+
topLogprobCapabilities: base.topLogprobCapabilities,
|
|
289
|
+
provider: target.provider,
|
|
290
|
+
model: target.model,
|
|
291
|
+
label: target.provider + '/' + target.model,
|
|
292
|
+
...(target.reasoningEffort === undefined ? {} : { reasoningEffort: target.reasoningEffort }),
|
|
293
|
+
maxTokens,
|
|
294
|
+
temperature: GENERATION_TEMPERATURE,
|
|
295
|
+
timeoutMs: base.timeoutMs,
|
|
296
|
+
maxRetries: base.maxRetries,
|
|
297
|
+
retryBaseDelayMs: base.retryBaseDelayMs,
|
|
298
|
+
...(base.limiter === undefined ? {} : { limiter: base.limiter }),
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* One best-of-N draft.
|
|
304
|
+
*
|
|
305
|
+
* A named seam over the plain-text path: generation has no A–T contract and no scoring channel, and
|
|
306
|
+
* the name keeps it distinct from the judge calls at every call site and in the decision snapshot.
|
|
307
|
+
*
|
|
308
|
+
* Hitting the output ceiling is deliberately NOT an error here. The operator already paid for those
|
|
309
|
+
* tokens, and the judge can see for itself that the text stops mid-sentence. That is exactly why the
|
|
310
|
+
* judge's fail-closed max-tokens handling was split out of this path: before the split, one long
|
|
311
|
+
* task made the whole tool return zero candidates.
|
|
312
|
+
* @param base - any configured verifier client (the primary judge is the cheapest source).
|
|
313
|
+
* @param target - the model that writes the draft (normally the session model).
|
|
314
|
+
* @param prompt - the rendered drafting prompt.
|
|
315
|
+
* @param signal - caller's abort signal.
|
|
316
|
+
* @returns The draft text, token usage, and whether it ran into the ceiling.
|
|
317
|
+
*/
|
|
318
|
+
export async function generateCandidate(base: VerifierClientConfig, target: GenerationTarget, prompt: string, signal?: AbortSignal): Promise<GeneratedCandidate> {
|
|
319
|
+
return callGeneratedText(generationClient(base, target), prompt, signal)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Generation transport: the judge's timeout/retry/limiter wrappers, with truncation as a result. */
|
|
323
|
+
async function callGeneratedText(config: VerifierClientConfig, prompt: string, signal: AbortSignal | undefined): Promise<GeneratedCandidate> {
|
|
324
|
+
const invoke = () => retrying(config, signal, (attemptSignal, attempt) => callTextCompletion(config, prompt, attemptSignal, undefined, attempt, true))
|
|
325
|
+
return config.limiter === undefined ? invoke() : config.limiter.run(invoke, signal)
|
|
326
|
+
}
|
|
327
|
+
|
|
208
328
|
/** Plain-text verifier call for conservative JSON routing; probability labels are intentionally bypassed. */
|
|
209
329
|
export async function callVerifierText(config: VerifierClientConfig, prompt: string, signal?: AbortSignal): Promise<VerifierCompletion> {
|
|
210
330
|
const invoke = () => retrying(config, signal, (attemptSignal, attempt) => callExplicitTag(config, prompt, attemptSignal, undefined, attempt))
|
package/src/client-i18n.ts
CHANGED
|
@@ -13,7 +13,7 @@ export const zh = {
|
|
|
13
13
|
'field.enabled.title': '启用 Verifier 工具',
|
|
14
14
|
'field.enabled.helpOn': '允许 Agent 调用四个 verifier 工具并向裁判模型发起请求。',
|
|
15
15
|
'field.enabled.helpOff': '停用后,所有 verifier 工具都会立即返回错误。',
|
|
16
|
-
'field.enabled.warnDisabled': 'verifier_compare、verifier_select、verifier_track 和 verifier_current_session 当前不可用。',
|
|
16
|
+
'field.enabled.warnDisabled': 'verifier_compare、verifier_select、verifier_track、verifier_best_of_n 和 verifier_current_session 当前不可用。',
|
|
17
17
|
|
|
18
18
|
// Section: Automatic Verification
|
|
19
19
|
'section.autoVerify': '自动验收',
|
|
@@ -22,6 +22,20 @@ export const zh = {
|
|
|
22
22
|
'field.autoVerifyMode.manual': '手动',
|
|
23
23
|
'field.autoVerifyMode.smart': '智能(推荐)',
|
|
24
24
|
'field.autoVerifyMode.strict': '严格',
|
|
25
|
+
'field.criteriaPreset.title': '验收判据',
|
|
26
|
+
'field.criteriaPreset.help': '自动门控(最终验收、自动 compare/select/track)与未显式传 criteria 的工具所采用的评分标准。默认「编码」与历史内置判据逐字相同,切换会改变验收松紧与缓存键。',
|
|
27
|
+
'field.criteriaPreset.coding': '编码(默认)',
|
|
28
|
+
'field.criteriaPreset.debug': '调试排错',
|
|
29
|
+
'field.criteriaPreset.research': '研究问答',
|
|
30
|
+
'field.criteriaPreset.ops': '运维操作',
|
|
31
|
+
'field.criteriaPreset.writing': '文档写作',
|
|
32
|
+
'field.criteriaPreset.custom': '自定义文件',
|
|
33
|
+
'field.criteriaPreset.previewSummary': '预览判官提示词({count} 条判据中的第 1 条)',
|
|
34
|
+
'field.criteriaPreset.sampleTask': '(示例任务)修复登录接口返回 500 的问题。',
|
|
35
|
+
'field.criteriaPreset.sampleA': '(示例轨迹 A)运行 pytest -k login,输出 1 failed … 修改 auth.py … 再次运行,输出 1 passed。',
|
|
36
|
+
'field.criteriaPreset.sampleB': '(示例轨迹 B)已经改好了,应该没问题。',
|
|
37
|
+
'field.criteriaFile.title': '判据文件',
|
|
38
|
+
'field.criteriaFile.help': 'Markdown 判据文件路径:## Criteria 下每个 ### 判据名 一条判据,可选 {#id} 锚定 id,可选 ## Ground Truth Note。文件缺失或解析失败时退回「编码」判据并在日志中告警,不会让验收失效。注意:预算告警按 3 条判据估算,自定义文件条数更多时请按比例调高「任务/会话模型调用预算」,否则可能在验收中途耗尽。',
|
|
25
39
|
'field.autoRouteSemantic.title': '混合语义路由',
|
|
26
40
|
'field.autoRouteSemantic.help': '结构化候选或检查点不足时,由裁判模型保守识别真实的 compare/select/track 对象。智能模式仅在有候选线索时运行;严格模式每次结束边界都会检查。',
|
|
27
41
|
'field.autoRouteMinConfidence.title': '语义路由置信度',
|
|
@@ -29,11 +43,11 @@ export const zh = {
|
|
|
29
43
|
'field.autoRouteMaxCandidates.title': '最多候选数',
|
|
30
44
|
'field.autoRouteMaxCandidates.help': '一次自动 select 最多纳入的真实候选数量,至少 3。',
|
|
31
45
|
'field.autoRouteMaxPerTask.title': '每任务最多路由',
|
|
32
|
-
'field.autoRouteMaxPerTask.help': '自动路由(compare/select/track
|
|
46
|
+
'field.autoRouteMaxPerTask.help': '自动路由(compare/select/track、语义分类、团队任务闸与计划预审)的任务尝试上限;与「每任务最多验收」各自独立计数,路由无论跑多少次都不会占用最终验收的额度。',
|
|
33
47
|
'field.autoRouteMaxPerSession.title': '每会话最多路由',
|
|
34
|
-
'field.autoRouteMaxPerSession.help': '
|
|
48
|
+
'field.autoRouteMaxPerSession.help': '同一会话中自动路由的尝试上限;与「每会话最多验收」各自独立计数。',
|
|
35
49
|
'field.autoTrackCompletionThreshold.title': '进度完成阈值',
|
|
36
|
-
'field.autoTrackCompletionThreshold.help': 'track 最新检查点低于该值时 steering 要求 Agent 继续执行;更早的检查点只作展示,范围 0–1
|
|
50
|
+
'field.autoTrackCompletionThreshold.help': 'track 最新检查点低于该值时 steering 要求 Agent 继续执行;更早的检查点只作展示,范围 0–1。默认 0.684 = 标尺上 N(“偏成功”)的起点,与判官提示词的档位描述自洽。',
|
|
37
51
|
'field.autoRouteMaxItemChars.title': '单项证据字符上限',
|
|
38
52
|
'field.autoRouteMaxItemChars.help': '每个候选或检查点在脱敏后允许发送给裁判的最大字符数。',
|
|
39
53
|
'field.autoRouteMaxInputChars.title': '路由证据总字符上限',
|
|
@@ -45,7 +59,9 @@ export const zh = {
|
|
|
45
59
|
'field.autoVerifyThreshold.title': '通过阈值',
|
|
46
60
|
'field.autoVerifyThreshold.help': '会话证据分数达到该值且胜过空工作基线才允许结束;范围 0–1。',
|
|
47
61
|
'field.autoVerifyRepeats.title': '路由评估轮次',
|
|
48
|
-
'field.autoVerifyRepeats.help': '自动路由(compare / select
|
|
62
|
+
'field.autoVerifyRepeats.help': '自动路由(compare / select)每项标准的评分重复次数;1 为低成本初筛。',
|
|
63
|
+
'field.autoTrackRepeats.title': '进度跟踪轮次',
|
|
64
|
+
'field.autoTrackRepeats.help': 'track 路由的评分重复次数并取平均。判官模型没有 logprobs 时每次只采到一个字母(A–T 每档 5.3%),取平均可压住采样噪声;默认 3。',
|
|
49
65
|
'field.autoVerifyFinalRepeats.title': '最终验收轮次',
|
|
50
66
|
'field.autoVerifyFinalRepeats.help': '最终会话验收每项标准的评分重复次数;偶数轮会交换 A/B 位置以抵消位置偏好,默认 2。',
|
|
51
67
|
'field.autoVerifyMinToolCalls.title': '智能模式最少工具调用',
|
|
@@ -53,9 +69,9 @@ export const zh = {
|
|
|
53
69
|
'field.autoVerifyMaxChars.title': '最大证据字符',
|
|
54
70
|
'field.autoVerifyMaxChars.help': '发送给裁判前保留的最近会话证据字符数。',
|
|
55
71
|
'field.autoVerifyMaxPerTask.title': '每任务最多验收',
|
|
56
|
-
'field.autoVerifyMaxPerTask.help': '
|
|
72
|
+
'field.autoVerifyMaxPerTask.help': '最终验收的任务尝试上限;与「每任务最多路由」各自独立计数,保证被路由要求过的最终验收一定有额度可用,同时防止低分反馈形成无限修复循环。',
|
|
57
73
|
'field.autoVerifyMaxPerSession.title': '每会话最多验收',
|
|
58
|
-
'field.autoVerifyMaxPerSession.help': '
|
|
74
|
+
'field.autoVerifyMaxPerSession.help': '同一会话中最终验收的尝试上限;与「每会话最多路由」各自独立计数。',
|
|
59
75
|
'field.autoVerify.warnNotice': '自动路由按 select → compare → track → current_session 的阶段顺序处理;脱敏后的任务、Assistant 轨迹与真实工具输出会发送给所选裁判模型。候选选择或进度不足会 steering 继续工作,最终验收未通过会要求修复并重新验证。',
|
|
60
76
|
'field.autoMaxModelCallsPerTask.warnBudget': '当前单任务预算为 {current} 次模型调用,低于 {judges} 位裁判完整锦标赛所需的最差情况({required} 次)。建议调大该预算以避免自动路由过早耗尽。',
|
|
61
77
|
'field.autoMaxModelCallsPerSession.warnBudget': '当前会话预算为 {current} 次模型调用,低于 {judges} 位裁判推荐的最差情况({required} 次)。建议调大该预算以避免会话验证过早耗尽。',
|
|
@@ -127,6 +143,18 @@ export const zh = {
|
|
|
127
143
|
'stats.currentSession': '当前会话',
|
|
128
144
|
'stats.allSessions': '全部会话',
|
|
129
145
|
'stats.refresh': '刷新',
|
|
146
|
+
'probe.button': '判官自检',
|
|
147
|
+
'probe.running': '检测中…',
|
|
148
|
+
'probe.title': '判官自检',
|
|
149
|
+
'probe.note': '每个判官发一次真实调用,并强制重新探测评分通道;结果不计入统计',
|
|
150
|
+
'probe.reprobed': '已重新探测',
|
|
151
|
+
'probe.rubric': '生效判据:{source}({count} 条){file}',
|
|
152
|
+
'probe.rubricFallback': '自定义判据文件不可用:{error}(本次已退回编码判据)',
|
|
153
|
+
'probe.channel': '通道 {channel}',
|
|
154
|
+
'probe.scores': 'A {a} · B {b}',
|
|
155
|
+
'probe.latency': '{ms} ms',
|
|
156
|
+
'probe.calls': '{calls} 次调用',
|
|
157
|
+
'probe.failed': '失败',
|
|
130
158
|
'stats.requestFailed': '统计接口请求失败',
|
|
131
159
|
'stats.hostRestartHint': '请确认宿主已重启并加载最新版本的 dsh-llm-verifier。',
|
|
132
160
|
'stats.loading': '正在读取工具运行统计…',
|
|
@@ -158,8 +186,10 @@ export const zh = {
|
|
|
158
186
|
'stats.verdict.criteria': '标准 {criteria}',
|
|
159
187
|
|
|
160
188
|
// Metrics
|
|
161
|
-
'metric.cacheHitRate': '
|
|
162
|
-
'metric.cacheHitNote': '{hits} 命中 / {total}
|
|
189
|
+
'metric.cacheHitRate': '评分缓存命中率',
|
|
190
|
+
'metric.cacheHitNote': '{hits} 命中 / {total} 次评分查询(本地复用,未调用模型)',
|
|
191
|
+
'metric.prefixCacheHitRate': '前缀缓存命中率',
|
|
192
|
+
'metric.prefixCacheHitNote': '输入 {cached} / {input} 命中(由模型服务计费口径统计)',
|
|
163
193
|
'metric.tokens': 'Token',
|
|
164
194
|
'metric.tokensNote': '输入 {input} · 输出 {output}',
|
|
165
195
|
'metric.avgModelCalls': '平均模型请求',
|
|
@@ -190,6 +220,22 @@ export const zh = {
|
|
|
190
220
|
'recent.title': '最近调用',
|
|
191
221
|
'recent.maxCount': '最多 50 条',
|
|
192
222
|
'recent.empty': '暂无记录',
|
|
223
|
+
'recent.decision': '决策快照',
|
|
224
|
+
'recent.decisionHide': '收起快照',
|
|
225
|
+
'recent.decisionLoading': '正在读取…',
|
|
226
|
+
'recent.decisionMissing': '找不到这次调用的快照(可能已被淘汰或从未捕获)',
|
|
227
|
+
'recent.decisionEmpty': '这次调用没有捕获到模型调用(命中缓存或已关闭捕获)',
|
|
228
|
+
'recent.decisionPrompt': '提示词',
|
|
229
|
+
'recent.decisionOutput': '原始回答',
|
|
230
|
+
'recent.details': '查看详情',
|
|
231
|
+
'recent.detail.criterion': '判据',
|
|
232
|
+
'recent.detail.score': '分数',
|
|
233
|
+
'recent.detail.threshold': '阈值',
|
|
234
|
+
'recent.detail.calls': '模型调用 {calls}',
|
|
235
|
+
'recent.detail.tokens': '输入 {input} · 缓存 {cached} · 输出 {output}',
|
|
236
|
+
'recent.detail.scoreCache': '评分缓存 {hits} 命中 / {misses} 未命中',
|
|
237
|
+
'recent.detail.prefixCache': '前缀缓存命中 {rate}',
|
|
238
|
+
'recent.detail.cost': '预估成本 {cost}',
|
|
193
239
|
|
|
194
240
|
// Model summary
|
|
195
241
|
'models.title': '模型汇总',
|
|
@@ -208,6 +254,8 @@ export const zh = {
|
|
|
208
254
|
'field.autoVerifyTeamTasks.help': '当 Agent Teams 中的任务状态变更或进入完成状态时,自动路由进度追踪与任务验收。',
|
|
209
255
|
'field.autoVerifyPlanMode.title': '计划模式自动预审',
|
|
210
256
|
'field.autoVerifyPlanMode.help': '在 Agent 调用 exit_plan_mode 提交计划交付人类评审前,自动对计划完整度与风险进行独立预审。',
|
|
257
|
+
'field.captureDecisions.title': '保存决策快照',
|
|
258
|
+
'field.captureDecisions.help': '把每次裁判调用的提示词与原始回答脱敏后限量存进本话题的 verifier/decisions-v1.json(每次调用 ≤ 12 次模型调用、单条记录 ≤ 3 万字符、按话题保留最近 40 条)。用于在统计看板里回答"裁判为什么这么判",不参与任何判定。',
|
|
211
259
|
'field.autoVerifySubagents.title': '同时验收子 Agent',
|
|
212
260
|
'field.autoVerifySubagents.help': '子 Agent(subagent / fork)的会话也执行自动路由与最终验收。默认关闭:子会话同样以真实用户消息播种,被门控会额外消耗裁判预算并可能反复 steering 子 Agent。',
|
|
213
261
|
}
|
|
@@ -227,7 +275,7 @@ export const en: I18nDict = {
|
|
|
227
275
|
'field.enabled.title': 'Enable Verifier Tools',
|
|
228
276
|
'field.enabled.helpOn': 'Allows the Agent to call verifier tools and make requests to the judge model.',
|
|
229
277
|
'field.enabled.helpOff': 'When disabled, all verifier tools will return an error immediately.',
|
|
230
|
-
'field.enabled.warnDisabled': 'verifier_compare, verifier_select, verifier_track, and verifier_current_session are currently unavailable.',
|
|
278
|
+
'field.enabled.warnDisabled': 'verifier_compare, verifier_select, verifier_track, verifier_best_of_n, and verifier_current_session are currently unavailable.',
|
|
231
279
|
|
|
232
280
|
// Section: Automatic Verification
|
|
233
281
|
'section.autoVerify': 'Automatic Verification',
|
|
@@ -236,6 +284,20 @@ export const en: I18nDict = {
|
|
|
236
284
|
'field.autoVerifyMode.manual': 'Manual',
|
|
237
285
|
'field.autoVerifyMode.smart': 'Smart (Recommended)',
|
|
238
286
|
'field.autoVerifyMode.strict': 'Strict',
|
|
287
|
+
'field.criteriaPreset.title': 'Acceptance Rubric',
|
|
288
|
+
'field.criteriaPreset.help': 'The rubric the automatic gate (final acceptance, routed compare/select/track) and any tool call without an explicit criteria argument scores with. The default "Coding" preset is the historical rubric, word for word; switching it changes acceptance strictness and the cache keys.',
|
|
289
|
+
'field.criteriaPreset.coding': 'Coding (default)',
|
|
290
|
+
'field.criteriaPreset.debug': 'Debugging',
|
|
291
|
+
'field.criteriaPreset.research': 'Research Q&A',
|
|
292
|
+
'field.criteriaPreset.ops': 'Operations',
|
|
293
|
+
'field.criteriaPreset.writing': 'Writing',
|
|
294
|
+
'field.criteriaPreset.custom': 'Custom file',
|
|
295
|
+
'field.criteriaPreset.previewSummary': 'Preview the judge prompt (criterion 1 of {count})',
|
|
296
|
+
'field.criteriaPreset.sampleTask': '(sample task) Fix the login endpoint returning 500.',
|
|
297
|
+
'field.criteriaPreset.sampleA': '(sample trajectory A) Ran pytest -k login: 1 failed … changed auth.py … ran it again: 1 passed.',
|
|
298
|
+
'field.criteriaPreset.sampleB': '(sample trajectory B) Already fixed it, should be fine.',
|
|
299
|
+
'field.criteriaFile.title': 'Criteria File',
|
|
300
|
+
'field.criteriaFile.help': 'Path to a markdown rubric: one ### Criterion Name per criterion under ## Criteria, an optional {#id} anchor, an optional ## Ground Truth Note. A missing or unparsable file falls back to the coding rubric with a logged warning instead of disabling the gate. Note: the budget warning assumes three criteria, so raise the task/session call budget proportionally when your file has more.',
|
|
239
301
|
'field.autoRouteSemantic.title': 'Hybrid Semantic Routing',
|
|
240
302
|
'field.autoRouteSemantic.help': 'When structured candidates or checkpoints are lacking, the judge model conservatively identifies real compare/select/track targets. Smart mode only runs when candidate clues exist; Strict mode checks at every task boundary.',
|
|
241
303
|
'field.autoRouteMinConfidence.title': 'Semantic Route Confidence',
|
|
@@ -243,11 +305,11 @@ export const en: I18nDict = {
|
|
|
243
305
|
'field.autoRouteMaxCandidates.title': 'Max Candidates',
|
|
244
306
|
'field.autoRouteMaxCandidates.help': 'Maximum number of real candidates included in an automatic select, at least 3.',
|
|
245
307
|
'field.autoRouteMaxPerTask.title': 'Max Routes per Task',
|
|
246
|
-
'field.autoRouteMaxPerTask.help': 'Task attempt cap for automatic routing (compare/select/track and
|
|
308
|
+
'field.autoRouteMaxPerTask.help': 'Task attempt cap for automatic routing (compare/select/track, semantic classification, team-task gates and plan pre-review); counted separately from "Max Verifications per Task", so routing can never spend the final acceptance budget.',
|
|
247
309
|
'field.autoRouteMaxPerSession.title': 'Max Routes per Session',
|
|
248
|
-
'field.autoRouteMaxPerSession.help': 'Session attempt cap for automatic routing;
|
|
310
|
+
'field.autoRouteMaxPerSession.help': 'Session attempt cap for automatic routing; counted separately from "Max Verifications per Session".',
|
|
249
311
|
'field.autoTrackCompletionThreshold.title': 'Progress Completion Threshold',
|
|
250
|
-
'field.autoTrackCompletionThreshold.help': 'When the newest track checkpoint falls below this score, steering instructs the Agent to continue; earlier checkpoints are shown only; range 0–1.',
|
|
312
|
+
'field.autoTrackCompletionThreshold.help': 'When the newest track checkpoint falls below this score, steering instructs the Agent to continue; earlier checkpoints are shown only; range 0–1. The 0.684 default is the start of band N ("leans YES") on the rating scale, so it matches what the judge prompt tells the judge.',
|
|
251
313
|
'field.autoRouteMaxItemChars.title': 'Max Chars per Route Item',
|
|
252
314
|
'field.autoRouteMaxItemChars.help': 'Maximum sanitized character count allowed per candidate or checkpoint when sent to the judge.',
|
|
253
315
|
'field.autoRouteMaxInputChars.title': 'Total Route Input Chars Cap',
|
|
@@ -257,7 +319,9 @@ export const en: I18nDict = {
|
|
|
257
319
|
'field.autoMaxModelCallsPerSession.title': 'Model Call Budget per Session',
|
|
258
320
|
'field.autoMaxModelCallsPerSession.help': 'Estimated model call cap shared across all automatic verification phases throughout the entire session.',
|
|
259
321
|
'field.autoVerifyRepeats.title': 'Routing Repeats',
|
|
260
|
-
'field.autoVerifyRepeats.help': 'Scoring repeats per criterion for automatic routing (compare / select
|
|
322
|
+
'field.autoVerifyRepeats.help': 'Scoring repeats per criterion for automatic routing (compare / select); 1 is low-cost preliminary screening.',
|
|
323
|
+
'field.autoTrackRepeats.title': 'Progress Repeats',
|
|
324
|
+
'field.autoTrackRepeats.help': 'Scoring repeats for an automatic track route, averaged. Without token logprobs each call samples ONE letter (5.3% of the A-T scale per letter), so repeats keep the progress curve from flipping bands on noise; default 3.',
|
|
261
325
|
'field.autoVerifyFinalRepeats.title': 'Final Acceptance Repeats',
|
|
262
326
|
'field.autoVerifyFinalRepeats.help': 'Scoring repeats per criterion for the final session acceptance; even rounds swap A/B positions to cancel position bias. Defaults to 2.',
|
|
263
327
|
'field.autoVerifyThreshold.title': 'Pass Threshold',
|
|
@@ -267,9 +331,9 @@ export const en: I18nDict = {
|
|
|
267
331
|
'field.autoVerifyMaxChars.title': 'Max Evidence Characters',
|
|
268
332
|
'field.autoVerifyMaxChars.help': 'Maximum recent session evidence characters retained before sending to the judge.',
|
|
269
333
|
'field.autoVerifyMaxPerTask.title': 'Max Verifications per Task',
|
|
270
|
-
'field.autoVerifyMaxPerTask.help': 'Final-acceptance attempt cap per task;
|
|
334
|
+
'field.autoVerifyMaxPerTask.help': 'Final-acceptance attempt cap per task; counted separately from "Max Routes per Task", so an armed final gate always has an attempt reserved. Prevents endless low-score repair loops.',
|
|
271
335
|
'field.autoVerifyMaxPerSession.title': 'Max Verifications per Session',
|
|
272
|
-
'field.autoVerifyMaxPerSession.help': 'Final-acceptance attempt cap within one session;
|
|
336
|
+
'field.autoVerifyMaxPerSession.help': 'Final-acceptance attempt cap within one session; counted separately from "Max Routes per Session".',
|
|
273
337
|
'field.autoVerify.warnNotice': 'Automatic routing follows the phase order: select → compare → track → current_session. Sanitized tasks, Assistant trajectories, and real tool outputs are sent to the judge model. Candidate selection or insufficient progress steers the Agent to keep working; failed final acceptance requires remediation and re-verification.',
|
|
274
338
|
'field.autoMaxModelCallsPerTask.warnBudget': 'Current task budget ({current} calls) is below the worst-case requirement ({required} calls) for {judges} judge(s) in a full tournament. Consider increasing it to avoid premature budget exhaustion.',
|
|
275
339
|
'field.autoMaxModelCallsPerSession.warnBudget': 'Current session budget ({current} calls) is below the worst-case requirement ({required} calls) for {judges} judge(s). Consider increasing it to avoid premature budget exhaustion.',
|
|
@@ -341,6 +405,18 @@ export const en: I18nDict = {
|
|
|
341
405
|
'stats.currentSession': 'Current Session',
|
|
342
406
|
'stats.allSessions': 'All Sessions',
|
|
343
407
|
'stats.refresh': 'Refresh',
|
|
408
|
+
'probe.button': 'Probe judges',
|
|
409
|
+
'probe.running': 'Probing…',
|
|
410
|
+
'probe.title': 'Judge probe',
|
|
411
|
+
'probe.note': 'One real call per judge, forcing a fresh scoring-channel probe; not counted in the statistics',
|
|
412
|
+
'probe.reprobed': 're-probed',
|
|
413
|
+
'probe.rubric': 'Rubric in effect: {source} ({count} criteria) {file}',
|
|
414
|
+
'probe.rubricFallback': 'Custom criteria file unusable: {error} (fell back to coding for this run)',
|
|
415
|
+
'probe.channel': 'channel {channel}',
|
|
416
|
+
'probe.scores': 'A {a} · B {b}',
|
|
417
|
+
'probe.latency': '{ms} ms',
|
|
418
|
+
'probe.calls': '{calls} calls',
|
|
419
|
+
'probe.failed': 'Failed',
|
|
344
420
|
'stats.requestFailed': 'Failed to query statistics API',
|
|
345
421
|
'stats.hostRestartHint': 'Please make sure the host has restarted and loaded the latest version of dsh-llm-verifier.',
|
|
346
422
|
'stats.loading': 'Loading tool execution statistics…',
|
|
@@ -372,8 +448,10 @@ export const en: I18nDict = {
|
|
|
372
448
|
'stats.verdict.criteria': 'Criteria {criteria}',
|
|
373
449
|
|
|
374
450
|
// Metrics
|
|
375
|
-
'metric.cacheHitRate': 'Cache Hit Rate',
|
|
376
|
-
'metric.cacheHitNote': '{hits} hits / {total}
|
|
451
|
+
'metric.cacheHitRate': 'Score Cache Hit Rate',
|
|
452
|
+
'metric.cacheHitNote': '{hits} hits / {total} lookups (reused locally, no model call)',
|
|
453
|
+
'metric.prefixCacheHitRate': 'Prefix Cache Hit Rate',
|
|
454
|
+
'metric.prefixCacheHitNote': '{cached} of {input} input tokens served from cache',
|
|
377
455
|
'metric.tokens': 'Tokens',
|
|
378
456
|
'metric.tokensNote': 'Input {input} · Output {output}',
|
|
379
457
|
'metric.avgModelCalls': 'Avg Model Calls',
|
|
@@ -404,6 +482,22 @@ export const en: I18nDict = {
|
|
|
404
482
|
'recent.title': 'Recent Invocations',
|
|
405
483
|
'recent.maxCount': 'Max 50',
|
|
406
484
|
'recent.empty': 'No records',
|
|
485
|
+
'recent.decision': 'Decision snapshot',
|
|
486
|
+
'recent.decisionHide': 'Hide snapshot',
|
|
487
|
+
'recent.decisionLoading': 'Loading…',
|
|
488
|
+
'recent.decisionMissing': 'No snapshot for this invocation (pruned, or never captured)',
|
|
489
|
+
'recent.decisionEmpty': 'No model calls were captured for this invocation (cache hit, or capture disabled)',
|
|
490
|
+
'recent.decisionPrompt': 'Prompt',
|
|
491
|
+
'recent.decisionOutput': 'Raw answer',
|
|
492
|
+
'recent.details': 'Details',
|
|
493
|
+
'recent.detail.criterion': 'Criterion',
|
|
494
|
+
'recent.detail.score': 'Score',
|
|
495
|
+
'recent.detail.threshold': 'Threshold',
|
|
496
|
+
'recent.detail.calls': '{calls} model calls',
|
|
497
|
+
'recent.detail.tokens': 'input {input} · cached {cached} · output {output}',
|
|
498
|
+
'recent.detail.scoreCache': 'score cache {hits} hits / {misses} misses',
|
|
499
|
+
'recent.detail.prefixCache': 'prefix cache {rate}',
|
|
500
|
+
'recent.detail.cost': 'est. {cost}',
|
|
407
501
|
|
|
408
502
|
// Model summary
|
|
409
503
|
'models.title': 'Model Summary',
|
|
@@ -422,6 +516,8 @@ export const en: I18nDict = {
|
|
|
422
516
|
'field.autoVerifyTeamTasks.help': 'Automatically route progress and verify tasks when Agent Teams task status changes or completes.',
|
|
423
517
|
'field.autoVerifyPlanMode.title': 'Plan Mode Pre-verification',
|
|
424
518
|
'field.autoVerifyPlanMode.help': 'Automatically pre-verify plan feasibility and risks before exit_plan_mode presents it for user review.',
|
|
519
|
+
'field.captureDecisions.title': 'Keep decision snapshots',
|
|
520
|
+
'field.captureDecisions.help': 'Store the redacted prompt and raw answer of every judge call in this topic (verifier/decisions-v1.json): at most 12 model calls per invocation, 30k characters per record, the newest 40 records per topic. It answers "why did the judge decide that" in the dashboard and never affects a verdict.',
|
|
425
521
|
'field.autoVerifySubagents.title': 'Verify Subagent Sessions',
|
|
426
522
|
'field.autoVerifySubagents.help': 'Also run automatic routing and final acceptance on delegated child sessions. Off by default: child sessions are seeded with a real user message, so gating them spends extra judge budget and can repeatedly steer the subagent.',
|
|
427
523
|
}
|
|
@@ -434,6 +530,7 @@ export const toolLabels: Record<'zh' | 'en', Record<string, string>> = {
|
|
|
434
530
|
verifier_compare: '两项对比',
|
|
435
531
|
verifier_select: '多项优选',
|
|
436
532
|
verifier_track: '进度跟踪',
|
|
533
|
+
verifier_best_of_n: '生成选优',
|
|
437
534
|
verifier_current_session: '会话验收',
|
|
438
535
|
},
|
|
439
536
|
en: {
|
|
@@ -441,6 +538,7 @@ export const toolLabels: Record<'zh' | 'en', Record<string, string>> = {
|
|
|
441
538
|
verifier_compare: 'Pairwise Comparison',
|
|
442
539
|
verifier_select: 'Candidate Selection',
|
|
443
540
|
verifier_track: 'Progress Tracking',
|
|
541
|
+
verifier_best_of_n: 'Best-of-N Drafting',
|
|
444
542
|
verifier_current_session: 'Session Acceptance',
|
|
445
543
|
},
|
|
446
544
|
}
|
|
@@ -578,10 +676,23 @@ export function sectionForSave(
|
|
|
578
676
|
return section
|
|
579
677
|
}
|
|
580
678
|
|
|
581
|
-
/**
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
679
|
+
/**
|
|
680
|
+
* Criteria per comparison the worst-case estimate assumes.
|
|
681
|
+
*
|
|
682
|
+
* All five built-in presets have three, which is what keeps the historical constants below
|
|
683
|
+
* unchanged. A CUSTOM rubric file can have any number, and the engine reserves budget from the
|
|
684
|
+
* real count (\`rubric.criteria.length\`), so a custom file with more criteria needs a larger
|
|
685
|
+
* budget than a warning built on three would suggest — pass the count in when it is known.
|
|
686
|
+
*/
|
|
687
|
+
export const WORST_CASE_CRITERIA_PER_COMPARISON = 3
|
|
688
|
+
/** An eight-candidate select: 18 pairs (ring + pivot rounds) x criteria, one round (the per-pair orientation removes the slot bias). */
|
|
689
|
+
export function worstCaseRouteCallsPerJudge(criteria = WORST_CASE_CRITERIA_PER_COMPARISON): number { return 18 * Math.max(1, criteria) }
|
|
690
|
+
/** Final acceptance: criteria x the default two repeats (one per A/B position). */
|
|
691
|
+
export function worstCaseFinalCallsPerJudge(criteria = WORST_CASE_CRITERIA_PER_COMPARISON, repeats = 2): number { return Math.max(1, criteria) * Math.max(1, repeats) }
|
|
692
|
+
/** @deprecated Kept at the three-criteria value; prefer {@link worstCaseRouteCallsPerJudge}. */
|
|
693
|
+
export const WORST_CASE_ROUTE_CALLS_PER_JUDGE = worstCaseRouteCallsPerJudge()
|
|
694
|
+
/** @deprecated Kept at the three-criteria value; prefer {@link worstCaseFinalCallsPerJudge}. */
|
|
695
|
+
export const WORST_CASE_FINAL_CALLS_PER_JUDGE = worstCaseFinalCallsPerJudge()
|
|
585
696
|
export const WORST_CASE_TASK_PER_JUDGE = WORST_CASE_ROUTE_CALLS_PER_JUDGE + WORST_CASE_FINAL_CALLS_PER_JUDGE
|
|
586
697
|
export const WORST_CASE_SESSION_PER_JUDGE = 160
|
|
587
698
|
|
|
@@ -589,13 +700,13 @@ export function computeJudgeCount(extraJudgesCount: number): number {
|
|
|
589
700
|
return 1 + Math.max(0, extraJudgesCount)
|
|
590
701
|
}
|
|
591
702
|
|
|
592
|
-
export function computeWorstCaseBudget(judgeCount: number): {
|
|
703
|
+
export function computeWorstCaseBudget(judgeCount: number, criteria = WORST_CASE_CRITERIA_PER_COMPARISON): {
|
|
593
704
|
worstCaseTask: number
|
|
594
705
|
worstCaseSession: number
|
|
595
706
|
} {
|
|
596
707
|
const count = Math.max(1, judgeCount)
|
|
597
708
|
return {
|
|
598
|
-
worstCaseTask: count *
|
|
709
|
+
worstCaseTask: count * (worstCaseRouteCallsPerJudge(criteria) + worstCaseFinalCallsPerJudge(criteria)),
|
|
599
710
|
worstCaseSession: count * WORST_CASE_SESSION_PER_JUDGE,
|
|
600
711
|
}
|
|
601
712
|
}
|
|
@@ -613,10 +724,11 @@ export function evaluateBudgetWarning(
|
|
|
613
724
|
extraJudgesCount: number,
|
|
614
725
|
autoMaxModelCallsPerTask: number,
|
|
615
726
|
autoMaxModelCallsPerSession: number,
|
|
727
|
+
criteria = WORST_CASE_CRITERIA_PER_COMPARISON,
|
|
616
728
|
): BudgetWarningState | null {
|
|
617
729
|
if (autoVerifyMode === 'manual') return null
|
|
618
730
|
const judgeCount = computeJudgeCount(extraJudgesCount)
|
|
619
|
-
const { worstCaseTask, worstCaseSession } = computeWorstCaseBudget(judgeCount)
|
|
731
|
+
const { worstCaseTask, worstCaseSession } = computeWorstCaseBudget(judgeCount, criteria)
|
|
620
732
|
const warnTask = autoMaxModelCallsPerTask < worstCaseTask
|
|
621
733
|
const warnSession = autoMaxModelCallsPerSession < worstCaseSession
|
|
622
734
|
if (!warnTask && !warnSession) return null
|
package/src/client.test.ts
CHANGED
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
WORST_CASE_FINAL_CALLS_PER_JUDGE,
|
|
9
9
|
WORST_CASE_TASK_PER_JUDGE,
|
|
10
10
|
WORST_CASE_SESSION_PER_JUDGE,
|
|
11
|
+
WORST_CASE_CRITERIA_PER_COMPARISON,
|
|
12
|
+
worstCaseRouteCallsPerJudge,
|
|
13
|
+
worstCaseFinalCallsPerJudge,
|
|
11
14
|
computeJudgeCount,
|
|
12
15
|
computeWorstCaseBudget,
|
|
13
16
|
evaluateBudgetWarning,
|
|
@@ -52,6 +55,7 @@ describe('client i18n dictionaries', () => {
|
|
|
52
55
|
'verifier_compare',
|
|
53
56
|
'verifier_select',
|
|
54
57
|
'verifier_track',
|
|
58
|
+
'verifier_best_of_n',
|
|
55
59
|
'verifier_current_session',
|
|
56
60
|
]
|
|
57
61
|
|
|
@@ -475,6 +479,23 @@ describe('budget worst case and sanity warnings', () => {
|
|
|
475
479
|
expect(computeWorstCaseBudget(3)).toEqual({ worstCaseTask: 180, worstCaseSession: 480 })
|
|
476
480
|
})
|
|
477
481
|
|
|
482
|
+
it('scales the route estimate with the criteria count the engine actually reserves for', () => {
|
|
483
|
+
// All built-in presets have three criteria, which is why the historical constants still hold.
|
|
484
|
+
expect(WORST_CASE_CRITERIA_PER_COMPARISON).toBe(3)
|
|
485
|
+
expect(worstCaseRouteCallsPerJudge()).toBe(WORST_CASE_ROUTE_CALLS_PER_JUDGE)
|
|
486
|
+
expect(worstCaseFinalCallsPerJudge()).toBe(WORST_CASE_FINAL_CALLS_PER_JUDGE)
|
|
487
|
+
// A custom rubric file with more criteria costs proportionally more.
|
|
488
|
+
expect(worstCaseRouteCallsPerJudge(6)).toBe(108)
|
|
489
|
+
expect(worstCaseFinalCallsPerJudge(6)).toBe(12)
|
|
490
|
+
expect(computeWorstCaseBudget(1, 6)).toEqual({ worstCaseTask: 120, worstCaseSession: 160 })
|
|
491
|
+
// Degenerate counts are clamped, never zero or negative.
|
|
492
|
+
expect(worstCaseRouteCallsPerJudge(0)).toBe(18)
|
|
493
|
+
expect(worstCaseFinalCallsPerJudge(0)).toBe(2)
|
|
494
|
+
// The warning follows: the shipped 96/240 clears three criteria but not six.
|
|
495
|
+
expect(evaluateBudgetWarning('smart', 0, 96, 240, 3)).toBeNull()
|
|
496
|
+
expect(evaluateBudgetWarning('smart', 0, 96, 240, 6)).toMatchObject({ warnTask: true, warnSession: false, worstCaseTask: 120 })
|
|
497
|
+
})
|
|
498
|
+
|
|
478
499
|
it('suppresses budget warnings in manual mode', () => {
|
|
479
500
|
const warning = evaluateBudgetWarning('manual', 2, 10, 10)
|
|
480
501
|
expect(warning).toBeNull()
|