@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,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debate Pattern Executor
|
|
3
|
+
*
|
|
4
|
+
* Models argue opposing positions with an impartial judge.
|
|
5
|
+
* Requires role assignments: proponent, opponent, judge.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Round 1: Proponent presents arguments
|
|
9
|
+
* 2. Round 2: Opponent responds with counter-arguments
|
|
10
|
+
* 3. Round 3+: Rebuttals (alternating)
|
|
11
|
+
* 4. Final: Judge evaluates and declares winner
|
|
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
|
+
DEBATE_PROPONENT,
|
|
22
|
+
DEBATE_OPPONENT,
|
|
23
|
+
DEBATE_REBUTTAL,
|
|
24
|
+
interpolate,
|
|
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 DebatePattern implements PatternExecutor {
|
|
44
|
+
readonly pattern = 'debate' 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
|
+
// Validate roles are assigned
|
|
51
|
+
if (!config.roles) {
|
|
52
|
+
return {
|
|
53
|
+
rounds: [],
|
|
54
|
+
participatingProviders: [],
|
|
55
|
+
failedProviders: [],
|
|
56
|
+
totalDurationMs: Date.now() - startTime,
|
|
57
|
+
success: false,
|
|
58
|
+
error: 'Debate pattern requires role assignments',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rounds: DiscussionRound[] = [];
|
|
63
|
+
const participatingProviders = new Set<string>();
|
|
64
|
+
const failedProviders = new Set<string>();
|
|
65
|
+
|
|
66
|
+
// Get role assignments
|
|
67
|
+
const proponentId = this.findProviderByRole(config.roles, 'proponent');
|
|
68
|
+
const opponentId = this.findProviderByRole(config.roles, 'opponent');
|
|
69
|
+
const judgeId = this.findProviderByRole(config.roles, 'judge');
|
|
70
|
+
|
|
71
|
+
if (!proponentId || !opponentId) {
|
|
72
|
+
return {
|
|
73
|
+
rounds: [],
|
|
74
|
+
participatingProviders: [],
|
|
75
|
+
failedProviders: [],
|
|
76
|
+
totalDurationMs: Date.now() - startTime,
|
|
77
|
+
success: false,
|
|
78
|
+
error: 'Debate requires proponent and opponent roles',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check availability
|
|
83
|
+
const requiredProviders = [proponentId, opponentId, judgeId].filter(Boolean) as string[];
|
|
84
|
+
const unavailable = requiredProviders.filter(p => !availableProviders.includes(p));
|
|
85
|
+
if (unavailable.length > 0) {
|
|
86
|
+
return {
|
|
87
|
+
rounds: [],
|
|
88
|
+
participatingProviders: [],
|
|
89
|
+
failedProviders: unavailable,
|
|
90
|
+
totalDurationMs: Date.now() - startTime,
|
|
91
|
+
success: false,
|
|
92
|
+
error: `Required providers unavailable: ${unavailable.join(', ')}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let proponentArguments = '';
|
|
97
|
+
let opponentArguments = '';
|
|
98
|
+
|
|
99
|
+
// Round 1: Proponent's opening arguments
|
|
100
|
+
onProgress?.({
|
|
101
|
+
type: 'round_start',
|
|
102
|
+
round: 1,
|
|
103
|
+
message: 'Proponent presenting opening arguments',
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const round1 = await this.executeDebateRound(
|
|
108
|
+
1,
|
|
109
|
+
proponentId,
|
|
110
|
+
'proponent',
|
|
111
|
+
config,
|
|
112
|
+
providerExecutor,
|
|
113
|
+
interpolate(DEBATE_PROPONENT, {
|
|
114
|
+
topic: config.prompt,
|
|
115
|
+
context: config.context || '',
|
|
116
|
+
}),
|
|
117
|
+
abortSignal,
|
|
118
|
+
onProgress
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
rounds.push(round1.round);
|
|
122
|
+
if (round1.success) {
|
|
123
|
+
participatingProviders.add(proponentId);
|
|
124
|
+
proponentArguments = round1.content;
|
|
125
|
+
} else {
|
|
126
|
+
failedProviders.add(proponentId);
|
|
127
|
+
return {
|
|
128
|
+
rounds,
|
|
129
|
+
participatingProviders: Array.from(participatingProviders),
|
|
130
|
+
failedProviders: Array.from(failedProviders),
|
|
131
|
+
totalDurationMs: Date.now() - startTime,
|
|
132
|
+
success: false,
|
|
133
|
+
error: 'Proponent failed to present arguments',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Round 2: Opponent's counter-arguments
|
|
138
|
+
onProgress?.({
|
|
139
|
+
type: 'round_start',
|
|
140
|
+
round: 2,
|
|
141
|
+
message: 'Opponent presenting counter-arguments',
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const round2 = await this.executeDebateRound(
|
|
146
|
+
2,
|
|
147
|
+
opponentId,
|
|
148
|
+
'opponent',
|
|
149
|
+
config,
|
|
150
|
+
providerExecutor,
|
|
151
|
+
interpolate(DEBATE_OPPONENT, {
|
|
152
|
+
topic: config.prompt,
|
|
153
|
+
context: config.context || '',
|
|
154
|
+
proponentArguments,
|
|
155
|
+
}),
|
|
156
|
+
abortSignal,
|
|
157
|
+
onProgress
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
rounds.push(round2.round);
|
|
161
|
+
if (round2.success) {
|
|
162
|
+
participatingProviders.add(opponentId);
|
|
163
|
+
opponentArguments = round2.content;
|
|
164
|
+
} else {
|
|
165
|
+
failedProviders.add(opponentId);
|
|
166
|
+
return {
|
|
167
|
+
rounds,
|
|
168
|
+
participatingProviders: Array.from(participatingProviders),
|
|
169
|
+
failedProviders: Array.from(failedProviders),
|
|
170
|
+
totalDurationMs: Date.now() - startTime,
|
|
171
|
+
success: false,
|
|
172
|
+
error: 'Opponent failed to present counter-arguments',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Additional rounds for rebuttals
|
|
177
|
+
if (config.rounds > 2) {
|
|
178
|
+
// Proponent rebuttal
|
|
179
|
+
onProgress?.({
|
|
180
|
+
type: 'round_start',
|
|
181
|
+
round: 3,
|
|
182
|
+
message: 'Proponent rebuttal',
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const round3 = await this.executeDebateRound(
|
|
187
|
+
3,
|
|
188
|
+
proponentId,
|
|
189
|
+
'proponent',
|
|
190
|
+
config,
|
|
191
|
+
providerExecutor,
|
|
192
|
+
interpolate(DEBATE_REBUTTAL, {
|
|
193
|
+
topic: config.prompt,
|
|
194
|
+
proponentArguments,
|
|
195
|
+
opponentArguments,
|
|
196
|
+
role: 'PROPONENT',
|
|
197
|
+
}),
|
|
198
|
+
abortSignal,
|
|
199
|
+
onProgress
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
rounds.push(round3.round);
|
|
203
|
+
|
|
204
|
+
// Opponent rebuttal
|
|
205
|
+
if (config.rounds > 3) {
|
|
206
|
+
onProgress?.({
|
|
207
|
+
type: 'round_start',
|
|
208
|
+
round: 4,
|
|
209
|
+
message: 'Opponent rebuttal',
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const round4 = await this.executeDebateRound(
|
|
214
|
+
4,
|
|
215
|
+
opponentId,
|
|
216
|
+
'opponent',
|
|
217
|
+
config,
|
|
218
|
+
providerExecutor,
|
|
219
|
+
interpolate(DEBATE_REBUTTAL, {
|
|
220
|
+
topic: config.prompt,
|
|
221
|
+
proponentArguments,
|
|
222
|
+
opponentArguments,
|
|
223
|
+
role: 'OPPONENT',
|
|
224
|
+
}),
|
|
225
|
+
abortSignal,
|
|
226
|
+
onProgress
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
rounds.push(round4.round);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Add judge if present (they participate but don't contribute rounds until consensus)
|
|
234
|
+
if (judgeId && availableProviders.includes(judgeId)) {
|
|
235
|
+
participatingProviders.add(judgeId);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
rounds,
|
|
240
|
+
participatingProviders: Array.from(participatingProviders),
|
|
241
|
+
failedProviders: Array.from(failedProviders),
|
|
242
|
+
totalDurationMs: Date.now() - startTime,
|
|
243
|
+
success: participatingProviders.size >= 2,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private findProviderByRole(
|
|
248
|
+
roles: Record<string, DebateRole>,
|
|
249
|
+
targetRole: DebateRole
|
|
250
|
+
): string | undefined {
|
|
251
|
+
return Object.entries(roles).find(([_, role]) => role === targetRole)?.[0];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private async executeDebateRound(
|
|
255
|
+
roundNum: number,
|
|
256
|
+
providerId: string,
|
|
257
|
+
role: DebateRole,
|
|
258
|
+
config: PatternExecutionContext['config'],
|
|
259
|
+
providerExecutor: PatternExecutionContext['providerExecutor'],
|
|
260
|
+
prompt: string,
|
|
261
|
+
abortSignal?: AbortSignal,
|
|
262
|
+
onProgress?: PatternExecutionContext['onProgress']
|
|
263
|
+
): Promise<{ round: DiscussionRound; success: boolean; content: string }> {
|
|
264
|
+
const roundStart = Date.now();
|
|
265
|
+
|
|
266
|
+
onProgress?.({
|
|
267
|
+
type: 'provider_start',
|
|
268
|
+
round: roundNum,
|
|
269
|
+
provider: providerId,
|
|
270
|
+
timestamp: new Date().toISOString(),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const result = await providerExecutor.execute({
|
|
275
|
+
providerId,
|
|
276
|
+
prompt,
|
|
277
|
+
systemPrompt: getProviderSystemPrompt(providerId),
|
|
278
|
+
temperature: config.temperature,
|
|
279
|
+
timeoutMs: config.providerTimeout,
|
|
280
|
+
abortSignal,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const response: DiscussionProviderResponse = {
|
|
284
|
+
provider: providerId,
|
|
285
|
+
content: result.success ? result.content || '' : '',
|
|
286
|
+
round: roundNum,
|
|
287
|
+
role,
|
|
288
|
+
timestamp: new Date().toISOString(),
|
|
289
|
+
durationMs: result.durationMs,
|
|
290
|
+
tokenCount: result.tokenCount,
|
|
291
|
+
error: result.success ? undefined : result.error,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
onProgress?.({
|
|
295
|
+
type: 'provider_complete',
|
|
296
|
+
round: roundNum,
|
|
297
|
+
provider: providerId,
|
|
298
|
+
message: result.success ? 'completed' : `failed: ${result.error}`,
|
|
299
|
+
timestamp: new Date().toISOString(),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
onProgress?.({
|
|
303
|
+
type: 'round_complete',
|
|
304
|
+
round: roundNum,
|
|
305
|
+
timestamp: new Date().toISOString(),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
round: {
|
|
310
|
+
roundNumber: roundNum,
|
|
311
|
+
responses: [response],
|
|
312
|
+
durationMs: Date.now() - roundStart,
|
|
313
|
+
},
|
|
314
|
+
success: result.success,
|
|
315
|
+
content: result.content || '',
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
} catch (error) {
|
|
319
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
round: {
|
|
323
|
+
roundNumber: roundNum,
|
|
324
|
+
responses: [{
|
|
325
|
+
provider: providerId,
|
|
326
|
+
content: '',
|
|
327
|
+
round: roundNum,
|
|
328
|
+
role,
|
|
329
|
+
timestamp: new Date().toISOString(),
|
|
330
|
+
durationMs: Date.now() - roundStart,
|
|
331
|
+
error: errorMessage,
|
|
332
|
+
}],
|
|
333
|
+
durationMs: Date.now() - roundStart,
|
|
334
|
+
},
|
|
335
|
+
success: false,
|
|
336
|
+
content: '',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discussion Pattern Executors
|
|
3
|
+
*
|
|
4
|
+
* Exports all pattern implementations and a factory for getting the right executor.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { DiscussionPattern } from '@defai.digital/contracts';
|
|
8
|
+
import type { PatternExecutor } from '../types.js';
|
|
9
|
+
import { RoundRobinPattern } from './round-robin.js';
|
|
10
|
+
import { SynthesisPattern } from './synthesis.js';
|
|
11
|
+
import { DebatePattern } from './debate.js';
|
|
12
|
+
import { CritiquePattern } from './critique.js';
|
|
13
|
+
import { VotingPattern } from './voting.js';
|
|
14
|
+
|
|
15
|
+
// Export all pattern classes
|
|
16
|
+
export { RoundRobinPattern } from './round-robin.js';
|
|
17
|
+
export { SynthesisPattern } from './synthesis.js';
|
|
18
|
+
export { DebatePattern } from './debate.js';
|
|
19
|
+
export { CritiquePattern } from './critique.js';
|
|
20
|
+
export { VotingPattern } from './voting.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Map of pattern types to their executors
|
|
24
|
+
*/
|
|
25
|
+
const patternExecutors: Record<DiscussionPattern, () => PatternExecutor> = {
|
|
26
|
+
'round-robin': () => new RoundRobinPattern(),
|
|
27
|
+
synthesis: () => new SynthesisPattern(),
|
|
28
|
+
debate: () => new DebatePattern(),
|
|
29
|
+
critique: () => new CritiquePattern(),
|
|
30
|
+
voting: () => new VotingPattern(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get a pattern executor for the given pattern type
|
|
35
|
+
*/
|
|
36
|
+
export function getPatternExecutor(pattern: DiscussionPattern): PatternExecutor {
|
|
37
|
+
const factory = patternExecutors[pattern];
|
|
38
|
+
if (!factory) {
|
|
39
|
+
throw new Error(`Unknown discussion pattern: ${pattern}`);
|
|
40
|
+
}
|
|
41
|
+
return factory();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if a pattern type is supported
|
|
46
|
+
*/
|
|
47
|
+
export function isPatternSupported(pattern: string): pattern is DiscussionPattern {
|
|
48
|
+
return pattern in patternExecutors;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get all supported pattern types
|
|
53
|
+
*/
|
|
54
|
+
export function getSupportedPatterns(): DiscussionPattern[] {
|
|
55
|
+
return Object.keys(patternExecutors) as DiscussionPattern[];
|
|
56
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round-Robin Pattern Executor
|
|
3
|
+
*
|
|
4
|
+
* Models respond sequentially, each building on previous responses.
|
|
5
|
+
* Good for: brainstorming, iterative refinement, building consensus.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DiscussionRound, DebateRole } from '@defai.digital/contracts';
|
|
9
|
+
import type {
|
|
10
|
+
PatternExecutor,
|
|
11
|
+
PatternExecutionContext,
|
|
12
|
+
PatternExecutionResult,
|
|
13
|
+
} from '../types.js';
|
|
14
|
+
|
|
15
|
+
// Local type for discussion ProviderResponse (avoids conflict with provider/v1 ProviderResponse)
|
|
16
|
+
interface DiscussionProviderResponse {
|
|
17
|
+
provider: string;
|
|
18
|
+
content: string;
|
|
19
|
+
round: number;
|
|
20
|
+
role?: DebateRole | undefined;
|
|
21
|
+
confidence?: number | undefined;
|
|
22
|
+
vote?: string | undefined;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
tokenCount?: number | undefined;
|
|
26
|
+
truncated?: boolean | undefined;
|
|
27
|
+
error?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
import {
|
|
30
|
+
ROUND_ROBIN_INITIAL,
|
|
31
|
+
ROUND_ROBIN_FOLLOWUP,
|
|
32
|
+
interpolate,
|
|
33
|
+
formatPreviousResponses,
|
|
34
|
+
getProviderSystemPrompt,
|
|
35
|
+
} from '../prompts/templates.js';
|
|
36
|
+
|
|
37
|
+
export class RoundRobinPattern implements PatternExecutor {
|
|
38
|
+
readonly pattern = 'round-robin' as const;
|
|
39
|
+
|
|
40
|
+
async execute(context: PatternExecutionContext): Promise<PatternExecutionResult> {
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
const { config, providerExecutor, availableProviders, abortSignal, onProgress } = context;
|
|
43
|
+
|
|
44
|
+
const rounds: DiscussionRound[] = [];
|
|
45
|
+
const participatingProviders = new Set<string>();
|
|
46
|
+
const failedProviders = new Set<string>();
|
|
47
|
+
const allResponses: DiscussionProviderResponse[] = [];
|
|
48
|
+
|
|
49
|
+
// Filter to available providers
|
|
50
|
+
const providers = config.providers.filter(p => availableProviders.includes(p));
|
|
51
|
+
|
|
52
|
+
if (providers.length < config.minProviders) {
|
|
53
|
+
return {
|
|
54
|
+
rounds: [],
|
|
55
|
+
participatingProviders: [],
|
|
56
|
+
failedProviders: config.providers.filter(p => !availableProviders.includes(p)),
|
|
57
|
+
totalDurationMs: Date.now() - startTime,
|
|
58
|
+
success: false,
|
|
59
|
+
error: `Only ${providers.length} providers available, need ${config.minProviders}`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Execute rounds
|
|
64
|
+
for (let roundNum = 1; roundNum <= config.rounds; roundNum++) {
|
|
65
|
+
if (abortSignal?.aborted) {
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onProgress?.({
|
|
70
|
+
type: 'round_start',
|
|
71
|
+
round: roundNum,
|
|
72
|
+
message: `Starting round ${roundNum} of ${config.rounds}`,
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const roundStart = Date.now();
|
|
77
|
+
const roundResponses: DiscussionProviderResponse[] = [];
|
|
78
|
+
|
|
79
|
+
// Each provider responds sequentially
|
|
80
|
+
for (const providerId of providers) {
|
|
81
|
+
if (abortSignal?.aborted) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onProgress?.({
|
|
86
|
+
type: 'provider_start',
|
|
87
|
+
round: roundNum,
|
|
88
|
+
provider: providerId,
|
|
89
|
+
message: `${providerId} is responding...`,
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const responseStart = Date.now();
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Build prompt based on round
|
|
97
|
+
const prompt = this.buildPrompt(
|
|
98
|
+
config.prompt,
|
|
99
|
+
config.context || '',
|
|
100
|
+
allResponses,
|
|
101
|
+
roundNum === 1
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const result = await providerExecutor.execute({
|
|
105
|
+
providerId,
|
|
106
|
+
prompt,
|
|
107
|
+
systemPrompt: getProviderSystemPrompt(providerId),
|
|
108
|
+
temperature: config.temperature,
|
|
109
|
+
timeoutMs: config.providerTimeout,
|
|
110
|
+
abortSignal,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const response: DiscussionProviderResponse = {
|
|
114
|
+
provider: providerId,
|
|
115
|
+
content: result.success ? result.content || '' : '',
|
|
116
|
+
round: roundNum,
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
durationMs: result.durationMs,
|
|
119
|
+
tokenCount: result.tokenCount,
|
|
120
|
+
truncated: result.truncated,
|
|
121
|
+
error: result.success ? undefined : result.error,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
roundResponses.push(response);
|
|
125
|
+
allResponses.push(response);
|
|
126
|
+
|
|
127
|
+
if (result.success) {
|
|
128
|
+
participatingProviders.add(providerId);
|
|
129
|
+
} else if (!config.continueOnProviderFailure) {
|
|
130
|
+
failedProviders.add(providerId);
|
|
131
|
+
break;
|
|
132
|
+
} else {
|
|
133
|
+
failedProviders.add(providerId);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
onProgress?.({
|
|
137
|
+
type: 'provider_complete',
|
|
138
|
+
round: roundNum,
|
|
139
|
+
provider: providerId,
|
|
140
|
+
message: result.success ? `${providerId} completed` : `${providerId} failed: ${result.error}`,
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
146
|
+
failedProviders.add(providerId);
|
|
147
|
+
|
|
148
|
+
roundResponses.push({
|
|
149
|
+
provider: providerId,
|
|
150
|
+
content: '',
|
|
151
|
+
round: roundNum,
|
|
152
|
+
timestamp: new Date().toISOString(),
|
|
153
|
+
durationMs: Date.now() - responseStart,
|
|
154
|
+
error: errorMessage,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!config.continueOnProviderFailure) {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
rounds.push({
|
|
164
|
+
roundNumber: roundNum,
|
|
165
|
+
responses: roundResponses,
|
|
166
|
+
durationMs: Date.now() - roundStart,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
onProgress?.({
|
|
170
|
+
type: 'round_complete',
|
|
171
|
+
round: roundNum,
|
|
172
|
+
message: `Round ${roundNum} complete`,
|
|
173
|
+
timestamp: new Date().toISOString(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Check if we have enough participating providers
|
|
177
|
+
if (participatingProviders.size < config.minProviders) {
|
|
178
|
+
return {
|
|
179
|
+
rounds,
|
|
180
|
+
participatingProviders: Array.from(participatingProviders),
|
|
181
|
+
failedProviders: Array.from(failedProviders),
|
|
182
|
+
totalDurationMs: Date.now() - startTime,
|
|
183
|
+
success: false,
|
|
184
|
+
error: `Only ${participatingProviders.size} providers succeeded, need ${config.minProviders}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
rounds,
|
|
191
|
+
participatingProviders: Array.from(participatingProviders),
|
|
192
|
+
failedProviders: Array.from(failedProviders),
|
|
193
|
+
totalDurationMs: Date.now() - startTime,
|
|
194
|
+
success: participatingProviders.size >= config.minProviders,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private buildPrompt(
|
|
199
|
+
topic: string,
|
|
200
|
+
context: string,
|
|
201
|
+
previousResponses: DiscussionProviderResponse[],
|
|
202
|
+
isInitial: boolean
|
|
203
|
+
): string {
|
|
204
|
+
if (isInitial || previousResponses.length === 0) {
|
|
205
|
+
return interpolate(ROUND_ROBIN_INITIAL, {
|
|
206
|
+
topic,
|
|
207
|
+
context: context ? `\n## Additional Context\n${context}` : '',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const formattedResponses = formatPreviousResponses(
|
|
212
|
+
previousResponses.map(r => ({
|
|
213
|
+
provider: r.provider,
|
|
214
|
+
content: r.content,
|
|
215
|
+
}))
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return interpolate(ROUND_ROBIN_FOLLOWUP, {
|
|
219
|
+
topic,
|
|
220
|
+
previousResponses: formattedResponses,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|