@defai.digital/discussion-domain 13.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +214 -0
- package/dist/consensus/index.d.ts +23 -0
- package/dist/consensus/index.d.ts.map +1 -0
- package/dist/consensus/index.js +45 -0
- package/dist/consensus/index.js.map +1 -0
- package/dist/consensus/moderator.d.ts +15 -0
- package/dist/consensus/moderator.d.ts.map +1 -0
- package/dist/consensus/moderator.js +201 -0
- package/dist/consensus/moderator.js.map +1 -0
- package/dist/consensus/synthesis.d.ts +15 -0
- package/dist/consensus/synthesis.d.ts.map +1 -0
- package/dist/consensus/synthesis.js +161 -0
- package/dist/consensus/synthesis.js.map +1 -0
- package/dist/consensus/voting.d.ts +17 -0
- package/dist/consensus/voting.d.ts.map +1 -0
- package/dist/consensus/voting.js +168 -0
- package/dist/consensus/voting.js.map +1 -0
- package/dist/executor.d.ts +73 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +223 -0
- package/dist/executor.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/critique.d.ts +20 -0
- package/dist/patterns/critique.d.ts.map +1 -0
- package/dist/patterns/critique.js +338 -0
- package/dist/patterns/critique.js.map +1 -0
- package/dist/patterns/debate.d.ts +20 -0
- package/dist/patterns/debate.d.ts.map +1 -0
- package/dist/patterns/debate.js +236 -0
- package/dist/patterns/debate.js.map +1 -0
- package/dist/patterns/index.d.ts +25 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +49 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/round-robin.d.ts +13 -0
- package/dist/patterns/round-robin.d.ts.map +1 -0
- package/dist/patterns/round-robin.js +160 -0
- package/dist/patterns/round-robin.js.map +1 -0
- package/dist/patterns/synthesis.d.ts +20 -0
- package/dist/patterns/synthesis.d.ts.map +1 -0
- package/dist/patterns/synthesis.js +250 -0
- package/dist/patterns/synthesis.js.map +1 -0
- package/dist/patterns/voting.d.ts +19 -0
- package/dist/patterns/voting.d.ts.map +1 -0
- package/dist/patterns/voting.js +186 -0
- package/dist/patterns/voting.js.map +1 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +21 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/templates.d.ts +55 -0
- package/dist/prompts/templates.d.ts.map +1 -0
- package/dist/prompts/templates.js +346 -0
- package/dist/prompts/templates.js.map +1 -0
- package/dist/provider-bridge.d.ts +115 -0
- package/dist/provider-bridge.d.ts.map +1 -0
- package/dist/provider-bridge.js +215 -0
- package/dist/provider-bridge.js.map +1 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +102 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/consensus/index.ts +52 -0
- package/src/consensus/moderator.ts +242 -0
- package/src/consensus/synthesis.ts +202 -0
- package/src/consensus/voting.ts +221 -0
- package/src/executor.ts +338 -0
- package/src/index.ts +69 -0
- package/src/patterns/critique.ts +465 -0
- package/src/patterns/debate.ts +340 -0
- package/src/patterns/index.ts +56 -0
- package/src/patterns/round-robin.ts +223 -0
- package/src/patterns/synthesis.ts +353 -0
- package/src/patterns/voting.ts +266 -0
- package/src/prompts/index.ts +41 -0
- package/src/prompts/templates.ts +381 -0
- package/src/provider-bridge.ts +346 -0
- package/src/types.ts +375 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesis Pattern Executor
|
|
3
|
+
*
|
|
4
|
+
* Models give parallel perspectives, then cross-discuss, then one synthesizes.
|
|
5
|
+
* This is the default and most commonly used pattern.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Round 1: All providers respond in parallel with initial perspectives
|
|
9
|
+
* 2. Round 2: Each provider responds to others' perspectives
|
|
10
|
+
* 3. (Optional) Additional cross-discussion rounds
|
|
11
|
+
* 4. Final synthesis by designated synthesizer (default: claude)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { DiscussionRound, DebateRole } from '@defai.digital/contracts';
|
|
15
|
+
import type {
|
|
16
|
+
PatternExecutor,
|
|
17
|
+
PatternExecutionContext,
|
|
18
|
+
PatternExecutionResult,
|
|
19
|
+
} from '../types.js';
|
|
20
|
+
import {
|
|
21
|
+
SYNTHESIS_INITIAL,
|
|
22
|
+
SYNTHESIS_CROSS_DISCUSS,
|
|
23
|
+
interpolate,
|
|
24
|
+
formatPreviousResponses,
|
|
25
|
+
getProviderSystemPrompt,
|
|
26
|
+
} from '../prompts/templates.js';
|
|
27
|
+
|
|
28
|
+
// Local type for discussion DiscussionProviderResponse (avoids conflict with provider/v1 DiscussionProviderResponse)
|
|
29
|
+
interface DiscussionProviderResponse {
|
|
30
|
+
provider: string;
|
|
31
|
+
content: string;
|
|
32
|
+
round: number;
|
|
33
|
+
role?: DebateRole | undefined;
|
|
34
|
+
confidence?: number | undefined;
|
|
35
|
+
vote?: string | undefined;
|
|
36
|
+
timestamp: string;
|
|
37
|
+
durationMs: number;
|
|
38
|
+
tokenCount?: number | undefined;
|
|
39
|
+
truncated?: boolean | undefined;
|
|
40
|
+
error?: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class SynthesisPattern implements PatternExecutor {
|
|
44
|
+
readonly pattern = 'synthesis' as const;
|
|
45
|
+
|
|
46
|
+
async execute(context: PatternExecutionContext): Promise<PatternExecutionResult> {
|
|
47
|
+
const startTime = Date.now();
|
|
48
|
+
const { config, providerExecutor, availableProviders, abortSignal, onProgress } = context;
|
|
49
|
+
|
|
50
|
+
const rounds: DiscussionRound[] = [];
|
|
51
|
+
const participatingProviders = new Set<string>();
|
|
52
|
+
const failedProviders = new Set<string>();
|
|
53
|
+
|
|
54
|
+
// Filter to available providers
|
|
55
|
+
const providers = config.providers.filter(p => availableProviders.includes(p));
|
|
56
|
+
|
|
57
|
+
if (providers.length < config.minProviders) {
|
|
58
|
+
return {
|
|
59
|
+
rounds: [],
|
|
60
|
+
participatingProviders: [],
|
|
61
|
+
failedProviders: config.providers.filter(p => !availableProviders.includes(p)),
|
|
62
|
+
totalDurationMs: Date.now() - startTime,
|
|
63
|
+
success: false,
|
|
64
|
+
error: `Only ${providers.length} providers available, need ${config.minProviders}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Round 1: Gather initial perspectives in parallel
|
|
69
|
+
onProgress?.({
|
|
70
|
+
type: 'round_start',
|
|
71
|
+
round: 1,
|
|
72
|
+
message: 'Gathering initial perspectives in parallel',
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const initialRound = await this.executeParallelRound(
|
|
77
|
+
1,
|
|
78
|
+
providers,
|
|
79
|
+
config,
|
|
80
|
+
providerExecutor,
|
|
81
|
+
[],
|
|
82
|
+
abortSignal,
|
|
83
|
+
onProgress
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
rounds.push(initialRound.round);
|
|
87
|
+
initialRound.succeeded.forEach(p => participatingProviders.add(p));
|
|
88
|
+
initialRound.failed.forEach(p => failedProviders.add(p));
|
|
89
|
+
|
|
90
|
+
onProgress?.({
|
|
91
|
+
type: 'round_complete',
|
|
92
|
+
round: 1,
|
|
93
|
+
message: `Initial perspectives gathered from ${initialRound.succeeded.length} providers`,
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Check if enough providers participated
|
|
98
|
+
if (participatingProviders.size < config.minProviders) {
|
|
99
|
+
return {
|
|
100
|
+
rounds,
|
|
101
|
+
participatingProviders: Array.from(participatingProviders),
|
|
102
|
+
failedProviders: Array.from(failedProviders),
|
|
103
|
+
totalDurationMs: Date.now() - startTime,
|
|
104
|
+
success: false,
|
|
105
|
+
error: `Only ${participatingProviders.size} providers succeeded in round 1`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Additional rounds: Cross-discussion
|
|
110
|
+
for (let roundNum = 2; roundNum <= config.rounds; roundNum++) {
|
|
111
|
+
if (abortSignal?.aborted) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
onProgress?.({
|
|
116
|
+
type: 'round_start',
|
|
117
|
+
round: roundNum,
|
|
118
|
+
message: `Starting cross-discussion round ${roundNum}`,
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const crossRound = await this.executeCrossDiscussionRound(
|
|
123
|
+
roundNum,
|
|
124
|
+
providers.filter(p => participatingProviders.has(p)),
|
|
125
|
+
config,
|
|
126
|
+
providerExecutor,
|
|
127
|
+
rounds,
|
|
128
|
+
abortSignal,
|
|
129
|
+
onProgress
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
rounds.push(crossRound.round);
|
|
133
|
+
crossRound.failed.forEach(p => failedProviders.add(p));
|
|
134
|
+
|
|
135
|
+
onProgress?.({
|
|
136
|
+
type: 'round_complete',
|
|
137
|
+
round: roundNum,
|
|
138
|
+
message: `Cross-discussion round ${roundNum} complete`,
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
rounds,
|
|
145
|
+
participatingProviders: Array.from(participatingProviders),
|
|
146
|
+
failedProviders: Array.from(failedProviders),
|
|
147
|
+
totalDurationMs: Date.now() - startTime,
|
|
148
|
+
success: participatingProviders.size >= config.minProviders,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private async executeParallelRound(
|
|
153
|
+
roundNum: number,
|
|
154
|
+
providers: string[],
|
|
155
|
+
config: PatternExecutionContext['config'],
|
|
156
|
+
providerExecutor: PatternExecutionContext['providerExecutor'],
|
|
157
|
+
_previousRounds: DiscussionRound[],
|
|
158
|
+
abortSignal?: AbortSignal,
|
|
159
|
+
onProgress?: PatternExecutionContext['onProgress']
|
|
160
|
+
): Promise<{ round: DiscussionRound; succeeded: string[]; failed: string[] }> {
|
|
161
|
+
const roundStart = Date.now();
|
|
162
|
+
const succeeded: string[] = [];
|
|
163
|
+
const failed: string[] = [];
|
|
164
|
+
|
|
165
|
+
// Build prompts for all providers
|
|
166
|
+
const prompt = interpolate(SYNTHESIS_INITIAL, {
|
|
167
|
+
topic: config.prompt,
|
|
168
|
+
context: config.context ? `\n## Additional Context\n${config.context}` : '',
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Execute all providers in parallel
|
|
172
|
+
const promises = providers.map(async (providerId) => {
|
|
173
|
+
if (abortSignal?.aborted) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
onProgress?.({
|
|
178
|
+
type: 'provider_start',
|
|
179
|
+
round: roundNum,
|
|
180
|
+
provider: providerId,
|
|
181
|
+
timestamp: new Date().toISOString(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const responseStart = Date.now();
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const result = await providerExecutor.execute({
|
|
188
|
+
providerId,
|
|
189
|
+
prompt: config.providerPrompts?.[providerId] || prompt,
|
|
190
|
+
systemPrompt: getProviderSystemPrompt(providerId),
|
|
191
|
+
temperature: config.temperature,
|
|
192
|
+
timeoutMs: config.providerTimeout,
|
|
193
|
+
abortSignal,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const response: DiscussionProviderResponse = {
|
|
197
|
+
provider: providerId,
|
|
198
|
+
content: result.success ? result.content || '' : '',
|
|
199
|
+
round: roundNum,
|
|
200
|
+
timestamp: new Date().toISOString(),
|
|
201
|
+
durationMs: result.durationMs,
|
|
202
|
+
tokenCount: result.tokenCount,
|
|
203
|
+
truncated: result.truncated,
|
|
204
|
+
error: result.success ? undefined : result.error,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (result.success) {
|
|
208
|
+
succeeded.push(providerId);
|
|
209
|
+
} else {
|
|
210
|
+
failed.push(providerId);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
onProgress?.({
|
|
214
|
+
type: 'provider_complete',
|
|
215
|
+
round: roundNum,
|
|
216
|
+
provider: providerId,
|
|
217
|
+
message: result.success ? 'completed' : `failed: ${result.error}`,
|
|
218
|
+
timestamp: new Date().toISOString(),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return response;
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
225
|
+
failed.push(providerId);
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
provider: providerId,
|
|
229
|
+
content: '',
|
|
230
|
+
round: roundNum,
|
|
231
|
+
timestamp: new Date().toISOString(),
|
|
232
|
+
durationMs: Date.now() - responseStart,
|
|
233
|
+
error: errorMessage,
|
|
234
|
+
} as DiscussionProviderResponse;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const responses = await Promise.all(promises);
|
|
239
|
+
const validResponses = responses.filter((r): r is DiscussionProviderResponse => r !== null);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
round: {
|
|
243
|
+
roundNumber: roundNum,
|
|
244
|
+
responses: validResponses,
|
|
245
|
+
durationMs: Date.now() - roundStart,
|
|
246
|
+
},
|
|
247
|
+
succeeded,
|
|
248
|
+
failed,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private async executeCrossDiscussionRound(
|
|
253
|
+
roundNum: number,
|
|
254
|
+
providers: string[],
|
|
255
|
+
config: PatternExecutionContext['config'],
|
|
256
|
+
providerExecutor: PatternExecutionContext['providerExecutor'],
|
|
257
|
+
previousRounds: DiscussionRound[],
|
|
258
|
+
abortSignal?: AbortSignal,
|
|
259
|
+
onProgress?: PatternExecutionContext['onProgress']
|
|
260
|
+
): Promise<{ round: DiscussionRound; failed: string[] }> {
|
|
261
|
+
const roundStart = Date.now();
|
|
262
|
+
const failed: string[] = [];
|
|
263
|
+
|
|
264
|
+
// Execute all providers in parallel
|
|
265
|
+
const promises = providers.map(async (providerId) => {
|
|
266
|
+
if (abortSignal?.aborted) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
onProgress?.({
|
|
271
|
+
type: 'provider_start',
|
|
272
|
+
round: roundNum,
|
|
273
|
+
provider: providerId,
|
|
274
|
+
timestamp: new Date().toISOString(),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const responseStart = Date.now();
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
// Get other providers' responses from previous round
|
|
281
|
+
const previousRound = previousRounds[previousRounds.length - 1];
|
|
282
|
+
const otherResponses = previousRound?.responses.filter(r => r.provider !== providerId) || [];
|
|
283
|
+
|
|
284
|
+
const prompt = interpolate(SYNTHESIS_CROSS_DISCUSS, {
|
|
285
|
+
topic: config.prompt,
|
|
286
|
+
otherPerspectives: formatPreviousResponses(
|
|
287
|
+
otherResponses.map(r => ({
|
|
288
|
+
provider: r.provider,
|
|
289
|
+
content: r.content,
|
|
290
|
+
}))
|
|
291
|
+
),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const result = await providerExecutor.execute({
|
|
295
|
+
providerId,
|
|
296
|
+
prompt,
|
|
297
|
+
systemPrompt: getProviderSystemPrompt(providerId),
|
|
298
|
+
temperature: config.temperature,
|
|
299
|
+
timeoutMs: config.providerTimeout,
|
|
300
|
+
abortSignal,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const response: DiscussionProviderResponse = {
|
|
304
|
+
provider: providerId,
|
|
305
|
+
content: result.success ? result.content || '' : '',
|
|
306
|
+
round: roundNum,
|
|
307
|
+
timestamp: new Date().toISOString(),
|
|
308
|
+
durationMs: result.durationMs,
|
|
309
|
+
tokenCount: result.tokenCount,
|
|
310
|
+
error: result.success ? undefined : result.error,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (!result.success) {
|
|
314
|
+
failed.push(providerId);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
onProgress?.({
|
|
318
|
+
type: 'provider_complete',
|
|
319
|
+
round: roundNum,
|
|
320
|
+
provider: providerId,
|
|
321
|
+
timestamp: new Date().toISOString(),
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return response;
|
|
325
|
+
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
328
|
+
failed.push(providerId);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
provider: providerId,
|
|
332
|
+
content: '',
|
|
333
|
+
round: roundNum,
|
|
334
|
+
timestamp: new Date().toISOString(),
|
|
335
|
+
durationMs: Date.now() - responseStart,
|
|
336
|
+
error: errorMessage,
|
|
337
|
+
} as DiscussionProviderResponse;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const responses = await Promise.all(promises);
|
|
342
|
+
const validResponses = responses.filter((r): r is DiscussionProviderResponse => r !== null);
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
round: {
|
|
346
|
+
roundNumber: roundNum,
|
|
347
|
+
responses: validResponses,
|
|
348
|
+
durationMs: Date.now() - roundStart,
|
|
349
|
+
},
|
|
350
|
+
failed,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voting Pattern Executor
|
|
3
|
+
*
|
|
4
|
+
* Each model evaluates options and votes with confidence scores.
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* 1. Each provider evaluates all options in parallel
|
|
8
|
+
* 2. Votes are collected with confidence scores
|
|
9
|
+
* 3. Results are tallied (raw and confidence-weighted)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { DiscussionRound, DebateRole } from '@defai.digital/contracts';
|
|
13
|
+
import type {
|
|
14
|
+
PatternExecutor,
|
|
15
|
+
PatternExecutionContext,
|
|
16
|
+
PatternExecutionResult,
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
import {
|
|
19
|
+
VOTING_EVALUATE,
|
|
20
|
+
interpolate,
|
|
21
|
+
formatVotingOptions,
|
|
22
|
+
getProviderSystemPrompt,
|
|
23
|
+
} from '../prompts/templates.js';
|
|
24
|
+
|
|
25
|
+
// Local type for discussion DiscussionProviderResponse (avoids conflict with provider/v1 DiscussionProviderResponse)
|
|
26
|
+
interface DiscussionProviderResponse {
|
|
27
|
+
provider: string;
|
|
28
|
+
content: string;
|
|
29
|
+
round: number;
|
|
30
|
+
role?: DebateRole | undefined;
|
|
31
|
+
confidence?: number | undefined;
|
|
32
|
+
vote?: string | undefined;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
durationMs: number;
|
|
35
|
+
tokenCount?: number | undefined;
|
|
36
|
+
truncated?: boolean | undefined;
|
|
37
|
+
error?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class VotingPattern implements PatternExecutor {
|
|
41
|
+
readonly pattern = 'voting' as const;
|
|
42
|
+
|
|
43
|
+
async execute(context: PatternExecutionContext): Promise<PatternExecutionResult> {
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
const { config, providerExecutor, availableProviders, abortSignal, onProgress } = context;
|
|
46
|
+
|
|
47
|
+
const rounds: DiscussionRound[] = [];
|
|
48
|
+
const participatingProviders = new Set<string>();
|
|
49
|
+
const failedProviders = new Set<string>();
|
|
50
|
+
|
|
51
|
+
// Filter to available providers
|
|
52
|
+
const providers = config.providers.filter(p => availableProviders.includes(p));
|
|
53
|
+
|
|
54
|
+
if (providers.length < config.minProviders) {
|
|
55
|
+
return {
|
|
56
|
+
rounds: [],
|
|
57
|
+
participatingProviders: [],
|
|
58
|
+
failedProviders: config.providers.filter(p => !availableProviders.includes(p)),
|
|
59
|
+
totalDurationMs: Date.now() - startTime,
|
|
60
|
+
success: false,
|
|
61
|
+
error: `Only ${providers.length} providers available, need ${config.minProviders}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Extract options from prompt (look for numbered list or explicit options)
|
|
66
|
+
const options = this.extractOptions(config.prompt);
|
|
67
|
+
|
|
68
|
+
// Round 1: All providers vote in parallel
|
|
69
|
+
onProgress?.({
|
|
70
|
+
type: 'round_start',
|
|
71
|
+
round: 1,
|
|
72
|
+
message: 'Providers evaluating options and voting',
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const votingRound = await this.executeVotingRound(
|
|
77
|
+
1,
|
|
78
|
+
providers,
|
|
79
|
+
options,
|
|
80
|
+
config,
|
|
81
|
+
providerExecutor,
|
|
82
|
+
abortSignal,
|
|
83
|
+
onProgress
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
rounds.push(votingRound.round);
|
|
87
|
+
votingRound.succeeded.forEach(p => participatingProviders.add(p));
|
|
88
|
+
votingRound.failed.forEach(p => failedProviders.add(p));
|
|
89
|
+
|
|
90
|
+
onProgress?.({
|
|
91
|
+
type: 'round_complete',
|
|
92
|
+
round: 1,
|
|
93
|
+
message: `Collected ${votingRound.succeeded.length} votes`,
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
rounds,
|
|
99
|
+
participatingProviders: Array.from(participatingProviders),
|
|
100
|
+
failedProviders: Array.from(failedProviders),
|
|
101
|
+
totalDurationMs: Date.now() - startTime,
|
|
102
|
+
success: participatingProviders.size >= config.minProviders,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private extractOptions(prompt: string): string[] {
|
|
107
|
+
// Try to extract numbered options (1. Option, 2. Option, etc.)
|
|
108
|
+
const numberedPattern = /^\d+\.\s*(.+)$/gm;
|
|
109
|
+
const matches = [...prompt.matchAll(numberedPattern)];
|
|
110
|
+
|
|
111
|
+
if (matches.length >= 2) {
|
|
112
|
+
return matches.map(m => m[1] ? m[1].trim() : '').filter(Boolean);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Try to extract from "Options: A, B, C" format
|
|
116
|
+
const optionsMatch = /options?:\s*(.+?)(?:\n|$)/i.exec(prompt);
|
|
117
|
+
if (optionsMatch?.[1]) {
|
|
118
|
+
return optionsMatch[1].split(/[,;]/).map(o => o.trim()).filter(Boolean);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Default: create Yes/No options
|
|
122
|
+
return ['Yes', 'No'];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private async executeVotingRound(
|
|
126
|
+
roundNum: number,
|
|
127
|
+
providers: string[],
|
|
128
|
+
options: string[],
|
|
129
|
+
config: PatternExecutionContext['config'],
|
|
130
|
+
providerExecutor: PatternExecutionContext['providerExecutor'],
|
|
131
|
+
abortSignal?: AbortSignal,
|
|
132
|
+
onProgress?: PatternExecutionContext['onProgress']
|
|
133
|
+
): Promise<{ round: DiscussionRound; succeeded: string[]; failed: string[] }> {
|
|
134
|
+
const roundStart = Date.now();
|
|
135
|
+
const succeeded: string[] = [];
|
|
136
|
+
const failed: string[] = [];
|
|
137
|
+
|
|
138
|
+
const formattedOptions = formatVotingOptions(options);
|
|
139
|
+
const prompt = interpolate(VOTING_EVALUATE, {
|
|
140
|
+
topic: config.prompt,
|
|
141
|
+
options: formattedOptions,
|
|
142
|
+
context: config.context || '',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Execute all votes in parallel
|
|
146
|
+
const promises = providers.map(async (providerId) => {
|
|
147
|
+
if (abortSignal?.aborted) return null;
|
|
148
|
+
|
|
149
|
+
onProgress?.({
|
|
150
|
+
type: 'provider_start',
|
|
151
|
+
round: roundNum,
|
|
152
|
+
provider: providerId,
|
|
153
|
+
timestamp: new Date().toISOString(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const responseStart = Date.now();
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const result = await providerExecutor.execute({
|
|
160
|
+
providerId,
|
|
161
|
+
prompt,
|
|
162
|
+
systemPrompt: getProviderSystemPrompt(providerId),
|
|
163
|
+
temperature: config.temperature,
|
|
164
|
+
timeoutMs: config.providerTimeout,
|
|
165
|
+
abortSignal,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Parse vote from response
|
|
169
|
+
const vote = result.success ? this.parseVote(result.content || '', options) : null;
|
|
170
|
+
|
|
171
|
+
const response: DiscussionProviderResponse = {
|
|
172
|
+
provider: providerId,
|
|
173
|
+
content: result.success ? result.content || '' : '',
|
|
174
|
+
round: roundNum,
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
durationMs: result.durationMs,
|
|
177
|
+
tokenCount: result.tokenCount,
|
|
178
|
+
confidence: vote?.confidence,
|
|
179
|
+
vote: vote?.choice,
|
|
180
|
+
error: result.success ? undefined : result.error,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if (result.success && vote) {
|
|
184
|
+
succeeded.push(providerId);
|
|
185
|
+
} else {
|
|
186
|
+
failed.push(providerId);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onProgress?.({
|
|
190
|
+
type: 'provider_complete',
|
|
191
|
+
round: roundNum,
|
|
192
|
+
provider: providerId,
|
|
193
|
+
message: vote ? `Voted: ${vote.choice} (${Math.round(vote.confidence * 100)}%)` : 'failed',
|
|
194
|
+
timestamp: new Date().toISOString(),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return response;
|
|
198
|
+
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
201
|
+
failed.push(providerId);
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
provider: providerId,
|
|
205
|
+
content: '',
|
|
206
|
+
round: roundNum,
|
|
207
|
+
timestamp: new Date().toISOString(),
|
|
208
|
+
durationMs: Date.now() - responseStart,
|
|
209
|
+
error: errorMessage,
|
|
210
|
+
} as DiscussionProviderResponse;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const responses = await Promise.all(promises);
|
|
215
|
+
const validResponses = responses.filter((r): r is DiscussionProviderResponse => r !== null);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
round: {
|
|
219
|
+
roundNumber: roundNum,
|
|
220
|
+
responses: validResponses,
|
|
221
|
+
durationMs: Date.now() - roundStart,
|
|
222
|
+
},
|
|
223
|
+
succeeded,
|
|
224
|
+
failed,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private parseVote(
|
|
229
|
+
content: string,
|
|
230
|
+
options: string[]
|
|
231
|
+
): { choice: string; confidence: number; reasoning?: string | undefined } | null {
|
|
232
|
+
// Try to find "Your Vote: [option]" pattern
|
|
233
|
+
const voteMatch = /your vote:\s*\[?([^\]\n]+)\]?/i.exec(content);
|
|
234
|
+
const choice = voteMatch?.[1] ? voteMatch[1].trim() : null;
|
|
235
|
+
|
|
236
|
+
// Try to find confidence pattern
|
|
237
|
+
const confidenceMatch = /confidence:\s*\[?(\d+)%?\]?/i.exec(content);
|
|
238
|
+
const confidence = confidenceMatch?.[1] ? parseInt(confidenceMatch[1], 10) / 100 : 0.5;
|
|
239
|
+
|
|
240
|
+
// Try to find reasoning
|
|
241
|
+
const reasoningMatch = /reasoning:\s*(.+?)(?=\n\n|$)/is.exec(content);
|
|
242
|
+
const reasoning = reasoningMatch?.[1] ? reasoningMatch[1].trim() : undefined;
|
|
243
|
+
|
|
244
|
+
if (!choice) {
|
|
245
|
+
// Try to find which option is mentioned most prominently
|
|
246
|
+
for (const option of options) {
|
|
247
|
+
if (content.toLowerCase().includes(option.toLowerCase())) {
|
|
248
|
+
return { choice: option, confidence: Math.min(confidence, 0.7), reasoning };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Normalize choice to match options
|
|
255
|
+
const normalizedChoice = options.find(
|
|
256
|
+
o => o.toLowerCase() === choice.toLowerCase() ||
|
|
257
|
+
choice.toLowerCase().includes(o.toLowerCase())
|
|
258
|
+
) || choice;
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
choice: normalizedChoice,
|
|
262
|
+
confidence: Math.max(0, Math.min(1, confidence)),
|
|
263
|
+
reasoning,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Templates Module
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all prompt templates and helper functions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
// Provider prompts
|
|
9
|
+
getProviderSystemPrompt,
|
|
10
|
+
|
|
11
|
+
// Round-robin templates
|
|
12
|
+
ROUND_ROBIN_INITIAL,
|
|
13
|
+
ROUND_ROBIN_FOLLOWUP,
|
|
14
|
+
|
|
15
|
+
// Synthesis templates
|
|
16
|
+
SYNTHESIS_INITIAL,
|
|
17
|
+
SYNTHESIS_CROSS_DISCUSS,
|
|
18
|
+
SYNTHESIS_FINAL,
|
|
19
|
+
|
|
20
|
+
// Debate templates
|
|
21
|
+
DEBATE_PROPONENT,
|
|
22
|
+
DEBATE_OPPONENT,
|
|
23
|
+
DEBATE_REBUTTAL,
|
|
24
|
+
DEBATE_JUDGE,
|
|
25
|
+
|
|
26
|
+
// Critique templates
|
|
27
|
+
CRITIQUE_PROPOSAL,
|
|
28
|
+
CRITIQUE_REVIEW,
|
|
29
|
+
CRITIQUE_REVISION,
|
|
30
|
+
|
|
31
|
+
// Voting templates
|
|
32
|
+
VOTING_EVALUATE,
|
|
33
|
+
VOTING_TALLY,
|
|
34
|
+
|
|
35
|
+
// Helper functions
|
|
36
|
+
interpolate,
|
|
37
|
+
formatPreviousResponses,
|
|
38
|
+
formatVotingOptions,
|
|
39
|
+
formatVotes,
|
|
40
|
+
getRolePromptModifier,
|
|
41
|
+
} from './templates.js';
|