0nmcp 2.6.0 → 2.8.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/README.md +233 -695
- package/cli.js +9 -1
- package/crm/objects.js +5 -69
- package/crm/users.js +5 -80
- package/engine/index.js +341 -2
- package/engine/multi-ai.js +525 -0
- package/engine/plugin-builder.js +578 -0
- package/engine/plugin-registry.js +419 -0
- package/engine/plugin.js +448 -0
- package/engine/sxo-writer.js +596 -0
- package/engine/training-feed.js +520 -0
- package/engine/training.js +875 -0
- package/index.js +10 -1
- package/lib/stats.json +1 -1
- package/package.json +12 -2
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 0nMCP — Multi-AI Council Engine
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Jaxx orchestrates conversations across multiple AI providers.
|
|
5
|
+
// Send a problem → all AIs respond → Jaxx synthesizes the best answer.
|
|
6
|
+
//
|
|
7
|
+
// Providers: OpenAI (GPT), Google (Gemini), Anthropic (Claude), xAI (Grok)
|
|
8
|
+
//
|
|
9
|
+
// SECURITY: API keys NEVER leave this module. Other AIs only see
|
|
10
|
+
// the problem statement — never our credentials, architecture,
|
|
11
|
+
// database schemas, or internal systems.
|
|
12
|
+
//
|
|
13
|
+
// 4 MCP Tools:
|
|
14
|
+
// council_ask — Ask all AIs a question, get parallel responses
|
|
15
|
+
// council_debate — Have AIs critique each other's answers
|
|
16
|
+
// council_solve — Full pipeline: ask → debate → synthesize
|
|
17
|
+
// council_config — Check which providers are available
|
|
18
|
+
// ============================================================
|
|
19
|
+
|
|
20
|
+
const PROVIDERS = {
|
|
21
|
+
openai: {
|
|
22
|
+
name: 'OpenAI (GPT-4o)',
|
|
23
|
+
url: 'https://api.openai.com/v1/chat/completions',
|
|
24
|
+
model: 'gpt-4o',
|
|
25
|
+
envKey: 'OPENAI_API_KEY',
|
|
26
|
+
buildRequest: (prompt, systemPrompt, apiKey) => ({
|
|
27
|
+
url: 'https://api.openai.com/v1/chat/completions',
|
|
28
|
+
options: {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
model: 'gpt-4o',
|
|
33
|
+
messages: [
|
|
34
|
+
{ role: 'system', content: systemPrompt },
|
|
35
|
+
{ role: 'user', content: prompt },
|
|
36
|
+
],
|
|
37
|
+
max_tokens: 2000,
|
|
38
|
+
temperature: 0.7,
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
extract: (data) => data.choices?.[0]?.message?.content || 'No response',
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
gemini: {
|
|
46
|
+
name: 'Google (Gemini)',
|
|
47
|
+
url: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent',
|
|
48
|
+
envKey: 'GEMINI_API_KEY',
|
|
49
|
+
buildRequest: (prompt, systemPrompt, apiKey) => ({
|
|
50
|
+
url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
|
|
51
|
+
options: {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
system_instruction: { parts: [{ text: systemPrompt }] },
|
|
56
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
57
|
+
generationConfig: { maxOutputTokens: 2000, temperature: 0.7 },
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
extract: (data) => data.candidates?.[0]?.content?.parts?.[0]?.text || 'No response',
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
grok: {
|
|
65
|
+
name: 'xAI (Grok)',
|
|
66
|
+
url: 'https://api.x.ai/v1/chat/completions',
|
|
67
|
+
envKey: 'XAI_API_KEY',
|
|
68
|
+
buildRequest: (prompt, systemPrompt, apiKey) => ({
|
|
69
|
+
url: 'https://api.x.ai/v1/chat/completions',
|
|
70
|
+
options: {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
model: 'grok-3-latest',
|
|
75
|
+
messages: [
|
|
76
|
+
{ role: 'system', content: systemPrompt },
|
|
77
|
+
{ role: 'user', content: prompt },
|
|
78
|
+
],
|
|
79
|
+
max_tokens: 2000,
|
|
80
|
+
temperature: 0.7,
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
extract: (data) => data.choices?.[0]?.message?.content || 'No response',
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
anthropic: {
|
|
88
|
+
name: 'Anthropic (Claude)',
|
|
89
|
+
url: 'https://api.anthropic.com/v1/messages',
|
|
90
|
+
envKey: 'ANTHROPIC_API_KEY',
|
|
91
|
+
buildRequest: (prompt, systemPrompt, apiKey) => ({
|
|
92
|
+
url: 'https://api.anthropic.com/v1/messages',
|
|
93
|
+
options: {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'x-api-key': apiKey,
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
'anthropic-version': '2023-06-01',
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
model: 'claude-sonnet-4-20250514',
|
|
102
|
+
system: systemPrompt,
|
|
103
|
+
messages: [{ role: 'user', content: prompt }],
|
|
104
|
+
max_tokens: 2000,
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
extract: (data) => data.content?.[0]?.text || 'No response',
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// SECURITY: Sanitize prompts to never leak internal info
|
|
113
|
+
const SAFETY_PREAMBLE = `You are being asked a question as part of a multi-AI collaborative problem-solving session.
|
|
114
|
+
Answer the question directly and thoroughly. Do not ask for additional context about the user's systems or infrastructure.`;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get available providers (have API keys set)
|
|
118
|
+
*/
|
|
119
|
+
function getAvailableProviders() {
|
|
120
|
+
const available = [];
|
|
121
|
+
for (const [key, provider] of Object.entries(PROVIDERS)) {
|
|
122
|
+
const apiKey = process.env[provider.envKey];
|
|
123
|
+
if (apiKey && apiKey.length > 5) {
|
|
124
|
+
available.push({ key, name: provider.name, ready: true });
|
|
125
|
+
} else {
|
|
126
|
+
available.push({ key, name: provider.name, ready: false, missing: provider.envKey });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return available;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Ask a single provider
|
|
134
|
+
*/
|
|
135
|
+
async function askProvider(providerKey, prompt, systemPrompt) {
|
|
136
|
+
const provider = PROVIDERS[providerKey];
|
|
137
|
+
if (!provider) return { provider: providerKey, error: `Unknown provider: ${providerKey}` };
|
|
138
|
+
|
|
139
|
+
const apiKey = process.env[provider.envKey];
|
|
140
|
+
if (!apiKey) return { provider: providerKey, name: provider.name, error: `No API key (${provider.envKey})` };
|
|
141
|
+
|
|
142
|
+
const startTime = Date.now();
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const req = provider.buildRequest(prompt, systemPrompt || SAFETY_PREAMBLE, apiKey);
|
|
146
|
+
const res = await fetch(req.url, { ...req.options, signal: AbortSignal.timeout(30000) });
|
|
147
|
+
|
|
148
|
+
if (!res.ok) {
|
|
149
|
+
const errBody = await res.text().catch(() => 'Unknown error');
|
|
150
|
+
return {
|
|
151
|
+
provider: providerKey,
|
|
152
|
+
name: provider.name,
|
|
153
|
+
error: `HTTP ${res.status}: ${errBody.slice(0, 200)}`,
|
|
154
|
+
durationMs: Date.now() - startTime,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const data = await res.json();
|
|
159
|
+
const response = req.extract(data);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
provider: providerKey,
|
|
163
|
+
name: provider.name,
|
|
164
|
+
response,
|
|
165
|
+
durationMs: Date.now() - startTime,
|
|
166
|
+
tokens: data.usage?.total_tokens || data.usageMetadata?.totalTokenCount || null,
|
|
167
|
+
};
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
provider: providerKey,
|
|
171
|
+
name: provider.name,
|
|
172
|
+
error: err.message,
|
|
173
|
+
durationMs: Date.now() - startTime,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Ask ALL available providers in parallel
|
|
180
|
+
*/
|
|
181
|
+
async function askAll(prompt, options = {}) {
|
|
182
|
+
const available = getAvailableProviders().filter(p => p.ready);
|
|
183
|
+
if (available.length === 0) {
|
|
184
|
+
return { error: 'No AI providers configured. Set API keys: OPENAI_API_KEY, GEMINI_API_KEY, XAI_API_KEY, ANTHROPIC_API_KEY' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const systemPrompt = options.systemPrompt || SAFETY_PREAMBLE;
|
|
188
|
+
const exclude = new Set(options.exclude || []);
|
|
189
|
+
|
|
190
|
+
const providers = available.filter(p => !exclude.has(p.key));
|
|
191
|
+
|
|
192
|
+
const results = await Promise.allSettled(
|
|
193
|
+
providers.map(p => askProvider(p.key, prompt, systemPrompt))
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
prompt,
|
|
198
|
+
responses: results.map(r => r.status === 'fulfilled' ? r.value : { error: r.reason?.message }),
|
|
199
|
+
providerCount: providers.length,
|
|
200
|
+
successCount: results.filter(r => r.status === 'fulfilled' && !r.value.error).length,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Have AIs debate/critique each other's answers
|
|
206
|
+
*/
|
|
207
|
+
async function debate(prompt, responses, rounds = 1) {
|
|
208
|
+
const debateResults = [];
|
|
209
|
+
|
|
210
|
+
for (const response of responses) {
|
|
211
|
+
if (response.error || !response.response) continue;
|
|
212
|
+
|
|
213
|
+
// Ask other providers to critique this response
|
|
214
|
+
const critiquePrompt = `A question was asked: "${prompt}"
|
|
215
|
+
|
|
216
|
+
One AI responded with:
|
|
217
|
+
"${response.response.slice(0, 1500)}"
|
|
218
|
+
|
|
219
|
+
Evaluate this response:
|
|
220
|
+
1. What's correct and strong about it?
|
|
221
|
+
2. What's missing or could be improved?
|
|
222
|
+
3. What would you add or change?
|
|
223
|
+
|
|
224
|
+
Be specific and constructive. Keep it under 500 words.`;
|
|
225
|
+
|
|
226
|
+
// Pick a different provider to critique
|
|
227
|
+
const critics = responses.filter(r => r.provider !== response.provider && !r.error && r.response);
|
|
228
|
+
if (critics.length === 0) continue;
|
|
229
|
+
|
|
230
|
+
const critic = critics[0];
|
|
231
|
+
const critique = await askProvider(critic.provider, critiquePrompt, SAFETY_PREAMBLE);
|
|
232
|
+
|
|
233
|
+
debateResults.push({
|
|
234
|
+
original: { provider: response.provider, name: response.name },
|
|
235
|
+
critic: { provider: critic.provider, name: critic.name },
|
|
236
|
+
critique: critique.response || critique.error,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return debateResults;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Register Multi-AI Council tools on an MCP server
|
|
245
|
+
*/
|
|
246
|
+
export function registerCouncilTools(server, z) {
|
|
247
|
+
|
|
248
|
+
// ─── council_ask ──────────────────────────────────────────
|
|
249
|
+
server.tool(
|
|
250
|
+
"council_ask",
|
|
251
|
+
`Ask multiple AI providers the same question simultaneously.
|
|
252
|
+
Sends the prompt to all available AIs (GPT-4o, Gemini, Grok, Claude) in parallel.
|
|
253
|
+
Returns each response with timing. YOUR credentials are never shared.
|
|
254
|
+
|
|
255
|
+
Example: council_ask({ prompt: "What's the best approach for rate limiting in a Node.js API?" })
|
|
256
|
+
Example: council_ask({ prompt: "Compare REST vs GraphQL for a CRM platform", exclude: ["anthropic"] })`,
|
|
257
|
+
{
|
|
258
|
+
prompt: z.string().describe("The question or problem to solve"),
|
|
259
|
+
system_prompt: z.string().optional().describe("Custom system prompt (default: neutral problem-solving)"),
|
|
260
|
+
exclude: z.array(z.string()).optional().describe("Provider keys to exclude: openai, gemini, grok, anthropic"),
|
|
261
|
+
},
|
|
262
|
+
async ({ prompt, system_prompt, exclude }) => {
|
|
263
|
+
try {
|
|
264
|
+
const result = await askAll(prompt, { systemPrompt: system_prompt, exclude });
|
|
265
|
+
return {
|
|
266
|
+
content: [{
|
|
267
|
+
type: "text",
|
|
268
|
+
text: JSON.stringify(result, null, 2),
|
|
269
|
+
}],
|
|
270
|
+
};
|
|
271
|
+
} catch (err) {
|
|
272
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// ─── council_debate ───────────────────────────────────────
|
|
278
|
+
server.tool(
|
|
279
|
+
"council_debate",
|
|
280
|
+
`Have AI providers critique each other's responses to a question.
|
|
281
|
+
First asks all AIs, then has them evaluate each other's answers.
|
|
282
|
+
Returns original responses + critiques.
|
|
283
|
+
|
|
284
|
+
Example: council_debate({ prompt: "Should we use microservices or monolith for a SaaS platform?" })`,
|
|
285
|
+
{
|
|
286
|
+
prompt: z.string().describe("The question to debate"),
|
|
287
|
+
},
|
|
288
|
+
async ({ prompt }) => {
|
|
289
|
+
try {
|
|
290
|
+
const askResult = await askAll(prompt);
|
|
291
|
+
if (askResult.error) {
|
|
292
|
+
return { content: [{ type: "text", text: JSON.stringify(askResult) }] };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const successResponses = askResult.responses.filter(r => !r.error && r.response);
|
|
296
|
+
if (successResponses.length < 2) {
|
|
297
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Need at least 2 AI responses to debate", responses: askResult.responses }) }] };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const debateResults = await debate(prompt, successResponses);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
content: [{
|
|
304
|
+
type: "text",
|
|
305
|
+
text: JSON.stringify({
|
|
306
|
+
prompt,
|
|
307
|
+
responses: askResult.responses,
|
|
308
|
+
debate: debateResults,
|
|
309
|
+
providers_used: askResult.providerCount,
|
|
310
|
+
}, null, 2),
|
|
311
|
+
}],
|
|
312
|
+
};
|
|
313
|
+
} catch (err) {
|
|
314
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// ─── council_solve ────────────────────────────────────────
|
|
320
|
+
server.tool(
|
|
321
|
+
"council_solve",
|
|
322
|
+
`Full multi-AI problem-solving pipeline:
|
|
323
|
+
1. Ask all AIs the question (parallel)
|
|
324
|
+
2. Have them critique each other
|
|
325
|
+
3. Synthesize the best answer from all responses
|
|
326
|
+
|
|
327
|
+
This is the most thorough approach — uses multiple AI perspectives
|
|
328
|
+
to arrive at the strongest possible answer.
|
|
329
|
+
|
|
330
|
+
Example: council_solve({ prompt: "Design a webhook verification system that handles Stripe, GitHub, and Slack" })`,
|
|
331
|
+
{
|
|
332
|
+
prompt: z.string().describe("The problem to solve"),
|
|
333
|
+
synthesis_prompt: z.string().optional().describe("Custom instructions for the final synthesis"),
|
|
334
|
+
},
|
|
335
|
+
async ({ prompt, synthesis_prompt }) => {
|
|
336
|
+
try {
|
|
337
|
+
// Step 1: Ask all
|
|
338
|
+
const askResult = await askAll(prompt);
|
|
339
|
+
if (askResult.error) {
|
|
340
|
+
return { content: [{ type: "text", text: JSON.stringify(askResult) }] };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const successResponses = askResult.responses.filter(r => !r.error && r.response);
|
|
344
|
+
|
|
345
|
+
// Step 2: Debate (if enough responses)
|
|
346
|
+
let debateResults = [];
|
|
347
|
+
if (successResponses.length >= 2) {
|
|
348
|
+
debateResults = await debate(prompt, successResponses);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Step 3: Synthesize — use the first available provider
|
|
352
|
+
const synthesisInput = successResponses.map(r =>
|
|
353
|
+
`### ${r.name}\n${r.response}`
|
|
354
|
+
).join('\n\n---\n\n');
|
|
355
|
+
|
|
356
|
+
const debateSummary = debateResults.map(d =>
|
|
357
|
+
`${d.critic.name} critiquing ${d.original.name}: ${d.critique}`
|
|
358
|
+
).join('\n\n');
|
|
359
|
+
|
|
360
|
+
const synthesisPrompt = `${synthesis_prompt || 'You are Jaxx, the AI orchestrator for the 0n ecosystem. Synthesize the best answer from multiple AI responses.'}
|
|
361
|
+
|
|
362
|
+
The question was: "${prompt}"
|
|
363
|
+
|
|
364
|
+
Here are the responses from different AI providers:
|
|
365
|
+
|
|
366
|
+
${synthesisInput}
|
|
367
|
+
|
|
368
|
+
${debateSummary ? `\nHere are the critiques:\n\n${debateSummary}` : ''}
|
|
369
|
+
|
|
370
|
+
Now synthesize the BEST possible answer by:
|
|
371
|
+
1. Taking the strongest points from each response
|
|
372
|
+
2. Addressing any weaknesses identified in the critiques
|
|
373
|
+
3. Adding anything all responses missed
|
|
374
|
+
4. Being direct and actionable
|
|
375
|
+
|
|
376
|
+
Give the final synthesized answer.`;
|
|
377
|
+
|
|
378
|
+
// Use the first available provider for synthesis
|
|
379
|
+
const synthesizer = successResponses[0];
|
|
380
|
+
const synthesis = await askProvider(synthesizer.provider, synthesisPrompt, 'You are Jaxx, a master AI synthesizer. Combine multiple perspectives into one optimal answer.');
|
|
381
|
+
|
|
382
|
+
// Auto-save to 0nAI training pipeline
|
|
383
|
+
const trainingResult = await saveToTraining(
|
|
384
|
+
prompt,
|
|
385
|
+
askResult.responses,
|
|
386
|
+
synthesis.response,
|
|
387
|
+
debateResults
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
content: [{
|
|
392
|
+
type: "text",
|
|
393
|
+
text: JSON.stringify({
|
|
394
|
+
prompt,
|
|
395
|
+
individual_responses: askResult.responses,
|
|
396
|
+
debate: debateResults.length > 0 ? debateResults : 'Skipped (< 2 responses)',
|
|
397
|
+
synthesis: {
|
|
398
|
+
synthesized_by: synthesis.name,
|
|
399
|
+
answer: synthesis.response,
|
|
400
|
+
durationMs: synthesis.durationMs,
|
|
401
|
+
},
|
|
402
|
+
meta: {
|
|
403
|
+
providers_asked: askResult.providerCount,
|
|
404
|
+
responses_received: successResponses.length,
|
|
405
|
+
debate_rounds: debateResults.length,
|
|
406
|
+
},
|
|
407
|
+
training: trainingResult,
|
|
408
|
+
}, null, 2),
|
|
409
|
+
}],
|
|
410
|
+
};
|
|
411
|
+
} catch (err) {
|
|
412
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }] };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// ─── council_config ───────────────────────────────────────
|
|
418
|
+
server.tool(
|
|
419
|
+
"council_config",
|
|
420
|
+
`Check which AI providers are available for the Multi-AI Council.
|
|
421
|
+
Shows which API keys are set and which providers are ready.
|
|
422
|
+
|
|
423
|
+
Example: council_config({})`,
|
|
424
|
+
{},
|
|
425
|
+
async () => {
|
|
426
|
+
const providers = getAvailableProviders();
|
|
427
|
+
const ready = providers.filter(p => p.ready);
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
content: [{
|
|
431
|
+
type: "text",
|
|
432
|
+
text: JSON.stringify({
|
|
433
|
+
total: providers.length,
|
|
434
|
+
ready: ready.length,
|
|
435
|
+
providers,
|
|
436
|
+
message: ready.length > 0
|
|
437
|
+
? `${ready.length}/${providers.length} providers ready: ${ready.map(p => p.name).join(', ')}`
|
|
438
|
+
: 'No providers configured. Set API keys in environment variables.',
|
|
439
|
+
}, null, 2),
|
|
440
|
+
}],
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ── Training Pipeline Integration ────────────────────────────
|
|
447
|
+
// Every council session auto-generates training pairs for 0nAI.
|
|
448
|
+
// The synthesis becomes the "ideal" answer, scored by agreement level.
|
|
449
|
+
|
|
450
|
+
async function saveToTraining(prompt, responses, synthesis, debateResults) {
|
|
451
|
+
try {
|
|
452
|
+
const { createClient } = await import("@supabase/supabase-js");
|
|
453
|
+
const url = process.env.SUPABASE_URL || "https://pwujhhmlrtxjmjzyttwn.supabase.co";
|
|
454
|
+
const key = process.env.SUPABASE_SERVICE_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
455
|
+
if (!key) return { saved: false, reason: "No Supabase key" };
|
|
456
|
+
|
|
457
|
+
const sb = createClient(url, key);
|
|
458
|
+
|
|
459
|
+
// Calculate quality score from agreement level
|
|
460
|
+
const successful = responses.filter(r => !r.error && r.response);
|
|
461
|
+
if (successful.length < 2 || !synthesis) return { saved: false, reason: "Not enough responses" };
|
|
462
|
+
|
|
463
|
+
// Agreement score: how many AIs gave similar answers (rough heuristic)
|
|
464
|
+
const words = new Set();
|
|
465
|
+
successful.forEach(r => {
|
|
466
|
+
(r.response || "").toLowerCase().split(/\s+/).filter(w => w.length > 4).forEach(w => words.add(w));
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const totalUniqueWords = words.size;
|
|
470
|
+
const sharedWords = [...words].filter(w =>
|
|
471
|
+
successful.every(r => (r.response || "").toLowerCase().includes(w))
|
|
472
|
+
).length;
|
|
473
|
+
|
|
474
|
+
const agreementScore = totalUniqueWords > 0
|
|
475
|
+
? Math.min(1.0, Math.max(0.3, 0.5 + (sharedWords / totalUniqueWords) * 0.5))
|
|
476
|
+
: 0.5;
|
|
477
|
+
|
|
478
|
+
// Save the synthesis as a training pair
|
|
479
|
+
const { data, error } = await sb.from("training_pairs").insert({
|
|
480
|
+
user_input: prompt,
|
|
481
|
+
assistant_output: synthesis,
|
|
482
|
+
system_prompt: null,
|
|
483
|
+
domain: "council",
|
|
484
|
+
difficulty: "medium",
|
|
485
|
+
quality_score: Math.round(agreementScore * 100) / 100,
|
|
486
|
+
human_reviewed: false,
|
|
487
|
+
approved: false,
|
|
488
|
+
tags: ["council-generated", "multi-ai"],
|
|
489
|
+
metadata: {
|
|
490
|
+
source: "multi-ai-council",
|
|
491
|
+
providers: successful.map(r => r.provider),
|
|
492
|
+
provider_count: successful.length,
|
|
493
|
+
agreement_score: agreementScore,
|
|
494
|
+
debate_rounds: debateResults?.length || 0,
|
|
495
|
+
generated_at: new Date().toISOString(),
|
|
496
|
+
},
|
|
497
|
+
}).select("id").single();
|
|
498
|
+
|
|
499
|
+
if (error) return { saved: false, reason: error.message };
|
|
500
|
+
|
|
501
|
+
// Also save each individual response as a training source
|
|
502
|
+
for (const r of successful) {
|
|
503
|
+
await sb.from("training_sources").insert({
|
|
504
|
+
source_type: "council",
|
|
505
|
+
title: `Council: ${r.name} on "${prompt.slice(0, 80)}"`,
|
|
506
|
+
content: r.response.slice(0, 5000),
|
|
507
|
+
token_count: Math.ceil(r.response.length / 4),
|
|
508
|
+
tags: ["council", r.provider],
|
|
509
|
+
status: "raw",
|
|
510
|
+
metadata: {
|
|
511
|
+
provider: r.provider,
|
|
512
|
+
name: r.name,
|
|
513
|
+
duration_ms: r.durationMs,
|
|
514
|
+
prompt: prompt.slice(0, 500),
|
|
515
|
+
},
|
|
516
|
+
}).catch(() => {}); // non-critical
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return { saved: true, pair_id: data?.id, quality_score: agreementScore };
|
|
520
|
+
} catch (err) {
|
|
521
|
+
return { saved: false, reason: err.message };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export { getAvailableProviders, askProvider, askAll, debate, saveToTraining, PROVIDERS };
|