@defai.digital/discussion-domain 13.0.3 → 13.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/budget-manager.d.ts +79 -0
- package/dist/budget-manager.d.ts.map +1 -0
- package/dist/budget-manager.js +155 -0
- package/dist/budget-manager.js.map +1 -0
- package/dist/confidence-extractor.d.ts +60 -0
- package/dist/confidence-extractor.d.ts.map +1 -0
- package/dist/confidence-extractor.js +251 -0
- package/dist/confidence-extractor.js.map +1 -0
- package/dist/consensus/synthesis.d.ts.map +1 -1
- package/dist/consensus/synthesis.js +2 -0
- package/dist/consensus/synthesis.js.map +1 -1
- package/dist/consensus/voting.d.ts +3 -0
- package/dist/consensus/voting.d.ts.map +1 -1
- package/dist/consensus/voting.js +15 -4
- package/dist/consensus/voting.js.map +1 -1
- package/dist/context-tracker.d.ts +77 -0
- package/dist/context-tracker.d.ts.map +1 -0
- package/dist/context-tracker.js +177 -0
- package/dist/context-tracker.js.map +1 -0
- package/dist/cost-tracker.d.ts +123 -0
- package/dist/cost-tracker.d.ts.map +1 -0
- package/dist/cost-tracker.js +195 -0
- package/dist/cost-tracker.js.map +1 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +9 -0
- package/dist/executor.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/participant-resolver.d.ts +111 -0
- package/dist/participant-resolver.d.ts.map +1 -0
- package/dist/participant-resolver.js +160 -0
- package/dist/participant-resolver.js.map +1 -0
- package/dist/patterns/round-robin.d.ts +3 -0
- package/dist/patterns/round-robin.d.ts.map +1 -1
- package/dist/patterns/round-robin.js +41 -2
- package/dist/patterns/round-robin.js.map +1 -1
- package/dist/patterns/synthesis.d.ts +3 -0
- package/dist/patterns/synthesis.d.ts.map +1 -1
- package/dist/patterns/synthesis.js +77 -3
- package/dist/patterns/synthesis.js.map +1 -1
- package/dist/prompts/templates.d.ts +1 -0
- package/dist/prompts/templates.d.ts.map +1 -1
- package/dist/prompts/templates.js +3 -1
- package/dist/prompts/templates.js.map +1 -1
- package/dist/provider-bridge.d.ts.map +1 -1
- package/dist/provider-bridge.js +31 -30
- package/dist/provider-bridge.js.map +1 -1
- package/dist/recursive-executor.d.ts +77 -0
- package/dist/recursive-executor.d.ts.map +1 -0
- package/dist/recursive-executor.js +344 -0
- package/dist/recursive-executor.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/budget-manager.ts +272 -0
- package/src/confidence-extractor.ts +321 -0
- package/src/consensus/synthesis.ts +2 -0
- package/src/consensus/voting.ts +22 -6
- package/src/context-tracker.ts +307 -0
- package/src/cost-tracker.ts +362 -0
- package/src/executor.ts +9 -0
- package/src/index.ts +72 -0
- package/src/participant-resolver.ts +297 -0
- package/src/patterns/round-robin.ts +48 -2
- package/src/patterns/synthesis.ts +89 -3
- package/src/prompts/templates.ts +4 -2
- package/src/provider-bridge.ts +31 -28
- package/src/recursive-executor.ts +500 -0
- package/src/types.ts +120 -0
package/src/index.ts
CHANGED
|
@@ -10,6 +10,32 @@
|
|
|
10
10
|
// Main executor
|
|
11
11
|
export { DiscussionExecutor, createDiscussionExecutor } from './executor.js';
|
|
12
12
|
|
|
13
|
+
// Recursive executor
|
|
14
|
+
export {
|
|
15
|
+
RecursiveDiscussionExecutor,
|
|
16
|
+
createRecursiveDiscussionExecutor,
|
|
17
|
+
type RecursiveDiscussionResult,
|
|
18
|
+
} from './recursive-executor.js';
|
|
19
|
+
|
|
20
|
+
// Context tracker for recursion management
|
|
21
|
+
export {
|
|
22
|
+
createContextTracker,
|
|
23
|
+
DiscussionContextError,
|
|
24
|
+
type DiscussionContextTracker,
|
|
25
|
+
type SubDiscussionCheck,
|
|
26
|
+
type ContextTrackerConfig,
|
|
27
|
+
} from './context-tracker.js';
|
|
28
|
+
|
|
29
|
+
// Budget manager for timeout cascade
|
|
30
|
+
export {
|
|
31
|
+
createBudgetManager,
|
|
32
|
+
formatBudgetStatus,
|
|
33
|
+
recommendProvidersForBudget,
|
|
34
|
+
type DiscussionBudgetManager,
|
|
35
|
+
type BudgetAllocation,
|
|
36
|
+
type BudgetStatus,
|
|
37
|
+
} from './budget-manager.js';
|
|
38
|
+
|
|
13
39
|
// Types and interfaces
|
|
14
40
|
export type {
|
|
15
41
|
DiscussionProviderExecutor,
|
|
@@ -23,11 +49,41 @@ export type {
|
|
|
23
49
|
ConsensusExecutionResult,
|
|
24
50
|
DiscussionExecutorOptions,
|
|
25
51
|
DiscussionProgressEvent,
|
|
52
|
+
// Cascading confidence types
|
|
53
|
+
CascadingConfidenceOptions,
|
|
54
|
+
EarlyExitInfo,
|
|
55
|
+
// Recursive types
|
|
56
|
+
RecursiveDiscussionExecutorOptions,
|
|
57
|
+
RecursivePatternExecutionContext,
|
|
58
|
+
RecursivePatternExecutionResult,
|
|
59
|
+
SubDiscussionRequest,
|
|
26
60
|
} from './types.js';
|
|
27
61
|
|
|
28
62
|
// Stub implementation for testing
|
|
29
63
|
export { StubProviderExecutor } from './types.js';
|
|
30
64
|
|
|
65
|
+
// Confidence extraction and early exit
|
|
66
|
+
export {
|
|
67
|
+
extractConfidence,
|
|
68
|
+
evaluateEarlyExit,
|
|
69
|
+
calculateAgreementScore,
|
|
70
|
+
DEFAULT_CASCADING_CONFIDENCE,
|
|
71
|
+
type ExtractedConfidence,
|
|
72
|
+
type EarlyExitDecision,
|
|
73
|
+
} from './confidence-extractor.js';
|
|
74
|
+
|
|
75
|
+
// Cost tracking
|
|
76
|
+
export {
|
|
77
|
+
createCostTracker,
|
|
78
|
+
formatCost,
|
|
79
|
+
formatCostSummary,
|
|
80
|
+
PROVIDER_COSTS,
|
|
81
|
+
type DiscussionCostTracker,
|
|
82
|
+
type ProviderCallRecord,
|
|
83
|
+
type CostSummary,
|
|
84
|
+
type BudgetCheckResult,
|
|
85
|
+
} from './cost-tracker.js';
|
|
86
|
+
|
|
31
87
|
// Pattern executors
|
|
32
88
|
export {
|
|
33
89
|
RoundRobinPattern,
|
|
@@ -67,3 +123,19 @@ export {
|
|
|
67
123
|
type ProviderRegistryLike,
|
|
68
124
|
type ProviderBridgeOptions,
|
|
69
125
|
} from './provider-bridge.js';
|
|
126
|
+
|
|
127
|
+
// Participant resolution (for agent/provider mixing)
|
|
128
|
+
export {
|
|
129
|
+
resolveParticipant,
|
|
130
|
+
resolveParticipants,
|
|
131
|
+
providersToParticipants,
|
|
132
|
+
parseParticipantString,
|
|
133
|
+
parseParticipantList,
|
|
134
|
+
getProviderIds,
|
|
135
|
+
buildEnhancedSystemPrompt,
|
|
136
|
+
type ResolvedParticipant,
|
|
137
|
+
type AgentProfileLike,
|
|
138
|
+
type AgentRegistryLike,
|
|
139
|
+
type AbilityManagerLike,
|
|
140
|
+
type ParticipantResolverOptions,
|
|
141
|
+
} from './participant-resolver.js';
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Participant Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves discussion participants (providers and agents) to their execution config.
|
|
5
|
+
* Handles agent-to-provider mapping using providerAffinity.
|
|
6
|
+
*
|
|
7
|
+
* Invariants:
|
|
8
|
+
* - INV-DISC-640: Agent uses providerAffinity.preferred[0] for provider selection
|
|
9
|
+
* - INV-DISC-641: Agent abilities injected with max 10K tokens
|
|
10
|
+
* - INV-DISC-642: Agent weight multiplier between 0.5-3.0 (default 1.5)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { DiscussionParticipant } from '@defai.digital/contracts';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolved participant ready for discussion execution
|
|
17
|
+
*/
|
|
18
|
+
export interface ResolvedParticipant {
|
|
19
|
+
/** Original participant identifier */
|
|
20
|
+
id: string;
|
|
21
|
+
|
|
22
|
+
/** Whether this is an agent (vs raw provider) */
|
|
23
|
+
isAgent: boolean;
|
|
24
|
+
|
|
25
|
+
/** Provider ID to use for LLM calls */
|
|
26
|
+
providerId: string;
|
|
27
|
+
|
|
28
|
+
/** Agent ID (if isAgent) */
|
|
29
|
+
agentId?: string | undefined;
|
|
30
|
+
|
|
31
|
+
/** System prompt override (from agent config) */
|
|
32
|
+
systemPromptOverride?: string | undefined;
|
|
33
|
+
|
|
34
|
+
/** Injected ability content */
|
|
35
|
+
abilityContent?: string | undefined;
|
|
36
|
+
|
|
37
|
+
/** Temperature override (from agent config) */
|
|
38
|
+
temperatureOverride?: number | undefined;
|
|
39
|
+
|
|
40
|
+
/** Weight multiplier for consensus (agents typically get higher weight) */
|
|
41
|
+
weightMultiplier: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Agent profile interface (subset needed for resolution)
|
|
46
|
+
*/
|
|
47
|
+
export interface AgentProfileLike {
|
|
48
|
+
agentId: string;
|
|
49
|
+
systemPrompt?: string | undefined;
|
|
50
|
+
providerAffinity?: {
|
|
51
|
+
preferred?: string[] | undefined;
|
|
52
|
+
defaultSynthesizer?: string | undefined;
|
|
53
|
+
temperatureOverrides?: Record<string, number> | undefined;
|
|
54
|
+
} | undefined;
|
|
55
|
+
temperature?: number | undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Agent registry interface for looking up agents
|
|
60
|
+
*/
|
|
61
|
+
export interface AgentRegistryLike {
|
|
62
|
+
get(agentId: string): Promise<AgentProfileLike | null>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ability manager interface for injecting abilities
|
|
67
|
+
*/
|
|
68
|
+
export interface AbilityManagerLike {
|
|
69
|
+
injectAbilities(
|
|
70
|
+
agentId: string,
|
|
71
|
+
task: string,
|
|
72
|
+
coreAbilities: string[],
|
|
73
|
+
options: { maxAbilities: number; maxTokens: number }
|
|
74
|
+
): Promise<{ combinedContent: string; injectedAbilities: string[] }>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Options for participant resolution
|
|
79
|
+
*/
|
|
80
|
+
export interface ParticipantResolverOptions {
|
|
81
|
+
/** Agent registry for looking up agents */
|
|
82
|
+
agentRegistry?: AgentRegistryLike | undefined;
|
|
83
|
+
|
|
84
|
+
/** Ability manager for injecting abilities */
|
|
85
|
+
abilityManager?: AbilityManagerLike | undefined;
|
|
86
|
+
|
|
87
|
+
/** Default provider to use when agent has no preference */
|
|
88
|
+
defaultProvider?: string | undefined;
|
|
89
|
+
|
|
90
|
+
/** Default weight multiplier for agent participants */
|
|
91
|
+
agentWeightMultiplier?: number | undefined;
|
|
92
|
+
|
|
93
|
+
/** Maximum tokens for ability injection */
|
|
94
|
+
maxAbilityTokens?: number | undefined;
|
|
95
|
+
|
|
96
|
+
/** Topic for ability matching */
|
|
97
|
+
topic?: string | undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Default provider when agent has no preference
|
|
102
|
+
*/
|
|
103
|
+
const DEFAULT_PROVIDER = 'claude';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Default agent weight multiplier (INV-DISC-642)
|
|
107
|
+
*/
|
|
108
|
+
const DEFAULT_AGENT_WEIGHT = 1.5;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Maximum tokens for ability injection (INV-DISC-641)
|
|
112
|
+
*/
|
|
113
|
+
const DEFAULT_MAX_ABILITY_TOKENS = 10000;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a single participant to execution config
|
|
117
|
+
*/
|
|
118
|
+
export async function resolveParticipant(
|
|
119
|
+
participant: DiscussionParticipant,
|
|
120
|
+
options: ParticipantResolverOptions = {}
|
|
121
|
+
): Promise<ResolvedParticipant> {
|
|
122
|
+
const {
|
|
123
|
+
agentRegistry,
|
|
124
|
+
abilityManager,
|
|
125
|
+
defaultProvider = DEFAULT_PROVIDER,
|
|
126
|
+
agentWeightMultiplier = DEFAULT_AGENT_WEIGHT,
|
|
127
|
+
maxAbilityTokens = DEFAULT_MAX_ABILITY_TOKENS,
|
|
128
|
+
topic = '',
|
|
129
|
+
} = options;
|
|
130
|
+
|
|
131
|
+
// Provider participant - simple resolution
|
|
132
|
+
if (participant.type === 'provider') {
|
|
133
|
+
return {
|
|
134
|
+
id: participant.id,
|
|
135
|
+
isAgent: false,
|
|
136
|
+
providerId: participant.id,
|
|
137
|
+
weightMultiplier: 1.0, // Base weight for providers
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Agent participant - resolve via registry
|
|
142
|
+
const agentId = participant.id;
|
|
143
|
+
|
|
144
|
+
// If no registry, fall back to default provider with agent weight
|
|
145
|
+
if (!agentRegistry) {
|
|
146
|
+
return {
|
|
147
|
+
id: agentId,
|
|
148
|
+
isAgent: true,
|
|
149
|
+
providerId: defaultProvider,
|
|
150
|
+
agentId,
|
|
151
|
+
weightMultiplier: agentWeightMultiplier,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Look up agent
|
|
156
|
+
const agent = await agentRegistry.get(agentId);
|
|
157
|
+
|
|
158
|
+
if (!agent) {
|
|
159
|
+
// Agent not found, fall back to default provider
|
|
160
|
+
return {
|
|
161
|
+
id: agentId,
|
|
162
|
+
isAgent: true,
|
|
163
|
+
providerId: defaultProvider,
|
|
164
|
+
agentId,
|
|
165
|
+
weightMultiplier: agentWeightMultiplier,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// INV-DISC-640: Use providerAffinity.preferred[0] or fallback to default
|
|
170
|
+
const providerId = agent.providerAffinity?.preferred?.[0] ?? defaultProvider;
|
|
171
|
+
|
|
172
|
+
// Get temperature override if configured
|
|
173
|
+
const temperatureOverride = agent.providerAffinity?.temperatureOverrides?.[providerId]
|
|
174
|
+
?? agent.temperature;
|
|
175
|
+
|
|
176
|
+
// Build resolved participant
|
|
177
|
+
const resolved: ResolvedParticipant = {
|
|
178
|
+
id: agentId,
|
|
179
|
+
isAgent: true,
|
|
180
|
+
providerId,
|
|
181
|
+
agentId,
|
|
182
|
+
systemPromptOverride: agent.systemPrompt,
|
|
183
|
+
temperatureOverride,
|
|
184
|
+
weightMultiplier: agentWeightMultiplier,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// INV-DISC-641: Inject abilities if ability manager available
|
|
188
|
+
if (abilityManager && topic) {
|
|
189
|
+
try {
|
|
190
|
+
const injection = await abilityManager.injectAbilities(
|
|
191
|
+
agentId,
|
|
192
|
+
topic,
|
|
193
|
+
[], // coreAbilities
|
|
194
|
+
{ maxAbilities: 5, maxTokens: maxAbilityTokens }
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
if (injection.combinedContent) {
|
|
198
|
+
resolved.abilityContent = injection.combinedContent;
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Ability injection failed, continue without abilities
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return resolved;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve multiple participants
|
|
210
|
+
*/
|
|
211
|
+
export async function resolveParticipants(
|
|
212
|
+
participants: DiscussionParticipant[],
|
|
213
|
+
options: ParticipantResolverOptions = {}
|
|
214
|
+
): Promise<ResolvedParticipant[]> {
|
|
215
|
+
const resolved = await Promise.all(
|
|
216
|
+
participants.map(p => resolveParticipant(p, options))
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return resolved;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Convert legacy string provider list to participant array
|
|
224
|
+
*/
|
|
225
|
+
export function providersToParticipants(providers: string[]): DiscussionParticipant[] {
|
|
226
|
+
return providers.map(id => ({ type: 'provider' as const, id }));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Parse participant string (e.g., "claude" or "reviewer:agent")
|
|
231
|
+
*/
|
|
232
|
+
export function parseParticipantString(input: string): DiscussionParticipant {
|
|
233
|
+
const parts = input.split(':');
|
|
234
|
+
|
|
235
|
+
if (parts.length === 2 && parts[1] === 'agent') {
|
|
236
|
+
return { type: 'agent', id: parts[0]! };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { type: 'provider', id: parts[0]! };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Parse participant list from CLI input
|
|
244
|
+
* Format: "claude,glm,reviewer:agent,security:agent"
|
|
245
|
+
*/
|
|
246
|
+
export function parseParticipantList(input: string): DiscussionParticipant[] {
|
|
247
|
+
return input
|
|
248
|
+
.split(',')
|
|
249
|
+
.map(s => s.trim())
|
|
250
|
+
.filter(s => s.length > 0)
|
|
251
|
+
.map(parseParticipantString);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get provider IDs from resolved participants
|
|
256
|
+
*/
|
|
257
|
+
export function getProviderIds(participants: ResolvedParticipant[]): string[] {
|
|
258
|
+
// Unique provider IDs preserving order
|
|
259
|
+
const seen = new Set<string>();
|
|
260
|
+
const result: string[] = [];
|
|
261
|
+
|
|
262
|
+
for (const p of participants) {
|
|
263
|
+
if (!seen.has(p.providerId)) {
|
|
264
|
+
seen.add(p.providerId);
|
|
265
|
+
result.push(p.providerId);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Build enhanced system prompt with agent context
|
|
274
|
+
*/
|
|
275
|
+
export function buildEnhancedSystemPrompt(
|
|
276
|
+
basePrompt: string,
|
|
277
|
+
participant: ResolvedParticipant
|
|
278
|
+
): string {
|
|
279
|
+
const parts: string[] = [];
|
|
280
|
+
|
|
281
|
+
// Add ability content first (provides domain knowledge)
|
|
282
|
+
if (participant.abilityContent) {
|
|
283
|
+
parts.push(participant.abilityContent);
|
|
284
|
+
parts.push('---');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Add agent system prompt
|
|
288
|
+
if (participant.systemPromptOverride) {
|
|
289
|
+
parts.push(participant.systemPromptOverride);
|
|
290
|
+
parts.push('---');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Add base system prompt
|
|
294
|
+
parts.push(basePrompt);
|
|
295
|
+
|
|
296
|
+
return parts.join('\n\n');
|
|
297
|
+
}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Models respond sequentially, each building on previous responses.
|
|
5
5
|
* Good for: brainstorming, iterative refinement, building consensus.
|
|
6
|
+
*
|
|
7
|
+
* Invariants:
|
|
8
|
+
* - INV-DISC-643: Early exit only after minProviders responded
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import type { DiscussionRound, DebateRole } from '@defai.digital/contracts';
|
|
@@ -10,7 +13,9 @@ import type {
|
|
|
10
13
|
PatternExecutor,
|
|
11
14
|
PatternExecutionContext,
|
|
12
15
|
PatternExecutionResult,
|
|
16
|
+
EarlyExitInfo,
|
|
13
17
|
} from '../types.js';
|
|
18
|
+
import { extractConfidence, evaluateEarlyExit } from '../confidence-extractor.js';
|
|
14
19
|
|
|
15
20
|
// Local type for discussion ProviderResponse (avoids conflict with provider/v1 ProviderResponse)
|
|
16
21
|
interface DiscussionProviderResponse {
|
|
@@ -39,12 +44,13 @@ export class RoundRobinPattern implements PatternExecutor {
|
|
|
39
44
|
|
|
40
45
|
async execute(context: PatternExecutionContext): Promise<PatternExecutionResult> {
|
|
41
46
|
const startTime = Date.now();
|
|
42
|
-
const { config, providerExecutor, availableProviders, abortSignal, onProgress } = context;
|
|
47
|
+
const { config, providerExecutor, availableProviders, abortSignal, onProgress, cascadingConfidence } = context;
|
|
43
48
|
|
|
44
49
|
const rounds: DiscussionRound[] = [];
|
|
45
50
|
const participatingProviders = new Set<string>();
|
|
46
51
|
const failedProviders = new Set<string>();
|
|
47
52
|
const allResponses: DiscussionProviderResponse[] = [];
|
|
53
|
+
let earlyExit: EarlyExitInfo | undefined;
|
|
48
54
|
|
|
49
55
|
// Filter to available providers
|
|
50
56
|
const providers = config.providers.filter(p => availableProviders.includes(p));
|
|
@@ -110,15 +116,20 @@ export class RoundRobinPattern implements PatternExecutor {
|
|
|
110
116
|
abortSignal,
|
|
111
117
|
});
|
|
112
118
|
|
|
119
|
+
// Extract confidence from response content
|
|
120
|
+
const content = result.success ? result.content || '' : '';
|
|
121
|
+
const confidenceResult = result.success ? extractConfidence(content) : null;
|
|
122
|
+
|
|
113
123
|
const response: DiscussionProviderResponse = {
|
|
114
124
|
provider: providerId,
|
|
115
|
-
content
|
|
125
|
+
content,
|
|
116
126
|
round: roundNum,
|
|
117
127
|
timestamp: new Date().toISOString(),
|
|
118
128
|
durationMs: result.durationMs,
|
|
119
129
|
tokenCount: result.tokenCount,
|
|
120
130
|
truncated: result.truncated,
|
|
121
131
|
error: result.success ? undefined : result.error,
|
|
132
|
+
confidence: confidenceResult?.score ?? undefined,
|
|
122
133
|
};
|
|
123
134
|
|
|
124
135
|
roundResponses.push(response);
|
|
@@ -184,6 +195,40 @@ export class RoundRobinPattern implements PatternExecutor {
|
|
|
184
195
|
error: `Only ${participatingProviders.size} providers succeeded, need ${config.minProviders}`,
|
|
185
196
|
};
|
|
186
197
|
}
|
|
198
|
+
|
|
199
|
+
// Check for early exit after each round (INV-DISC-643)
|
|
200
|
+
if (cascadingConfidence?.enabled && roundNum < config.rounds) {
|
|
201
|
+
const responsesWithConfidence = roundResponses
|
|
202
|
+
.filter(r => !r.error)
|
|
203
|
+
.map(r => ({
|
|
204
|
+
provider: r.provider,
|
|
205
|
+
content: r.content,
|
|
206
|
+
confidence: r.confidence,
|
|
207
|
+
}));
|
|
208
|
+
|
|
209
|
+
const exitDecision = evaluateEarlyExit(responsesWithConfidence, {
|
|
210
|
+
enabled: cascadingConfidence.enabled,
|
|
211
|
+
threshold: cascadingConfidence.threshold,
|
|
212
|
+
minProviders: cascadingConfidence.minProviders,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (exitDecision.shouldExit) {
|
|
216
|
+
earlyExit = {
|
|
217
|
+
triggered: true,
|
|
218
|
+
reason: exitDecision.reason,
|
|
219
|
+
atProviderCount: exitDecision.providerCount,
|
|
220
|
+
confidenceScore: exitDecision.confidence,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
onProgress?.({
|
|
224
|
+
type: 'round_complete',
|
|
225
|
+
message: `Early exit after round ${roundNum}: ${exitDecision.reason}`,
|
|
226
|
+
timestamp: new Date().toISOString(),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
break; // Exit the rounds loop
|
|
230
|
+
}
|
|
231
|
+
}
|
|
187
232
|
}
|
|
188
233
|
|
|
189
234
|
return {
|
|
@@ -192,6 +237,7 @@ export class RoundRobinPattern implements PatternExecutor {
|
|
|
192
237
|
failedProviders: Array.from(failedProviders),
|
|
193
238
|
totalDurationMs: Date.now() - startTime,
|
|
194
239
|
success: participatingProviders.size >= config.minProviders,
|
|
240
|
+
earlyExit: earlyExit ?? { triggered: false },
|
|
195
241
|
};
|
|
196
242
|
}
|
|
197
243
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* 2. Round 2: Each provider responds to others' perspectives
|
|
10
10
|
* 3. (Optional) Additional cross-discussion rounds
|
|
11
11
|
* 4. Final synthesis by designated synthesizer (default: claude)
|
|
12
|
+
*
|
|
13
|
+
* Invariants:
|
|
14
|
+
* - INV-DISC-643: Early exit only after minProviders responded
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import type { DiscussionRound, DebateRole } from '@defai.digital/contracts';
|
|
@@ -16,6 +19,7 @@ import type {
|
|
|
16
19
|
PatternExecutor,
|
|
17
20
|
PatternExecutionContext,
|
|
18
21
|
PatternExecutionResult,
|
|
22
|
+
EarlyExitInfo,
|
|
19
23
|
} from '../types.js';
|
|
20
24
|
import {
|
|
21
25
|
SYNTHESIS_INITIAL,
|
|
@@ -24,6 +28,7 @@ import {
|
|
|
24
28
|
formatPreviousResponses,
|
|
25
29
|
getProviderSystemPrompt,
|
|
26
30
|
} from '../prompts/templates.js';
|
|
31
|
+
import { extractConfidence, evaluateEarlyExit } from '../confidence-extractor.js';
|
|
27
32
|
|
|
28
33
|
// Local type for discussion DiscussionProviderResponse (avoids conflict with provider/v1 DiscussionProviderResponse)
|
|
29
34
|
interface DiscussionProviderResponse {
|
|
@@ -45,11 +50,12 @@ export class SynthesisPattern implements PatternExecutor {
|
|
|
45
50
|
|
|
46
51
|
async execute(context: PatternExecutionContext): Promise<PatternExecutionResult> {
|
|
47
52
|
const startTime = Date.now();
|
|
48
|
-
const { config, providerExecutor, availableProviders, abortSignal, onProgress } = context;
|
|
53
|
+
const { config, providerExecutor, availableProviders, abortSignal, onProgress, cascadingConfidence } = context;
|
|
49
54
|
|
|
50
55
|
const rounds: DiscussionRound[] = [];
|
|
51
56
|
const participatingProviders = new Set<string>();
|
|
52
57
|
const failedProviders = new Set<string>();
|
|
58
|
+
let earlyExit: EarlyExitInfo | undefined;
|
|
53
59
|
|
|
54
60
|
// Filter to available providers
|
|
55
61
|
const providers = config.providers.filter(p => availableProviders.includes(p));
|
|
@@ -106,6 +112,43 @@ export class SynthesisPattern implements PatternExecutor {
|
|
|
106
112
|
};
|
|
107
113
|
}
|
|
108
114
|
|
|
115
|
+
// Check for early exit after round 1 (INV-DISC-643)
|
|
116
|
+
if (cascadingConfidence?.enabled) {
|
|
117
|
+
const responsesWithConfidence = initialRound.round.responses.map(r => ({
|
|
118
|
+
provider: r.provider,
|
|
119
|
+
content: r.content,
|
|
120
|
+
confidence: r.confidence,
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
const exitDecision = evaluateEarlyExit(responsesWithConfidence, {
|
|
124
|
+
enabled: cascadingConfidence.enabled,
|
|
125
|
+
threshold: cascadingConfidence.threshold,
|
|
126
|
+
minProviders: cascadingConfidence.minProviders,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (exitDecision.shouldExit) {
|
|
130
|
+
onProgress?.({
|
|
131
|
+
type: 'round_complete',
|
|
132
|
+
message: `Early exit triggered: ${exitDecision.reason}`,
|
|
133
|
+
timestamp: new Date().toISOString(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
rounds,
|
|
138
|
+
participatingProviders: Array.from(participatingProviders),
|
|
139
|
+
failedProviders: Array.from(failedProviders),
|
|
140
|
+
totalDurationMs: Date.now() - startTime,
|
|
141
|
+
success: true,
|
|
142
|
+
earlyExit: {
|
|
143
|
+
triggered: true,
|
|
144
|
+
reason: exitDecision.reason,
|
|
145
|
+
atProviderCount: exitDecision.providerCount,
|
|
146
|
+
confidenceScore: exitDecision.confidence,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
109
152
|
// Additional rounds: Cross-discussion
|
|
110
153
|
for (let roundNum = 2; roundNum <= config.rounds; roundNum++) {
|
|
111
154
|
if (abortSignal?.aborted) {
|
|
@@ -138,6 +181,38 @@ export class SynthesisPattern implements PatternExecutor {
|
|
|
138
181
|
message: `Cross-discussion round ${roundNum} complete`,
|
|
139
182
|
timestamp: new Date().toISOString(),
|
|
140
183
|
});
|
|
184
|
+
|
|
185
|
+
// Check for early exit after each round
|
|
186
|
+
if (cascadingConfidence?.enabled && roundNum < config.rounds) {
|
|
187
|
+
const allResponses = crossRound.round.responses.map(r => ({
|
|
188
|
+
provider: r.provider,
|
|
189
|
+
content: r.content,
|
|
190
|
+
confidence: r.confidence,
|
|
191
|
+
}));
|
|
192
|
+
|
|
193
|
+
const exitDecision = evaluateEarlyExit(allResponses, {
|
|
194
|
+
enabled: cascadingConfidence.enabled,
|
|
195
|
+
threshold: cascadingConfidence.threshold,
|
|
196
|
+
minProviders: cascadingConfidence.minProviders,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (exitDecision.shouldExit) {
|
|
200
|
+
earlyExit = {
|
|
201
|
+
triggered: true,
|
|
202
|
+
reason: exitDecision.reason,
|
|
203
|
+
atProviderCount: exitDecision.providerCount,
|
|
204
|
+
confidenceScore: exitDecision.confidence,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
onProgress?.({
|
|
208
|
+
type: 'round_complete',
|
|
209
|
+
message: `Early exit after round ${roundNum}: ${exitDecision.reason}`,
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
break; // Exit the rounds loop
|
|
214
|
+
}
|
|
215
|
+
}
|
|
141
216
|
}
|
|
142
217
|
|
|
143
218
|
return {
|
|
@@ -146,6 +221,7 @@ export class SynthesisPattern implements PatternExecutor {
|
|
|
146
221
|
failedProviders: Array.from(failedProviders),
|
|
147
222
|
totalDurationMs: Date.now() - startTime,
|
|
148
223
|
success: participatingProviders.size >= config.minProviders,
|
|
224
|
+
earlyExit: earlyExit ?? { triggered: false },
|
|
149
225
|
};
|
|
150
226
|
}
|
|
151
227
|
|
|
@@ -193,15 +269,20 @@ export class SynthesisPattern implements PatternExecutor {
|
|
|
193
269
|
abortSignal,
|
|
194
270
|
});
|
|
195
271
|
|
|
272
|
+
// Extract confidence from response content
|
|
273
|
+
const content = result.success ? result.content || '' : '';
|
|
274
|
+
const confidenceResult = result.success ? extractConfidence(content) : null;
|
|
275
|
+
|
|
196
276
|
const response: DiscussionProviderResponse = {
|
|
197
277
|
provider: providerId,
|
|
198
|
-
content
|
|
278
|
+
content,
|
|
199
279
|
round: roundNum,
|
|
200
280
|
timestamp: new Date().toISOString(),
|
|
201
281
|
durationMs: result.durationMs,
|
|
202
282
|
tokenCount: result.tokenCount,
|
|
203
283
|
truncated: result.truncated,
|
|
204
284
|
error: result.success ? undefined : result.error,
|
|
285
|
+
confidence: confidenceResult?.score ?? undefined,
|
|
205
286
|
};
|
|
206
287
|
|
|
207
288
|
if (result.success) {
|
|
@@ -300,14 +381,19 @@ export class SynthesisPattern implements PatternExecutor {
|
|
|
300
381
|
abortSignal,
|
|
301
382
|
});
|
|
302
383
|
|
|
384
|
+
// Extract confidence from response content
|
|
385
|
+
const content = result.success ? result.content || '' : '';
|
|
386
|
+
const confidenceResult = result.success ? extractConfidence(content) : null;
|
|
387
|
+
|
|
303
388
|
const response: DiscussionProviderResponse = {
|
|
304
389
|
provider: providerId,
|
|
305
|
-
content
|
|
390
|
+
content,
|
|
306
391
|
round: roundNum,
|
|
307
392
|
timestamp: new Date().toISOString(),
|
|
308
393
|
durationMs: result.durationMs,
|
|
309
394
|
tokenCount: result.tokenCount,
|
|
310
395
|
error: result.success ? undefined : result.error,
|
|
396
|
+
confidence: confidenceResult?.score ?? undefined,
|
|
311
397
|
};
|
|
312
398
|
|
|
313
399
|
if (!result.success) {
|
package/src/prompts/templates.ts
CHANGED
|
@@ -335,12 +335,14 @@ export function interpolate(template: string, variables: Record<string, string>)
|
|
|
335
335
|
* Format previous responses for inclusion in prompts
|
|
336
336
|
*/
|
|
337
337
|
export function formatPreviousResponses(
|
|
338
|
-
responses: { provider: string; content: string; role?: DebateRole | undefined }[]
|
|
338
|
+
responses: { provider: string; content: string; role?: DebateRole | undefined; isAgent?: boolean | undefined }[]
|
|
339
339
|
): string {
|
|
340
340
|
return responses
|
|
341
341
|
.map(r => {
|
|
342
342
|
const roleLabel = r.role ? ` (${r.role})` : '';
|
|
343
|
-
|
|
343
|
+
// INV-DISC-642: Indicate agent responses for synthesis weighting
|
|
344
|
+
const agentLabel = r.isAgent ? ' [Agent - Domain Expert]' : '';
|
|
345
|
+
return `### ${r.provider}${roleLabel}${agentLabel}\n${r.content}`;
|
|
344
346
|
})
|
|
345
347
|
.join('\n\n');
|
|
346
348
|
}
|