@goodandready/dsh-moa 0.2.78 → 0.2.80
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 +2 -0
- package/README.ru.md +2 -0
- package/README.zh.md +2 -0
- package/lib/client.js +204 -3
- package/lib/events.js +4 -0
- package/lib/moa-schema.js +22 -0
- package/lib/pipeline/budget-guard.js +158 -0
- package/lib/pipeline/embedding-clustering.js +456 -0
- package/lib/pipeline/pareto-frontier.js +167 -0
- package/lib/pipeline/rebuttal-debate.js +335 -0
- package/lib/pipeline/runner.js +90 -5
- package/lib/pricing.js +8 -3
- package/package.json +1 -1
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Round Rebuttal Debate between Candidates (#298)
|
|
3
|
+
*
|
|
4
|
+
* Implements multi-round cross-examination and rebuttal discussion between candidate models
|
|
5
|
+
* before final arbitration by the Judge. Candidates anonymize and analyze peer solutions,
|
|
6
|
+
* identify flaws, security edge-cases, and hallucinations, and refine their own answers.
|
|
7
|
+
* The structured debate protocol is then passed to the Judge for weighted arbitration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { logger } from '../logger.js'
|
|
11
|
+
import { callWithTransientRetry } from '../moa-candidates.js'
|
|
12
|
+
import { extractFileBlocks, writeCandidateWorkspace } from '../workspace/fs.js'
|
|
13
|
+
import { verifyFileSyntax } from '../workspace/diff.js'
|
|
14
|
+
import { estimateTokenCost } from '../pricing.js'
|
|
15
|
+
import { emitEvent, MOA_EVENTS } from '../events.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Builds the rebuttal prompt for a candidate in a given debate round.
|
|
19
|
+
* @param {object} params
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
export function buildRebuttalPrompt({
|
|
23
|
+
userPrompt,
|
|
24
|
+
candidateText,
|
|
25
|
+
opponentAnswers,
|
|
26
|
+
roundNumber = 1,
|
|
27
|
+
totalRounds = 1,
|
|
28
|
+
isBlind = true,
|
|
29
|
+
}) {
|
|
30
|
+
const opponentsFormatted = opponentAnswers
|
|
31
|
+
.map((op, idx) => {
|
|
32
|
+
const label = op.label || `Candidate ${idx + 1}`
|
|
33
|
+
const persona = op.role_persona ? ` [Persona: ${op.role_persona}]` : ''
|
|
34
|
+
return `### ⚔️ Peer Solution [${label}]${persona}:\n${op.text}`
|
|
35
|
+
})
|
|
36
|
+
.join('\n\n')
|
|
37
|
+
|
|
38
|
+
return `You are an expert software engineer participating in a rigorous Multi-Agent Consilium Rebuttal Debate (Round ${roundNumber} of ${totalRounds}).
|
|
39
|
+
Your objective is to stress-test peer solutions, expose bugs, logical vulnerabilities, and hallucinations, and defend and refine your own solution.
|
|
40
|
+
|
|
41
|
+
=== ORIGINAL USER REQUEST ===
|
|
42
|
+
${userPrompt}
|
|
43
|
+
|
|
44
|
+
=== YOUR PREVIOUS PROPOSAL ===
|
|
45
|
+
${candidateText}
|
|
46
|
+
|
|
47
|
+
=== ALTERNATIVE PEER SOLUTIONS ===
|
|
48
|
+
${opponentsFormatted}
|
|
49
|
+
|
|
50
|
+
=== REBUTTAL DEBATE INSTRUCTIONS ===
|
|
51
|
+
Analyze the peer solutions with uncompromising technical rigor.
|
|
52
|
+
Structure your response EXACTLY into these three labeled sections:
|
|
53
|
+
|
|
54
|
+
### CRITIQUE OF PEERS
|
|
55
|
+
For each peer candidate, cite concrete issues:
|
|
56
|
+
- Identify logic bugs, off-by-one errors, uncaught exceptions, or security holes.
|
|
57
|
+
- Highlight missing edge cases or unhandled failure states.
|
|
58
|
+
- Point out inaccurate assumptions or hallucinations.
|
|
59
|
+
|
|
60
|
+
### REBUTTAL & DEFENSE
|
|
61
|
+
- Address the potential weaknesses or tradeoffs in your own approach.
|
|
62
|
+
- Explain why your architectural or algorithmic choices are superior or more maintainable.
|
|
63
|
+
|
|
64
|
+
### REFINED SOLUTION
|
|
65
|
+
- Provide your complete, improved, and final solution incorporating valid insights from the debate.
|
|
66
|
+
- Output complete code without placeholders or omissions.`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parses structured rebuttal debate output from a model response.
|
|
71
|
+
* @param {string} rawText
|
|
72
|
+
* @returns {{ critiqueText: string, defenseText: string, refinedText: string }}
|
|
73
|
+
*/
|
|
74
|
+
export function parseRebuttalResponse(rawText = '') {
|
|
75
|
+
if (typeof rawText !== 'string') {
|
|
76
|
+
return { critiqueText: '', defenseText: '', refinedText: '' }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let critiqueText = ''
|
|
80
|
+
let defenseText = ''
|
|
81
|
+
let refinedText = rawText
|
|
82
|
+
|
|
83
|
+
const critiqueMatch = rawText.match(/### CRITIQUE OF PEERS\s*([\s\S]*?)(?=### REBUTTAL & DEFENSE|### REFINED SOLUTION|$)/i)
|
|
84
|
+
if (critiqueMatch) {
|
|
85
|
+
critiqueText = critiqueMatch[1].trim()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const defenseMatch = rawText.match(/### REBUTTAL & DEFENSE\s*([\s\S]*?)(?=### REFINED SOLUTION|$)/i)
|
|
89
|
+
if (defenseMatch) {
|
|
90
|
+
defenseText = defenseMatch[1].trim()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const refinedMatch = rawText.match(/### REFINED SOLUTION\s*([\s\S]*)$/i)
|
|
94
|
+
if (refinedMatch && refinedMatch[1].trim().length > 50) {
|
|
95
|
+
refinedText = refinedMatch[1].trim()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
critiqueText,
|
|
100
|
+
defenseText,
|
|
101
|
+
refinedText,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Executes a single round of rebuttal debate across all active candidates.
|
|
107
|
+
* @param {object} params
|
|
108
|
+
* @returns {Promise<{ round: number, critiques: Array<object>, updatedCandidates: Array<object> }>}
|
|
109
|
+
*/
|
|
110
|
+
export async function executeDebateRound({
|
|
111
|
+
candidates = [],
|
|
112
|
+
userPrompt = '',
|
|
113
|
+
roundNumber = 1,
|
|
114
|
+
totalRounds = 1,
|
|
115
|
+
isBlindEvaluation = true,
|
|
116
|
+
callLlm,
|
|
117
|
+
refTemp = 0.5,
|
|
118
|
+
maxTokens = 4096,
|
|
119
|
+
refTimeoutSec = 60,
|
|
120
|
+
candidateRetries = 2,
|
|
121
|
+
prices = {},
|
|
122
|
+
signal = null,
|
|
123
|
+
onProgress = null,
|
|
124
|
+
cwd = process.cwd(),
|
|
125
|
+
runId = '',
|
|
126
|
+
ctx = null,
|
|
127
|
+
}) {
|
|
128
|
+
if (typeof onProgress === 'function') {
|
|
129
|
+
onProgress(`🗣️ *Rebuttal Debate Round ${roundNumber}/${totalRounds}: Candidates cross-examining peer solutions in parallel...*\n`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const roundCritiques = []
|
|
133
|
+
|
|
134
|
+
const debatePromises = candidates.map(async (cand) => {
|
|
135
|
+
const opponents = candidates
|
|
136
|
+
.filter((c) => c.index !== cand.index)
|
|
137
|
+
.map((c) => ({
|
|
138
|
+
index: c.index,
|
|
139
|
+
label: isBlindEvaluation ? `Candidate ${c.index}` : c.label,
|
|
140
|
+
role_persona: c.role_persona || c.slot?.role_persona,
|
|
141
|
+
text: c.text,
|
|
142
|
+
}))
|
|
143
|
+
|
|
144
|
+
const rebuttalPrompt = buildRebuttalPrompt({
|
|
145
|
+
userPrompt,
|
|
146
|
+
candidateText: cand.text,
|
|
147
|
+
opponentAnswers: opponents,
|
|
148
|
+
roundNumber,
|
|
149
|
+
totalRounds,
|
|
150
|
+
isBlind: isBlindEvaluation,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const slot = cand.slot || { provider: cand.provider, model: cand.model }
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const res = await callWithTransientRetry(
|
|
157
|
+
callLlm,
|
|
158
|
+
{
|
|
159
|
+
provider: slot.provider,
|
|
160
|
+
model: slot.model,
|
|
161
|
+
messages: [{ role: 'user', content: rebuttalPrompt }],
|
|
162
|
+
temperature: refTemp,
|
|
163
|
+
maxTokens,
|
|
164
|
+
timeoutMs: refTimeoutSec * 1000,
|
|
165
|
+
signal,
|
|
166
|
+
onProgress,
|
|
167
|
+
},
|
|
168
|
+
candidateRetries
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const parsed = parseRebuttalResponse(res.text)
|
|
172
|
+
const costEst = estimateTokenCost({
|
|
173
|
+
provider: slot.provider,
|
|
174
|
+
model: slot.model,
|
|
175
|
+
inputTokens: res.usage?.inputTokens || 0,
|
|
176
|
+
outputTokens: res.usage?.outputTokens || 0,
|
|
177
|
+
cacheHitTokens: res.usage?.cacheHitTokens || 0,
|
|
178
|
+
prices,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
const newFiles = extractFileBlocks(parsed.refinedText)
|
|
182
|
+
if (newFiles.length > 0) {
|
|
183
|
+
await writeCandidateWorkspace(cwd, cand.index, newFiles, { runId })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const syntaxValidation = verifyFileSyntax(newFiles)
|
|
187
|
+
|
|
188
|
+
const critiqueRecord = {
|
|
189
|
+
candidateIndex: cand.index,
|
|
190
|
+
candidateLabel: isBlindEvaluation ? `Candidate ${cand.index}` : cand.label,
|
|
191
|
+
critique: parsed.critiqueText,
|
|
192
|
+
defense: parsed.defenseText,
|
|
193
|
+
}
|
|
194
|
+
roundCritiques.push(critiqueRecord)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
...cand,
|
|
198
|
+
text: parsed.refinedText,
|
|
199
|
+
files: newFiles.length > 0 ? newFiles : cand.files,
|
|
200
|
+
syntaxValidation: syntaxValidation.valid ? syntaxValidation : cand.syntaxValidation,
|
|
201
|
+
usage: {
|
|
202
|
+
inputTokens: (cand.usage?.inputTokens || 0) + (res.usage?.inputTokens || 0),
|
|
203
|
+
outputTokens: (cand.usage?.outputTokens || 0) + (res.usage?.outputTokens || 0),
|
|
204
|
+
totalTokens: (cand.usage?.totalTokens || 0) + (res.usage?.totalTokens || 0),
|
|
205
|
+
},
|
|
206
|
+
costUsd: Number(((cand.costUsd || 0) + (costEst?.costUsd || 0)).toFixed(5)),
|
|
207
|
+
lastDebateCritique: parsed.critiqueText,
|
|
208
|
+
lastDebateDefense: parsed.defenseText,
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
logger.warn(`[dsh-moa] Candidate ${cand.index} rebuttal debate round ${roundNumber} failed:`, err?.message || err)
|
|
212
|
+
return cand
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const updatedCandidates = await Promise.all(debatePromises)
|
|
217
|
+
|
|
218
|
+
emitEvent(ctx, MOA_EVENTS.DEBATE_ROUND, {
|
|
219
|
+
runId,
|
|
220
|
+
roundNumber,
|
|
221
|
+
totalRounds,
|
|
222
|
+
critiquesCount: roundCritiques.length,
|
|
223
|
+
activeCandidates: updatedCandidates.length,
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
round: roundNumber,
|
|
228
|
+
critiques: roundCritiques,
|
|
229
|
+
updatedCandidates,
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Formats accumulated debate protocol into a clean markdown directive for the Judge.
|
|
235
|
+
* @param {Array<object>} debateRounds
|
|
236
|
+
* @returns {string}
|
|
237
|
+
*/
|
|
238
|
+
export function formatDebateDirective(debateRounds = []) {
|
|
239
|
+
if (!Array.isArray(debateRounds) || debateRounds.length === 0) return ''
|
|
240
|
+
|
|
241
|
+
const sections = []
|
|
242
|
+
sections.push('=== ⚔️ MULTI-ROUND REBUTTAL DEBATE PROTOCOL ===')
|
|
243
|
+
sections.push('The candidate models engaged in peer cross-examination prior to arbitration.')
|
|
244
|
+
sections.push('CRITICAL JUDGE DIRECTIVE: Heavily weight valid vulnerabilities or bugs identified by peers.')
|
|
245
|
+
|
|
246
|
+
for (const r of debateRounds) {
|
|
247
|
+
sections.push(`\n--- Debate Round ${r.round} ---`)
|
|
248
|
+
for (const c of r.critiques || []) {
|
|
249
|
+
if (c.critique) {
|
|
250
|
+
sections.push(`[${c.candidateLabel}] Critiques of peers:\n${c.critique}`)
|
|
251
|
+
}
|
|
252
|
+
if (c.defense) {
|
|
253
|
+
sections.push(`[${c.candidateLabel}] Defense & Justification:\n${c.defense}`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
sections.push('\n==============================================')
|
|
259
|
+
return sections.join('\n')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Orchestrates multi-round rebuttal debate phase (#298).
|
|
264
|
+
* @param {object} params
|
|
265
|
+
* @returns {Promise<{ ok: boolean, updatedCandidates: Array<object>, debateDirective: string, totalCritiques: number, rounds: Array<object> }>}
|
|
266
|
+
*/
|
|
267
|
+
export async function runRebuttalDebatePhase({
|
|
268
|
+
candidates = [],
|
|
269
|
+
userPrompt = '',
|
|
270
|
+
preset = {},
|
|
271
|
+
prices = {},
|
|
272
|
+
maxTokens = 4096,
|
|
273
|
+
isBlindEvaluation = true,
|
|
274
|
+
callLlm,
|
|
275
|
+
signal = null,
|
|
276
|
+
onProgress = null,
|
|
277
|
+
cwd = process.cwd(),
|
|
278
|
+
runId = '',
|
|
279
|
+
ctx = null,
|
|
280
|
+
}) {
|
|
281
|
+
const enabled = Boolean(preset?.rebuttal_debate_enabled || preset?.rebuttalDebateEnabled)
|
|
282
|
+
if (!enabled || candidates.length < 2) {
|
|
283
|
+
return {
|
|
284
|
+
ok: true,
|
|
285
|
+
updatedCandidates: candidates,
|
|
286
|
+
debateDirective: '',
|
|
287
|
+
totalCritiques: 0,
|
|
288
|
+
rounds: [],
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const configuredRounds = preset?.rebuttal_rounds ?? preset?.rebuttalRounds ?? 1
|
|
293
|
+
const totalRounds = Math.min(3, Math.max(1, Number(configuredRounds) || 1))
|
|
294
|
+
|
|
295
|
+
let currentCandidates = [...candidates]
|
|
296
|
+
const debateRounds = []
|
|
297
|
+
|
|
298
|
+
for (let r = 1; r <= totalRounds; r++) {
|
|
299
|
+
if (signal?.aborted) {
|
|
300
|
+
break
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const roundResult = await executeDebateRound({
|
|
304
|
+
candidates: currentCandidates,
|
|
305
|
+
userPrompt,
|
|
306
|
+
roundNumber: r,
|
|
307
|
+
totalRounds,
|
|
308
|
+
isBlindEvaluation,
|
|
309
|
+
callLlm,
|
|
310
|
+
refTemp: preset?.reference_temperature ?? 0.5,
|
|
311
|
+
maxTokens,
|
|
312
|
+
refTimeoutSec: preset?.reference_timeout_sec ?? 60,
|
|
313
|
+
prices,
|
|
314
|
+
signal,
|
|
315
|
+
onProgress,
|
|
316
|
+
cwd,
|
|
317
|
+
runId,
|
|
318
|
+
ctx,
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
debateRounds.push(roundResult)
|
|
322
|
+
currentCandidates = roundResult.updatedCandidates
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const totalCritiques = debateRounds.reduce((acc, r) => acc + (r.critiques?.length || 0), 0)
|
|
326
|
+
const debateDirective = formatDebateDirective(debateRounds)
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
ok: true,
|
|
330
|
+
updatedCandidates: currentCandidates,
|
|
331
|
+
debateDirective,
|
|
332
|
+
totalCritiques,
|
|
333
|
+
rounds: debateRounds,
|
|
334
|
+
}
|
|
335
|
+
}
|
package/lib/pipeline/runner.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { applyParetoAdjustment } from './pareto-frontier.js'
|
|
2
|
+
import { evaluateHardBudgetLimit } from './budget-guard.js'
|
|
1
3
|
import { sanitizeCandidatesForCloudJudge } from './privacy-sanitizer.js'
|
|
2
4
|
import { evaluateJudgeEscalation } from './escalation.js'
|
|
3
5
|
import { calculateSelfConsistencyAgreement } from './self-consistency.js'
|
|
@@ -23,6 +25,8 @@ import { runSecurityAuditGate } from './security-fuzzer.js'
|
|
|
23
25
|
import { runCoveVerificationGate } from './cove-verifier.js'
|
|
24
26
|
import { runProfilerGate } from './runtime-profiler.js'
|
|
25
27
|
import { runMutationGate } from './mutation-tester.js'
|
|
28
|
+
import { stageEmbeddingClustering } from './embedding-clustering.js'
|
|
29
|
+
import { runRebuttalDebatePhase } from './rebuttal-debate.js'
|
|
26
30
|
import { emitEvent, MOA_EVENTS } from '../events.js'
|
|
27
31
|
|
|
28
32
|
export async function runMoAPipeline({
|
|
@@ -50,9 +54,36 @@ export async function runMoAPipeline({
|
|
|
50
54
|
timestamp: new Date(startTime).toISOString(),
|
|
51
55
|
})
|
|
52
56
|
|
|
57
|
+
// Pre-Stage 0.5: Pareto Frontier Slider & Hard Budget Guard (#307, #319)
|
|
58
|
+
let effectivePreset = preset
|
|
59
|
+
if (effectivePreset?.pareto_slider_enabled) {
|
|
60
|
+
effectivePreset = applyParetoAdjustment(effectivePreset, { ctx, runId, onProgress, prices })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (effectivePreset?.hard_budget_guard_enabled) {
|
|
64
|
+
const budgetRes = evaluateHardBudgetLimit({
|
|
65
|
+
references: effectivePreset.reference_models || [],
|
|
66
|
+
aggregator: effectivePreset.aggregator || {},
|
|
67
|
+
limitUsd: effectivePreset.hard_budget_limit_usd ?? 0.02,
|
|
68
|
+
policy: effectivePreset.hard_budget_policy || 'auto_downgrade',
|
|
69
|
+
prices,
|
|
70
|
+
options: { ctx, runId, onProgress },
|
|
71
|
+
})
|
|
72
|
+
if (!budgetRes.ok) {
|
|
73
|
+
return { error: new Error(budgetRes.error) }
|
|
74
|
+
}
|
|
75
|
+
if (budgetRes.scaled) {
|
|
76
|
+
effectivePreset = {
|
|
77
|
+
...effectivePreset,
|
|
78
|
+
reference_models: budgetRes.references,
|
|
79
|
+
aggregator: budgetRes.aggregator,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
53
84
|
// Stage 1: Context & Configuration
|
|
54
85
|
const ctxStage = await stageProjectContext({
|
|
55
|
-
userPrompt, messages, preset, cwd, prices, maxTokens:
|
|
86
|
+
userPrompt, messages, preset: effectivePreset, cwd, prices, maxTokens: effectivePreset?.max_tokens, startTime, onProgress, ctx,
|
|
56
87
|
})
|
|
57
88
|
if (!ctxStage.ok) return ctxStage.abortPayload
|
|
58
89
|
|
|
@@ -90,7 +121,7 @@ export async function runMoAPipeline({
|
|
|
90
121
|
})
|
|
91
122
|
return candStage.failurePayload
|
|
92
123
|
}
|
|
93
|
-
|
|
124
|
+
let { referenceOutputs, successfulRefs, adversarialTddResult } = candStage
|
|
94
125
|
|
|
95
126
|
for (const ref of referenceOutputs) {
|
|
96
127
|
emitEvent(ctx, MOA_EVENTS.CANDIDATE_COMPLETE, {
|
|
@@ -173,6 +204,32 @@ export async function runMoAPipeline({
|
|
|
173
204
|
}
|
|
174
205
|
}
|
|
175
206
|
|
|
207
|
+
// Pre-Judge Stage 4.6: Blind Embedding Clustering for Zero-LLM Fast Consensus (#312)
|
|
208
|
+
if ((preset?.embedding_clustering_enabled || preset?.embeddingClusteringEnabled) && successfulRefs.length > 1) {
|
|
209
|
+
const embedRes = await stageEmbeddingClustering({
|
|
210
|
+
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
211
|
+
referenceOutputs,
|
|
212
|
+
preset,
|
|
213
|
+
userPrompt,
|
|
214
|
+
prices,
|
|
215
|
+
maxTokens: ctxStage.maxTokens,
|
|
216
|
+
cwd,
|
|
217
|
+
runId,
|
|
218
|
+
signal,
|
|
219
|
+
promoteOpts,
|
|
220
|
+
allowCandidateOverride: ctxStage.allowCandidateOverride,
|
|
221
|
+
isRefinement: ctxStage.isRefinement,
|
|
222
|
+
historyFilePath,
|
|
223
|
+
startTime,
|
|
224
|
+
collectedCtx: ctxStage.collectedCtx,
|
|
225
|
+
liveCanvas,
|
|
226
|
+
ctx,
|
|
227
|
+
})
|
|
228
|
+
if (embedRes.consensusReached) {
|
|
229
|
+
return embedRes.payload
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
176
233
|
if (signal?.aborted) {
|
|
177
234
|
await cleanMoaWorkspaces(cwd, { runId })
|
|
178
235
|
return { error: new Error('Turn aborted') }
|
|
@@ -199,6 +256,35 @@ export async function runMoAPipeline({
|
|
|
199
256
|
})
|
|
200
257
|
}
|
|
201
258
|
|
|
259
|
+
// Stage 5.2: Multi-Round Rebuttal Debate between Candidates (#298)
|
|
260
|
+
let debateDirective = null
|
|
261
|
+
if ((preset?.rebuttal_debate_enabled || preset?.rebuttalDebateEnabled) && successfulRefs.length > 1) {
|
|
262
|
+
const debateRes = await runRebuttalDebatePhase({
|
|
263
|
+
candidates: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
264
|
+
userPrompt,
|
|
265
|
+
preset,
|
|
266
|
+
prices,
|
|
267
|
+
maxTokens: ctxStage.maxTokens,
|
|
268
|
+
isBlindEvaluation: ctxStage.isBlindEvaluation,
|
|
269
|
+
callLlm,
|
|
270
|
+
signal,
|
|
271
|
+
onProgress,
|
|
272
|
+
cwd,
|
|
273
|
+
runId,
|
|
274
|
+
ctx,
|
|
275
|
+
})
|
|
276
|
+
if (debateRes.ok && debateRes.debateDirective) {
|
|
277
|
+
debateDirective = debateRes.debateDirective
|
|
278
|
+
if (debateRes.updatedCandidates?.length > 0) {
|
|
279
|
+
successfulRefs = debateRes.updatedCandidates
|
|
280
|
+
referenceOutputs = referenceOutputs.map((r) => {
|
|
281
|
+
const matching = successfulRefs.find((c) => c.index === r.index || c.label === r.label)
|
|
282
|
+
return matching ? { ...r, text: matching.text } : r
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
202
288
|
if (signal?.aborted) {
|
|
203
289
|
await cleanMoaWorkspaces(cwd, { runId })
|
|
204
290
|
return { error: new Error('Turn aborted') }
|
|
@@ -307,7 +393,6 @@ export async function runMoAPipeline({
|
|
|
307
393
|
let effectivePrimaryJudge = ctxStage.primaryJudge
|
|
308
394
|
let effectiveFallbackJudges = ctxStage.fallbackJudges
|
|
309
395
|
let effectiveJudgesChain = ctxStage.judgesChain
|
|
310
|
-
let effectivePreset = preset
|
|
311
396
|
|
|
312
397
|
if (escalationResult.escalated) {
|
|
313
398
|
emitEvent(ctx, MOA_EVENTS.JUDGE_ESCALATION, {
|
|
@@ -344,7 +429,7 @@ export async function runMoAPipeline({
|
|
|
344
429
|
preset: effectivePreset,
|
|
345
430
|
successfulRefs: candidatesForJudge,
|
|
346
431
|
referenceOutputs: sanitizedReferenceOutputs, primaryJudge: effectivePrimaryJudge,
|
|
347
|
-
userPrompt, judgeCriteria: [ctxStage.judgeCriteria, coveDirective, securityDirective, profilerDirective, mutationDirective, privacyDirective].filter(Boolean).join('\n\n'), prices, maxTokens: ctxStage.maxTokens,
|
|
432
|
+
userPrompt, judgeCriteria: [ctxStage.judgeCriteria, debateDirective, coveDirective, securityDirective, profilerDirective, mutationDirective, privacyDirective].filter(Boolean).join('\n\n'), prices, maxTokens: ctxStage.maxTokens,
|
|
348
433
|
callLlm, signal, onProgress, cwd, runId, isRefinement: ctxStage.isRefinement,
|
|
349
434
|
isFastMode: ctxStage.isFastMode, collectedCtx: ctxStage.collectedCtx, startTime,
|
|
350
435
|
})
|
|
@@ -402,7 +487,7 @@ export async function runMoAPipeline({
|
|
|
402
487
|
}
|
|
403
488
|
}
|
|
404
489
|
|
|
405
|
-
const fileScoringDirective = [rawFileDirective, visualResult?.directive, adversarialTddResult?.directive, syntaxDirective, coveDirective, securityDirective, profilerDirective, mutationDirective, privacyDirective, pruningDirective, ctxStage.stackAwareDirective, ctxStage.developerPreferencesDirective].filter(Boolean).join("\n\n") || null
|
|
490
|
+
const fileScoringDirective = [rawFileDirective, visualResult?.directive, adversarialTddResult?.directive, syntaxDirective, debateDirective, coveDirective, securityDirective, profilerDirective, mutationDirective, privacyDirective, pruningDirective, ctxStage.stackAwareDirective, ctxStage.developerPreferencesDirective].filter(Boolean).join("\n\n") || null
|
|
406
491
|
|
|
407
492
|
// Stage 7: Synthesis Phase
|
|
408
493
|
const synthRes = await stageSynthesis({
|
package/lib/pricing.js
CHANGED
|
@@ -152,6 +152,11 @@ export function resolveModelRates(slot = {}, customPrices = {}, cachePath = getD
|
|
|
152
152
|
const fullKey = provider ? `${provider}/${model}` : model
|
|
153
153
|
const colonKey = provider ? `${provider}:${model}` : model
|
|
154
154
|
|
|
155
|
+
// 0. Free local edge providers
|
|
156
|
+
if (provider === 'ollama' || provider === 'local') {
|
|
157
|
+
return { input: 0, output: 0, cacheHit: 0 }
|
|
158
|
+
}
|
|
159
|
+
|
|
155
160
|
// 1. Check custom user overrides in config
|
|
156
161
|
if (customPrices && typeof customPrices === 'object') {
|
|
157
162
|
if (customPrices[fullKey]) return normalizeRates(customPrices[fullKey])
|
|
@@ -278,9 +283,9 @@ export function resolveModelRates(slot = {}, customPrices = {}, cachePath = getD
|
|
|
278
283
|
function normalizeRates(rate) {
|
|
279
284
|
if (!rate || typeof rate !== 'object') return { ...FALLBACK_RATES }
|
|
280
285
|
return {
|
|
281
|
-
input: Number(rate.input ?? rate.prompt ?? FALLBACK_RATES.input),
|
|
282
|
-
output: Number(rate.output ?? rate.completion ?? FALLBACK_RATES.output),
|
|
283
|
-
cacheHit: Number(rate.cacheHit ?? rate.cache_hit ?? FALLBACK_RATES.cacheHit),
|
|
286
|
+
input: Number(rate.input ?? rate.prompt ?? rate.prompt_per_million ?? FALLBACK_RATES.input),
|
|
287
|
+
output: Number(rate.output ?? rate.completion ?? rate.completion_per_million ?? FALLBACK_RATES.output),
|
|
288
|
+
cacheHit: Number(rate.cacheHit ?? rate.cache_hit ?? rate.cache_hit_per_million ?? FALLBACK_RATES.cacheHit),
|
|
284
289
|
}
|
|
285
290
|
}
|
|
286
291
|
|