@goodandready/dsh-moa 0.2.70 → 0.2.72
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 +9 -0
- package/README.ru.md +9 -0
- package/README.zh.md +9 -0
- package/lib/client.js +103 -0
- package/lib/events.js +3 -0
- package/lib/moa-adversarial-tdd.js +222 -0
- package/lib/moa-ast-merge.js +233 -0
- package/lib/moa-schema.js +15 -0
- package/lib/moa-speculative.js +195 -0
- package/lib/moa-swarm.js +13 -4
- package/lib/moa-workbench.js +6 -0
- package/lib/pipeline/candidates.js +42 -2
- package/lib/pipeline/context.js +5 -0
- package/lib/pipeline/early-consensus.js +324 -0
- package/lib/pipeline/runner.js +62 -10
- package/lib/pipeline/syntax-filter.js +324 -0
- package/lib/tui-settings.js +2 -1
- package/package.json +2 -1
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Early Exit upon Semantic Consensus (#299)
|
|
3
|
+
* Detects convergence between reference candidates and bypasses judge round when consensus >= threshold.
|
|
4
|
+
* Saves up to 60-80% tokens and latency on requests where candidates converge.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { logger } from '../logger.js'
|
|
8
|
+
import { promoteCandidateWorkspace, cleanMoaWorkspaces } from '../workspace/fs.js'
|
|
9
|
+
import { createPromotedPreview } from '../live-canvas.js'
|
|
10
|
+
import { recordMoaRunAsync, candidatesForHistory } from '../history.js'
|
|
11
|
+
import { emitEvent, MOA_EVENTS } from '../events.js'
|
|
12
|
+
import { estimateTokenCost } from '../pricing.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Normalizes text/code by stripping comments and normalizing whitespace for consensus comparison.
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeForConsensus(text = '') {
|
|
20
|
+
if (typeof text !== 'string') return ''
|
|
21
|
+
return text
|
|
22
|
+
// Strip block comments /* ... */
|
|
23
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
24
|
+
// Strip JS/TS single line comments // ...
|
|
25
|
+
.replace(/\/\/.*$/gm, '')
|
|
26
|
+
// Strip Python/Shell comments # ...
|
|
27
|
+
.replace(/(^|\s)#.*$/gm, '$1')
|
|
28
|
+
// Normalize newlines
|
|
29
|
+
.replace(/\r?\n/g, '\n')
|
|
30
|
+
// Collapse horizontal whitespace
|
|
31
|
+
.replace(/[ \t]+/g, ' ')
|
|
32
|
+
// Collapse multi-blank lines
|
|
33
|
+
.replace(/\n\s*\n/g, '\n')
|
|
34
|
+
.trim()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Calculates Sørensen-Dice similarity coefficient between two code/text snippets (0.0 to 1.0).
|
|
39
|
+
* @param {string} textA
|
|
40
|
+
* @param {string} textB
|
|
41
|
+
* @returns {number}
|
|
42
|
+
*/
|
|
43
|
+
export function calculateSimilarity(textA = '', textB = '') {
|
|
44
|
+
const normA = normalizeForConsensus(textA)
|
|
45
|
+
const normB = normalizeForConsensus(textB)
|
|
46
|
+
|
|
47
|
+
if (normA === normB) return 1.0
|
|
48
|
+
if (!normA || !normB) return 0.0
|
|
49
|
+
|
|
50
|
+
const tokensA = normA.match(/[\w$]+|[^\s\w]/g) || []
|
|
51
|
+
const tokensB = normB.match(/[\w$]+|[^\s\w]/g) || []
|
|
52
|
+
|
|
53
|
+
if (tokensA.length === 0 && tokensB.length === 0) return 1.0
|
|
54
|
+
if (tokensA.length === 0 || tokensB.length === 0) return 0.0
|
|
55
|
+
|
|
56
|
+
const freqA = new Map()
|
|
57
|
+
for (const t of tokensA) freqA.set(t, (freqA.get(t) || 0) + 1)
|
|
58
|
+
|
|
59
|
+
const freqB = new Map()
|
|
60
|
+
for (const t of tokensB) freqB.set(t, (freqB.get(t) || 0) + 1)
|
|
61
|
+
|
|
62
|
+
let intersection = 0
|
|
63
|
+
for (const [token, countA] of freqA.entries()) {
|
|
64
|
+
const countB = freqB.get(token) || 0
|
|
65
|
+
intersection += Math.min(countA, countB)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const dice = (2 * intersection) / (tokensA.length + tokensB.length)
|
|
69
|
+
return Number(dice.toFixed(4))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Checks if candidates have reached semantic consensus above threshold.
|
|
74
|
+
* @param {Array<object>} candidates
|
|
75
|
+
* @param {number} [threshold=0.95]
|
|
76
|
+
* @returns {{ reached: boolean, similarity: number, winnerIndex: number, consensusIndices: number[], winnerCandidate: object | null, reason: string }}
|
|
77
|
+
*/
|
|
78
|
+
export function checkSemanticConsensus(candidates = [], threshold = 0.95) {
|
|
79
|
+
if (!Array.isArray(candidates) || candidates.length < 2) {
|
|
80
|
+
return {
|
|
81
|
+
reached: false,
|
|
82
|
+
similarity: 0,
|
|
83
|
+
winnerIndex: 0,
|
|
84
|
+
consensusIndices: [],
|
|
85
|
+
winnerCandidate: null,
|
|
86
|
+
reason: 'Insufficient candidates for consensus check',
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const n = candidates.length
|
|
91
|
+
let maxSim = 0
|
|
92
|
+
let bestPair = [0, 1]
|
|
93
|
+
const matrix = Array.from({ length: n }, () => new Float32Array(n))
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < n; i++) {
|
|
96
|
+
matrix[i][i] = 1.0
|
|
97
|
+
for (let j = i + 1; j < n; j++) {
|
|
98
|
+
const sim = calculateSimilarity(candidates[i].text || '', candidates[j].text || '')
|
|
99
|
+
matrix[i][j] = sim
|
|
100
|
+
matrix[j][i] = sim
|
|
101
|
+
if (sim > maxSim) {
|
|
102
|
+
maxSim = sim
|
|
103
|
+
bestPair = [i, j]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (maxSim < threshold) {
|
|
109
|
+
return {
|
|
110
|
+
reached: false,
|
|
111
|
+
similarity: maxSim,
|
|
112
|
+
winnerIndex: 0,
|
|
113
|
+
consensusIndices: [],
|
|
114
|
+
winnerCandidate: null,
|
|
115
|
+
reason: `Max pairwise similarity ${maxSim} is below consensus threshold ${threshold}`,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Find consensus cluster matching threshold with the best pair
|
|
120
|
+
const clusterSet = new Set(bestPair)
|
|
121
|
+
for (let k = 0; k < n; k++) {
|
|
122
|
+
if (k === bestPair[0] || k === bestPair[1]) continue
|
|
123
|
+
if (matrix[k][bestPair[0]] >= threshold && matrix[k][bestPair[1]] >= threshold) {
|
|
124
|
+
clusterSet.add(k)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const consensusIndices = Array.from(clusterSet).map((idx) => candidates[idx].index ?? idx)
|
|
129
|
+
const clusterCandidates = Array.from(clusterSet).map((idx) => candidates[idx])
|
|
130
|
+
|
|
131
|
+
// Select winner from consensus cluster (prioritize valid syntax, then lowest error count)
|
|
132
|
+
let winner = clusterCandidates[0]
|
|
133
|
+
for (const c of clusterCandidates) {
|
|
134
|
+
if (c.syntaxValidation?.valid && !winner.syntaxValidation?.valid) {
|
|
135
|
+
winner = c
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
reached: true,
|
|
141
|
+
similarity: maxSim,
|
|
142
|
+
winnerIndex: winner.index ?? 1,
|
|
143
|
+
consensusIndices,
|
|
144
|
+
winnerCandidate: winner,
|
|
145
|
+
reason: `Semantic consensus reached (${maxSim} >= ${threshold}) across candidates ${consensusIndices.join(', ')}`,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Estimates saved tokens and cost from bypassing the judge round.
|
|
151
|
+
* @param {Array<object>} candidates
|
|
152
|
+
* @param {string} prompt
|
|
153
|
+
* @param {number} [maxTokens=4096]
|
|
154
|
+
* @param {object} [prices={}]
|
|
155
|
+
* @returns {{ skippedTokens: number, skippedCostUsd: number }}
|
|
156
|
+
*/
|
|
157
|
+
export function estimateSkippedJudgeUsage(candidates = [], prompt = '', maxTokens = 4096, prices = {}) {
|
|
158
|
+
const promptChars = (prompt?.length || 0) + candidates.reduce((sum, c) => sum + (c.text?.length || 0), 0)
|
|
159
|
+
const estimatedInputTokens = Math.max(100, Math.round(promptChars / 3.8))
|
|
160
|
+
const estimatedOutputTokens = Math.min(Math.max(200, Math.round(maxTokens * 0.25)), 1000)
|
|
161
|
+
const skippedTokens = estimatedInputTokens + estimatedOutputTokens
|
|
162
|
+
|
|
163
|
+
const costEst = estimateTokenCost({ provider: 'default', model: 'judge' }, {
|
|
164
|
+
inputTokens: estimatedInputTokens,
|
|
165
|
+
outputTokens: estimatedOutputTokens,
|
|
166
|
+
}, prices)
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
skippedTokens,
|
|
170
|
+
skippedCostUsd: Number((costEst?.costUsd || 0.005).toFixed(5)),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Evaluates candidates for early consensus and executes early exit bypass if reached.
|
|
176
|
+
* @param {object} params
|
|
177
|
+
* @returns {Promise<{ ok: boolean, consensusReached: boolean, payload?: object, error?: Error, abort?: boolean }>}
|
|
178
|
+
*/
|
|
179
|
+
export function stageEarlyConsensus({
|
|
180
|
+
successfulRefs = [],
|
|
181
|
+
referenceOutputs = [],
|
|
182
|
+
preset = {},
|
|
183
|
+
userPrompt = '',
|
|
184
|
+
prices = {},
|
|
185
|
+
maxTokens = 4096,
|
|
186
|
+
cwd = process.cwd(),
|
|
187
|
+
runId = '',
|
|
188
|
+
signal = null,
|
|
189
|
+
promoteOpts = {},
|
|
190
|
+
allowCandidateOverride = false,
|
|
191
|
+
isRefinement = false,
|
|
192
|
+
historyFilePath = null,
|
|
193
|
+
startTime = Date.now(),
|
|
194
|
+
collectedCtx = null,
|
|
195
|
+
liveCanvas = null,
|
|
196
|
+
ctx = null,
|
|
197
|
+
}) {
|
|
198
|
+
const enabled = Boolean(preset?.early_exit_consensus_enabled)
|
|
199
|
+
const threshold = typeof preset?.early_exit_threshold === 'number' ? preset.early_exit_threshold : 0.95
|
|
200
|
+
|
|
201
|
+
if (!enabled || successfulRefs.length < 2) {
|
|
202
|
+
return Promise.resolve({ ok: true, consensusReached: false })
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const consensus = checkSemanticConsensus(successfulRefs, threshold)
|
|
206
|
+
if (!consensus.reached || !consensus.winnerCandidate) {
|
|
207
|
+
return Promise.resolve({ ok: true, consensusReached: false, similarity: consensus.similarity })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const winner = consensus.winnerCandidate
|
|
211
|
+
emitEvent(ctx, MOA_EVENTS.EARLY_CONSENSUS, {
|
|
212
|
+
runId,
|
|
213
|
+
similarity: consensus.similarity,
|
|
214
|
+
threshold,
|
|
215
|
+
winnerIndex: consensus.winnerIndex,
|
|
216
|
+
winnerModel: winner.label,
|
|
217
|
+
consensusCandidates: consensus.consensusIndices,
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// Promote winning candidate files
|
|
221
|
+
let promotedFiles = []
|
|
222
|
+
let promoteErr = null
|
|
223
|
+
const runPromotion = async () => {
|
|
224
|
+
if (winner.files?.length > 0) {
|
|
225
|
+
try {
|
|
226
|
+
promotedFiles = await promoteCandidateWorkspace(cwd, winner.index, {
|
|
227
|
+
...promoteOpts,
|
|
228
|
+
runId,
|
|
229
|
+
keepMoa: allowCandidateOverride,
|
|
230
|
+
})
|
|
231
|
+
} catch (err) {
|
|
232
|
+
promoteErr = err
|
|
233
|
+
}
|
|
234
|
+
} else if (!allowCandidateOverride) {
|
|
235
|
+
await cleanMoaWorkspaces(cwd, { runId })
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (promoteErr) {
|
|
239
|
+
if (!allowCandidateOverride) await cleanMoaWorkspaces(cwd, { runId })
|
|
240
|
+
if (signal?.aborted || promoteErr?.message?.includes('aborted')) {
|
|
241
|
+
return { ok: false, consensusReached: true, abort: true, error: new Error('Turn aborted') }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const skippedUsage = estimateSkippedJudgeUsage(successfulRefs, userPrompt, maxTokens, prices)
|
|
246
|
+
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs, {
|
|
247
|
+
winningIndex: winner.index,
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const totalTokens = referenceOutputs.reduce((acc, r) => acc + (r.usage?.totalTokens || 0), 0)
|
|
251
|
+
const totalCostUsd = Number(referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0).toFixed(5))
|
|
252
|
+
const durationMs = Date.now() - startTime
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
await recordMoaRunAsync({
|
|
256
|
+
id: runId,
|
|
257
|
+
cwd,
|
|
258
|
+
prompt: userPrompt,
|
|
259
|
+
preset: preset?.name || 'default',
|
|
260
|
+
isRefinement,
|
|
261
|
+
isEarlyConsensus: true,
|
|
262
|
+
candidates: candidatesForHistory(referenceOutputs),
|
|
263
|
+
aggregator: 'Early Consensus (Bypassed)',
|
|
264
|
+
winnerIndex: winner.index,
|
|
265
|
+
winnerModel: winner.label,
|
|
266
|
+
promotedFiles,
|
|
267
|
+
totalTokens,
|
|
268
|
+
totalCostUsd,
|
|
269
|
+
skippedJudgeTokens: skippedUsage.skippedTokens,
|
|
270
|
+
durationMs,
|
|
271
|
+
}, historyFilePath || undefined)
|
|
272
|
+
} catch (histErr) {
|
|
273
|
+
logger.warn('[dsh-moa] Failed to record early consensus in history:', histErr?.message || histErr)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
emitEvent(ctx, MOA_EVENTS.RUN_FINISH, {
|
|
277
|
+
runId,
|
|
278
|
+
durationMs,
|
|
279
|
+
totalTokens,
|
|
280
|
+
totalCostUsd,
|
|
281
|
+
promotedFiles,
|
|
282
|
+
winnerIndex: winner.index,
|
|
283
|
+
isEarlyConsensus: true,
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
ok: true,
|
|
288
|
+
consensusReached: true,
|
|
289
|
+
payload: {
|
|
290
|
+
kind: 'synthesis',
|
|
291
|
+
content: winner.text,
|
|
292
|
+
aggregator: 'Early Consensus (Bypassed)',
|
|
293
|
+
references: referenceOutputs,
|
|
294
|
+
presetName: preset?.name || 'default',
|
|
295
|
+
isRefinement,
|
|
296
|
+
isEarlyConsensus: true,
|
|
297
|
+
winningIndex: winner.index,
|
|
298
|
+
winnerModel: winner.label,
|
|
299
|
+
promotedFiles,
|
|
300
|
+
runId,
|
|
301
|
+
consensusInfo: {
|
|
302
|
+
similarity: consensus.similarity,
|
|
303
|
+
threshold,
|
|
304
|
+
consensusIndices: consensus.consensusIndices,
|
|
305
|
+
},
|
|
306
|
+
...(livePreview ? { liveCanvas: livePreview } : {}),
|
|
307
|
+
usage: {
|
|
308
|
+
totalTokens,
|
|
309
|
+
totalCostUsd,
|
|
310
|
+
skippedJudgeTokens: skippedUsage.skippedTokens,
|
|
311
|
+
skippedJudgeCostUsd: skippedUsage.skippedCostUsd,
|
|
312
|
+
candidates: referenceOutputs.map((r) => ({ label: r.label, usage: r.usage, costUsd: r.costUsd })),
|
|
313
|
+
aggregator: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 },
|
|
314
|
+
},
|
|
315
|
+
skippedFiles: collectedCtx?.skippedFiles || 0,
|
|
316
|
+
skippedList: collectedCtx?.skippedList || [],
|
|
317
|
+
durationMs,
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return runPromotion()
|
|
323
|
+
}
|
|
324
|
+
|
package/lib/pipeline/runner.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildAstCompositeMergeDirective } from '../moa-ast-merge.js'
|
|
1
2
|
import { runVisualMoaConsensus } from '../moa-visual-consensus.js'
|
|
2
3
|
import crypto from 'node:crypto'
|
|
3
4
|
import { isTestEnvironment } from '../history.js'
|
|
@@ -12,6 +13,8 @@ import { stagePeerCritique } from './peer-critique.js'
|
|
|
12
13
|
import { stageMultiJudge } from './multi-judge.js'
|
|
13
14
|
import { stageSynthesis } from './synthesis.js'
|
|
14
15
|
import { stagePromotion } from './promotion.js'
|
|
16
|
+
import { applySyntaxFilter, formatSyntaxDirective } from './syntax-filter.js'
|
|
17
|
+
import { stageEarlyConsensus } from './early-consensus.js'
|
|
15
18
|
import { emitEvent, MOA_EVENTS } from '../events.js'
|
|
16
19
|
|
|
17
20
|
export async function runMoAPipeline({
|
|
@@ -76,7 +79,7 @@ export async function runMoAPipeline({
|
|
|
76
79
|
})
|
|
77
80
|
return candStage.failurePayload
|
|
78
81
|
}
|
|
79
|
-
const { referenceOutputs, successfulRefs } = candStage
|
|
82
|
+
const { referenceOutputs, successfulRefs, adversarialTddResult } = candStage
|
|
80
83
|
|
|
81
84
|
for (const ref of referenceOutputs) {
|
|
82
85
|
emitEvent(ctx, MOA_EVENTS.CANDIDATE_COMPLETE, {
|
|
@@ -89,6 +92,16 @@ export async function runMoAPipeline({
|
|
|
89
92
|
})
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
// Pre-Judge Stage 2.5: Zero-Token Local Syntax Gate (#303)
|
|
96
|
+
const syntaxResult = applySyntaxFilter(successfulRefs, { enabled: ctxStage.localPreflightCheck })
|
|
97
|
+
emitEvent(ctx, MOA_EVENTS.SYNTAX_VALIDATION, {
|
|
98
|
+
runId,
|
|
99
|
+
filterApplied: syntaxResult.filterApplied,
|
|
100
|
+
validCount: syntaxResult.validCandidates.length,
|
|
101
|
+
invalidCount: syntaxResult.invalidCandidates.length,
|
|
102
|
+
})
|
|
103
|
+
const syntaxDirective = syntaxResult.filterApplied ? formatSyntaxDirective(referenceOutputs, { blind: ctxStage.isBlindEvaluation }) : null
|
|
104
|
+
|
|
92
105
|
// Stage 3: Clarifying Questions Branch
|
|
93
106
|
if (ctxStage.needsQuestions) {
|
|
94
107
|
return await stageClarifyingQuestions({
|
|
@@ -102,7 +115,8 @@ export async function runMoAPipeline({
|
|
|
102
115
|
// Stage 4: Fast Mode (1 candidate bypass)
|
|
103
116
|
if (ctxStage.isFastMode) {
|
|
104
117
|
const fastRes = await stageFastMode({
|
|
105
|
-
successfulRefs
|
|
118
|
+
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
119
|
+
referenceOutputs,
|
|
106
120
|
allowCandidateOverride: ctxStage.allowCandidateOverride,
|
|
107
121
|
promoteOpts, cwd, runId, signal, userPrompt, preset,
|
|
108
122
|
isRefinement: ctxStage.isRefinement, historyFilePath, startTime,
|
|
@@ -115,6 +129,32 @@ export async function runMoAPipeline({
|
|
|
115
129
|
return fastRes.payload
|
|
116
130
|
}
|
|
117
131
|
|
|
132
|
+
// Pre-Judge Stage 4.5: Early Exit upon Semantic Consensus (#299)
|
|
133
|
+
if (ctxStage.earlyExitConsensusEnabled && successfulRefs.length > 1) {
|
|
134
|
+
const earlyRes = await stageEarlyConsensus({
|
|
135
|
+
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
136
|
+
referenceOutputs,
|
|
137
|
+
preset,
|
|
138
|
+
userPrompt,
|
|
139
|
+
prices,
|
|
140
|
+
maxTokens: ctxStage.maxTokens,
|
|
141
|
+
cwd,
|
|
142
|
+
runId,
|
|
143
|
+
signal,
|
|
144
|
+
promoteOpts,
|
|
145
|
+
allowCandidateOverride: ctxStage.allowCandidateOverride,
|
|
146
|
+
isRefinement: ctxStage.isRefinement,
|
|
147
|
+
historyFilePath,
|
|
148
|
+
startTime,
|
|
149
|
+
collectedCtx: ctxStage.collectedCtx,
|
|
150
|
+
liveCanvas,
|
|
151
|
+
ctx,
|
|
152
|
+
})
|
|
153
|
+
if (earlyRes.consensusReached) {
|
|
154
|
+
return earlyRes.payload
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
118
158
|
if (signal?.aborted) {
|
|
119
159
|
await cleanMoaWorkspaces(cwd, { runId })
|
|
120
160
|
return { error: new Error('Turn aborted') }
|
|
@@ -123,7 +163,8 @@ export async function runMoAPipeline({
|
|
|
123
163
|
// Stage 5: Consilium Round 2 (Peer Critique)
|
|
124
164
|
const peerRes = await stagePeerCritique({
|
|
125
165
|
isPeerCritiqueEnabled: ctxStage.isPeerCritiqueEnabled,
|
|
126
|
-
successfulRefs
|
|
166
|
+
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
167
|
+
referenceOutputs, preset, prices,
|
|
127
168
|
maxTokens: ctxStage.maxTokens, primaryJudge: ctxStage.primaryJudge,
|
|
128
169
|
userPrompt, isBlindEvaluation: ctxStage.isBlindEvaluation,
|
|
129
170
|
refTemp: ctxStage.refTemp, refTimeoutSec: ctxStage.refTimeoutSec,
|
|
@@ -147,7 +188,9 @@ export async function runMoAPipeline({
|
|
|
147
188
|
|
|
148
189
|
// Stage 6: Multi-Judge Panel
|
|
149
190
|
const mjRes = await stageMultiJudge({
|
|
150
|
-
preset,
|
|
191
|
+
preset,
|
|
192
|
+
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
193
|
+
referenceOutputs, primaryJudge: ctxStage.primaryJudge,
|
|
151
194
|
userPrompt, judgeCriteria: ctxStage.judgeCriteria, prices, maxTokens: ctxStage.maxTokens,
|
|
152
195
|
callLlm, signal, onProgress, cwd, runId, isRefinement: ctxStage.isRefinement,
|
|
153
196
|
isFastMode: ctxStage.isFastMode, collectedCtx: ctxStage.collectedCtx, startTime,
|
|
@@ -155,9 +198,13 @@ export async function runMoAPipeline({
|
|
|
155
198
|
if (!mjRes.ok) return mjRes.abortPayload
|
|
156
199
|
const multiJudgeResult = mjRes.multiJudgeResult
|
|
157
200
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
:
|
|
201
|
+
let compositeMergeDirective = null
|
|
202
|
+
if (preset?.ast_composite_merge_enabled && successfulRefs.length > 1) {
|
|
203
|
+
const astMerge = await buildAstCompositeMergeDirective(successfulRefs, { ctx, blind: Boolean(preset.blind_evaluation) })
|
|
204
|
+
compositeMergeDirective = astMerge.directive
|
|
205
|
+
} else if (preset?.composite_merge_enabled && successfulRefs.length > 1) {
|
|
206
|
+
compositeMergeDirective = buildCompositeBlockDirectives(successfulRefs, { blind: Boolean(preset.blind_evaluation) })
|
|
207
|
+
}
|
|
161
208
|
let visualResult = null
|
|
162
209
|
if (preset?.visual_moa_enabled) {
|
|
163
210
|
visualResult = await runVisualMoaConsensus({
|
|
@@ -171,11 +218,13 @@ export async function runMoAPipeline({
|
|
|
171
218
|
const rawFileDirective = (preset?.interactive_workbench_enabled)
|
|
172
219
|
? formatFileScoresDirective()
|
|
173
220
|
: null
|
|
174
|
-
const fileScoringDirective = [rawFileDirective, visualResult?.directive].filter(Boolean).join("\n\n") || null
|
|
221
|
+
const fileScoringDirective = [rawFileDirective, visualResult?.directive, adversarialTddResult?.directive, syntaxDirective].filter(Boolean).join("\n\n") || null
|
|
175
222
|
|
|
176
223
|
// Stage 7: Synthesis Phase
|
|
177
224
|
const synthRes = await stageSynthesis({
|
|
178
|
-
userPrompt, referenceOutputs,
|
|
225
|
+
userPrompt, referenceOutputs,
|
|
226
|
+
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
227
|
+
judgeCriteria: ctxStage.judgeCriteria,
|
|
179
228
|
isCuratorSynthesis: ctxStage.isCuratorSynthesis, isBlindEvaluation: ctxStage.isBlindEvaluation,
|
|
180
229
|
priorTurnBaseline: ctxStage.priorTurnBaseline, compositeMergeDirective, fileScoringDirective,
|
|
181
230
|
multiJudgeResult, preset, prices, maxTokens: ctxStage.maxTokens, isFastMode: ctxStage.isFastMode,
|
|
@@ -195,7 +244,9 @@ export async function runMoAPipeline({
|
|
|
195
244
|
|
|
196
245
|
// Stage 8: Evaluation, Winner & Promotion
|
|
197
246
|
const promRes = await stagePromotion({
|
|
198
|
-
synthesizedText, referenceOutputs,
|
|
247
|
+
synthesizedText, referenceOutputs,
|
|
248
|
+
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
249
|
+
multiJudgeResult,
|
|
199
250
|
isCuratorSynthesis: ctxStage.isCuratorSynthesis,
|
|
200
251
|
allowCandidateOverride: ctxStage.allowCandidateOverride,
|
|
201
252
|
promoteOpts, cwd, runId, signal, liveCanvas, startTime, chosenJudge, aggUsage,
|
|
@@ -209,3 +260,4 @@ export async function runMoAPipeline({
|
|
|
209
260
|
}
|
|
210
261
|
return promRes.payload
|
|
211
262
|
}
|
|
263
|
+
|