prism-mcp-server 7.8.4 β 7.8.7
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 +7 -4
- package/dist/dashboard/server.js +7 -2
- package/dist/storage/supabase.js +38 -3
- package/dist/tools/hygieneHandlers.js +61 -33
- package/dist/utils/llm/adapters/traced.js +35 -0
- package/dist/utils/llm/adapters/voyage.js +58 -33
- package/dist/utils/llm/factory.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -691,9 +691,9 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
|
|
|
691
691
|
|
|
692
692
|
## π What's New
|
|
693
693
|
|
|
694
|
-
> **Current release: v7.8.
|
|
694
|
+
> **Current release: v7.8.7 β Cognitive Architecture**
|
|
695
695
|
|
|
696
|
-
- π§ **v7.8.
|
|
696
|
+
- π§ **v7.8.x β Cognitive Architecture:** The biggest leap forward yet. Moved beyond flat vector search into a true cognitive architecture inspired by human brain mechanics. Episodic-to-Semantic memory consolidation (Hebbian learning), ACT-R Spreading Activation with multi-hop causal reasoning, Uncertainty-Aware Rejection Gate (your agent can say "I don't know"), and Dynamic Fast Weight Decay (semantic memories outlive episodic chatter by 2Γ). Validated by **LoCoMo-Plus benchmark** (arXiv 2602.10715) with Precision@K and MRR metrics. **Your agents don't just remember; they learn.** β [Cognitive Architecture](#-cognitive-architecture-v78)
|
|
697
697
|
- π **v7.7.0 β Cloud-Native SSE Transport:** Full unauthenticated and authenticated Server-Sent Events MCP support for seamless network deployments.
|
|
698
698
|
- π©Ί **v7.5.0 β Intent Health Dashboard + Security Hardening:** Real-time 0β100 project health scoring (staleness Γ TODO load Γ decisions). 10 XSS injection vectors patched. Algorithm hardened with NaN guards and score ceiling.
|
|
699
699
|
- βοΈ **v7.4.0 β Adversarial Evaluation:** Split-brain anti-sycophancy pipeline. Generator and evaluator in isolated roles with evidence-bound findings.
|
|
@@ -968,6 +968,7 @@ Prism is a **stdio-based MCP server** that manages persistent agent memory. Here
|
|
|
968
968
|
β β β’ ACT-R Spreading Activation (multi-hop) β β
|
|
969
969
|
β β β’ Episodic β Semantic Consolidation (Hebbian) β β
|
|
970
970
|
β β β’ Uncertainty-Aware Rejection Gate β β
|
|
971
|
+
β β β’ LoCoMo-Plus Benchmark Validation β β
|
|
971
972
|
β β β’ Dynamic Fast Weight Decay (dual-rate) β β
|
|
972
973
|
β β β’ HDC Cognitive Routing (XOR binding) β β
|
|
973
974
|
β ββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββ β
|
|
@@ -1056,16 +1057,18 @@ Prism has evolved from smart session logging into a **cognitive memory architect
|
|
|
1056
1057
|
| **v7.8** | Multi-Hop Causal Reasoning β spreading activation traverses `caused_by`/`led_to` edges with damped fan effect (`1/ln(fan+e)`) and lateral inhibition | ACT-R spreading activation (Anderson), Collins & Loftus (1975) | β
Shipped |
|
|
1057
1058
|
| **v7.8** | Uncertainty-Aware Rejection Gate β dual-signal (similarity floor + gap distance) safety layer prevents hallucination from low-confidence retrievals | Metacognition research, uncertainty quantification | β
Shipped |
|
|
1058
1059
|
| **v7.8** | Dynamic Fast Weight Decay β `is_rollup` semantic nodes decay 50% slower (`ageModifier = 0.5`) than episodic entries, creating Long-Term Context anchors | ACT-R base-level activation with differential decay rates | β
Shipped |
|
|
1060
|
+
| **v7.8** | LoCoMo Benchmark Harness β deterministic integration suite (`tests/benchmarks/locomo.ts`, 20 assertions) benchmarking multi-hop compaction structures via `MockLLM` | Long-Context Memory evaluation (cognitive benchmarking) | β
Shipped |
|
|
1061
|
+
| **v7.8** | LoCoMo-Plus Benchmark β 16-assertion suite (`tests/benchmarks/locomo-plus.ts`) adapted from arXiv 2602.10715 validating cueβtrigger semantic disconnect bridging via graph traversal and Hebbian consolidation; reports Precision@1/3/5/10 and MRR | LoCoMo-Plus (Li et al., ARR 2026), cueβtrigger disconnect research | β
Shipped |
|
|
1059
1062
|
| **v7.x** | Affect-Tagged Memory β sentiment shapes what gets recalled | Affect-modulated retrieval (neuroscience) | π Horizon |
|
|
1060
1063
|
| **v8+** | Zero-Search Retrieval β no index, no ANN, just ask the vector | Holographic Reduced Representations | π Horizon |
|
|
1061
1064
|
|
|
1062
|
-
> Informed by Anderson's ACT-R (Adaptive Control of ThoughtβRational), Collins & Loftus spreading activation networks (1975), Kanerva's SDM (1988), Hebb's learning rule, and LeCun's "Why AI Systems Don't Learn" (Dupoux, LeCun, Malik).
|
|
1065
|
+
> Informed by Anderson's ACT-R (Adaptive Control of ThoughtβRational), Collins & Loftus spreading activation networks (1975), Kanerva's SDM (1988), Hebb's learning rule, Li et al. LoCoMo-Plus (ARR 2026), and LeCun's "Why AI Systems Don't Learn" (Dupoux, LeCun, Malik).
|
|
1063
1066
|
|
|
1064
1067
|
---
|
|
1065
1068
|
|
|
1066
1069
|
## π¦ Milestones & Roadmap
|
|
1067
1070
|
|
|
1068
|
-
> **Current: v7.8.
|
|
1071
|
+
> **Current: v7.8.7** β Cognitive Architecture ([CHANGELOG](CHANGELOG.md))
|
|
1069
1072
|
|
|
1070
1073
|
| Release | Headline |
|
|
1071
1074
|
|---------|----------|
|
package/dist/dashboard/server.js
CHANGED
|
@@ -445,6 +445,7 @@ return false;}
|
|
|
445
445
|
let cursorId = undefined;
|
|
446
446
|
let iterations = 0;
|
|
447
447
|
const MAX_ITERATIONS = 100; // safety cap: 100 Γ 50 = 5000 entries max
|
|
448
|
+
let lastBackfillError = undefined;
|
|
448
449
|
while (hasMore && iterations < MAX_ITERATIONS) {
|
|
449
450
|
iterations++;
|
|
450
451
|
const result = await backfillEmbeddingsHandler({ dry_run: false, limit: 50, _cursor_id: cursorId });
|
|
@@ -452,6 +453,8 @@ return false;}
|
|
|
452
453
|
if (bStats) {
|
|
453
454
|
repairedCount += bStats.repaired;
|
|
454
455
|
failedCount += bStats.failed;
|
|
456
|
+
if (bStats.error)
|
|
457
|
+
lastBackfillError = bStats.error;
|
|
455
458
|
if (bStats.last_id)
|
|
456
459
|
cursorId = bStats.last_id;
|
|
457
460
|
else
|
|
@@ -464,8 +467,10 @@ return false;}
|
|
|
464
467
|
}
|
|
465
468
|
}
|
|
466
469
|
cleanupMessages.push(`Repaired ${repairedCount} embeddings`);
|
|
467
|
-
if (failedCount > 0)
|
|
468
|
-
|
|
470
|
+
if (failedCount > 0) {
|
|
471
|
+
const errMsg = lastBackfillError ? ` (${lastBackfillError})` : '';
|
|
472
|
+
cleanupMessages.push(`Failed to repair ${failedCount} embeddings${errMsg}`);
|
|
473
|
+
}
|
|
469
474
|
}
|
|
470
475
|
catch (err) {
|
|
471
476
|
console.error("[Dashboard] Failed to backfill embeddings:", err);
|
package/dist/storage/supabase.js
CHANGED
|
@@ -1442,8 +1442,43 @@ export class SupabaseStorage {
|
|
|
1442
1442
|
}
|
|
1443
1443
|
// βββ v7.5: Semantic Consolidation ββββββββββββββββββββββββββββββββ
|
|
1444
1444
|
async upsertSemanticKnowledge(data) {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1445
|
+
const userId = data.userId || PRISM_USER_ID;
|
|
1446
|
+
// Check if concept already exists
|
|
1447
|
+
const existing = await supabaseGet("semantic_knowledge", {
|
|
1448
|
+
project: `eq.${data.project}`,
|
|
1449
|
+
concept: `eq.${data.concept}`,
|
|
1450
|
+
select: "id,instances,confidence",
|
|
1451
|
+
limit: "1"
|
|
1452
|
+
});
|
|
1453
|
+
const rows = Array.isArray(existing) ? existing : [];
|
|
1454
|
+
if (rows.length > 0) {
|
|
1455
|
+
const row = rows[0];
|
|
1456
|
+
const newConfidence = Math.min(1.0, (row.confidence || 0) + 0.1);
|
|
1457
|
+
const newInstances = (row.instances || 0) + 1;
|
|
1458
|
+
await supabasePatch("semantic_knowledge", {
|
|
1459
|
+
instances: newInstances,
|
|
1460
|
+
confidence: newConfidence,
|
|
1461
|
+
updated_at: new Date().toISOString()
|
|
1462
|
+
}, {
|
|
1463
|
+
id: `eq.${row.id}`
|
|
1464
|
+
});
|
|
1465
|
+
return row.id;
|
|
1466
|
+
}
|
|
1467
|
+
else {
|
|
1468
|
+
const id = crypto.randomUUID();
|
|
1469
|
+
await supabasePost("semantic_knowledge", {
|
|
1470
|
+
id,
|
|
1471
|
+
project: data.project,
|
|
1472
|
+
user_id: userId,
|
|
1473
|
+
concept: data.concept,
|
|
1474
|
+
description: data.description,
|
|
1475
|
+
confidence: 0.5,
|
|
1476
|
+
instances: 1,
|
|
1477
|
+
related_entities: data.related_entities ? JSON.stringify(data.related_entities) : "[]",
|
|
1478
|
+
created_at: new Date().toISOString(),
|
|
1479
|
+
updated_at: new Date().toISOString()
|
|
1480
|
+
});
|
|
1481
|
+
return id;
|
|
1482
|
+
}
|
|
1448
1483
|
}
|
|
1449
1484
|
}
|
|
@@ -98,46 +98,74 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
98
98
|
isError: false,
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
|
-
// Generate embeddings for each entry
|
|
102
101
|
let repaired = 0;
|
|
103
102
|
let failed = 0;
|
|
104
|
-
|
|
103
|
+
let lastError = undefined;
|
|
104
|
+
const validEntries = entries.map(e => {
|
|
105
|
+
const entry = e;
|
|
106
|
+
const textToEmbed = [
|
|
107
|
+
entry.summary || "",
|
|
108
|
+
...(entry.decisions || []),
|
|
109
|
+
].filter(Boolean).join(" | ");
|
|
110
|
+
return { entry, textToEmbed };
|
|
111
|
+
}).filter(x => {
|
|
112
|
+
if (!x.textToEmbed.trim()) {
|
|
113
|
+
debugLog(`[backfill] Skipping entry ${x.entry.id}: no text content`);
|
|
114
|
+
failed++;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
if (validEntries.length > 0) {
|
|
120
|
+
const provider = getLLMProvider();
|
|
105
121
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
].filter(Boolean).join(" | ");
|
|
111
|
-
if (!textToEmbed.trim()) {
|
|
112
|
-
debugLog(`[backfill] Skipping entry ${e.id}: no text content`);
|
|
113
|
-
failed++;
|
|
114
|
-
continue;
|
|
122
|
+
let embeddings;
|
|
123
|
+
if (provider.generateEmbeddings) {
|
|
124
|
+
// Use batch API
|
|
125
|
+
embeddings = await provider.generateEmbeddings(validEntries.map(x => x.textToEmbed));
|
|
115
126
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const { getDefaultCompressor, serialize } = await import("../utils/turboquant.js");
|
|
124
|
-
const compressor = getDefaultCompressor();
|
|
125
|
-
const compressed = compressor.compress(embedding);
|
|
126
|
-
const buf = serialize(compressed);
|
|
127
|
-
patchData.embedding_compressed = buf.toString("base64");
|
|
128
|
-
patchData.embedding_format = `turbo${compressor.bits}`;
|
|
129
|
-
patchData.embedding_turbo_radius = compressed.radius;
|
|
127
|
+
else {
|
|
128
|
+
// Fallback to sequential if batching is not supported by the adapter
|
|
129
|
+
embeddings = [];
|
|
130
|
+
for (const { textToEmbed } of validEntries) {
|
|
131
|
+
embeddings.push(await provider.generateEmbedding(textToEmbed));
|
|
132
|
+
}
|
|
130
133
|
}
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
for (let i = 0; i < validEntries.length; i++) {
|
|
135
|
+
const { entry } = validEntries[i];
|
|
136
|
+
const embedding = embeddings[i];
|
|
137
|
+
try {
|
|
138
|
+
const patchData = {
|
|
139
|
+
embedding: JSON.stringify(embedding),
|
|
140
|
+
};
|
|
141
|
+
try {
|
|
142
|
+
const { getDefaultCompressor, serialize } = await import("../utils/turboquant.js");
|
|
143
|
+
const compressor = getDefaultCompressor();
|
|
144
|
+
const compressed = compressor.compress(embedding);
|
|
145
|
+
const buf = serialize(compressed);
|
|
146
|
+
patchData.embedding_compressed = buf.toString("base64");
|
|
147
|
+
patchData.embedding_format = `turbo${compressor.bits}`;
|
|
148
|
+
patchData.embedding_turbo_radius = compressed.radius;
|
|
149
|
+
}
|
|
150
|
+
catch (turboErr) {
|
|
151
|
+
debugLog(`[backfill] TurboQuant compression failed for ${entry.id} (non-fatal): ${turboErr.message}`);
|
|
152
|
+
}
|
|
153
|
+
await storage.patchLedger(entry.id, patchData);
|
|
154
|
+
repaired++;
|
|
155
|
+
debugLog(`[backfill] β
Repaired ${entry.id} (${entry.project})`);
|
|
156
|
+
}
|
|
157
|
+
catch (entryErr) {
|
|
158
|
+
failed++;
|
|
159
|
+
lastError = entryErr instanceof Error ? entryErr.message : String(entryErr);
|
|
160
|
+
console.error(`[backfill] β Failed ${entry.id}: ${lastError}`);
|
|
161
|
+
}
|
|
133
162
|
}
|
|
134
|
-
await storage.patchLedger(e.id, patchData);
|
|
135
|
-
repaired++;
|
|
136
|
-
debugLog(`[backfill] β
Repaired ${e.id} (${e.project})`);
|
|
137
163
|
}
|
|
138
164
|
catch (err) {
|
|
139
|
-
failed
|
|
140
|
-
|
|
165
|
+
// Embedding API call itself failed β entire batch is lost.
|
|
166
|
+
failed += validEntries.length;
|
|
167
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
168
|
+
console.error(`[backfill] β Embedding API failed for batch of ${validEntries.length}: ${lastError}`);
|
|
141
169
|
}
|
|
142
170
|
}
|
|
143
171
|
return {
|
|
@@ -152,7 +180,7 @@ export async function backfillEmbeddingsHandler(args) {
|
|
|
152
180
|
: `All entries now have embeddings for semantic search.`),
|
|
153
181
|
}],
|
|
154
182
|
isError: false,
|
|
155
|
-
_stats: { repaired, failed, last_id: entries[entries.length - 1]?.id },
|
|
183
|
+
_stats: { repaired, failed, error: lastError, last_id: entries[entries.length - 1]?.id },
|
|
156
184
|
};
|
|
157
185
|
}
|
|
158
186
|
export async function sessionBackfillLinksHandler(args) {
|
|
@@ -46,6 +46,10 @@ import { getTracer } from "../../telemetry.js";
|
|
|
46
46
|
export class TracingLLMProvider {
|
|
47
47
|
inner;
|
|
48
48
|
providerName;
|
|
49
|
+
/**
|
|
50
|
+
* Optional batch embeddings generation support.
|
|
51
|
+
*/
|
|
52
|
+
generateEmbeddings;
|
|
49
53
|
/**
|
|
50
54
|
* The optional VLM method is declared here as a typed property so TypeScript
|
|
51
55
|
* knows about it. It is assigned (or left undefined) in the constructor body
|
|
@@ -62,6 +66,37 @@ export class TracingLLMProvider {
|
|
|
62
66
|
constructor(inner, providerName) {
|
|
63
67
|
this.inner = inner;
|
|
64
68
|
this.providerName = providerName;
|
|
69
|
+
// ββ Batch Embeddings: conditional own-property assignment βββββββββββββββ
|
|
70
|
+
if (inner.generateEmbeddings) {
|
|
71
|
+
const innerEmbeds = inner.generateEmbeddings.bind(inner);
|
|
72
|
+
const providerName = this.providerName;
|
|
73
|
+
this.generateEmbeddings = async (texts) => {
|
|
74
|
+
const span = getTracer().startSpan("llm.generate_embeddings_batch", {
|
|
75
|
+
attributes: {
|
|
76
|
+
"llm.provider": providerName,
|
|
77
|
+
"llm.batch_size": texts.length,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
return context.with(trace.setSpan(context.active(), span), async () => {
|
|
81
|
+
try {
|
|
82
|
+
const result = await innerEmbeds(texts);
|
|
83
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
span.recordException(err instanceof Error ? err : new Error(String(err)));
|
|
88
|
+
span.setStatus({
|
|
89
|
+
code: SpanStatusCode.ERROR,
|
|
90
|
+
message: err instanceof Error ? err.message : String(err),
|
|
91
|
+
});
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
span.end();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
}
|
|
65
100
|
// ββ VLM method: conditional own-property assignment ββββββββββββββββββ
|
|
66
101
|
// REVIEWER NOTE: TypeScript class methods always appear on the prototype,
|
|
67
102
|
// which means `if (llm.generateImageDescription)` would always be truthy
|
|
@@ -78,53 +78,78 @@ export class VoyageAdapter {
|
|
|
78
78
|
"Set text_provider to 'anthropic', 'openai', or 'gemini' in the dashboard.");
|
|
79
79
|
}
|
|
80
80
|
// βββ Embedding Generation ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
81
|
-
async
|
|
82
|
-
if (!
|
|
83
|
-
|
|
84
|
-
}
|
|
81
|
+
async generateEmbeddings(texts) {
|
|
82
|
+
if (!texts || texts.length === 0)
|
|
83
|
+
return [];
|
|
85
84
|
// Truncate to character limit (consistent with other adapters)
|
|
86
|
-
const
|
|
85
|
+
const truncatedTexts = texts.map(text => text.length > MAX_EMBEDDING_CHARS
|
|
87
86
|
? text.slice(0, MAX_EMBEDDING_CHARS).replace(/\s+\S*$/, "")
|
|
88
|
-
: text;
|
|
87
|
+
: text);
|
|
89
88
|
const model = getSettingSync("voyage_model", DEFAULT_MODEL);
|
|
90
|
-
debugLog(`[VoyageAdapter]
|
|
89
|
+
debugLog(`[VoyageAdapter] generateEmbeddings batch β model=${model}, count=${texts.length}`);
|
|
91
90
|
const requestBody = {
|
|
92
|
-
input:
|
|
91
|
+
input: truncatedTexts,
|
|
93
92
|
model,
|
|
94
93
|
// We do NOT send output_dimension here because Voyage's API explicitly
|
|
95
94
|
// restricts it to [256, 512, 1024, 2048] for MRL models. We will
|
|
96
95
|
// manually slice the 1024-dim result down to 768 client-side.
|
|
97
96
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
let response = null;
|
|
98
|
+
let retries = 0;
|
|
99
|
+
const maxRetries = 4;
|
|
100
|
+
const baseDelayMs = 15000; // 15 seconds base delay
|
|
101
|
+
while (true) {
|
|
102
|
+
response = await fetch(`${VOYAGE_API_BASE}/embeddings`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify(requestBody),
|
|
109
|
+
});
|
|
110
|
+
if (response.ok) {
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
107
113
|
const errorText = await response.text().catch(() => "unknown error");
|
|
114
|
+
if (response.status === 429 && retries < maxRetries) {
|
|
115
|
+
// Simple backoff: baseDelayMs * (retries + 1) -> 15s, 30s, 45s, 60s
|
|
116
|
+
const delay = baseDelayMs * (retries + 1);
|
|
117
|
+
retries++;
|
|
118
|
+
debugLog(`[VoyageAdapter] Rate limited (429). Retrying in ${delay}ms... (Attempt ${retries}/${maxRetries}): ${errorText.substring(0, 50)}...`);
|
|
119
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
108
122
|
throw new Error(`[VoyageAdapter] API request failed β status=${response.status}: ${errorText}`);
|
|
109
123
|
}
|
|
110
124
|
const data = (await response.json());
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
throw new Error(
|
|
114
|
-
}
|
|
115
|
-
// Client-side MRL Truncation:
|
|
116
|
-
// Voyage models returning 1024 dims can be safely sliced to 768 since they
|
|
117
|
-
// are trained with Matryoshka Representation Learning.
|
|
118
|
-
if (embedding.length > EMBEDDING_DIMS) {
|
|
119
|
-
embedding = embedding.slice(0, EMBEDDING_DIMS);
|
|
120
|
-
}
|
|
121
|
-
// Dimension guard: Prism's DB schema requires exactly 768 dims.
|
|
122
|
-
if (embedding.length !== EMBEDDING_DIMS) {
|
|
123
|
-
throw new Error(`[VoyageAdapter] Embedding dimension mismatch: expected ${EMBEDDING_DIMS}, ` +
|
|
124
|
-
`got ${embedding.length}. Make sure you are using a model that returns at least 768 dims.`);
|
|
125
|
+
const embeddings = data?.data?.map(d => d.embedding) || [];
|
|
126
|
+
if (embeddings.length !== texts.length) {
|
|
127
|
+
throw new Error(`[VoyageAdapter] Unexpected response length β expected ${texts.length}, got ${embeddings.length}`);
|
|
125
128
|
}
|
|
126
|
-
|
|
129
|
+
const processedEmbeddings = embeddings.map(emb => {
|
|
130
|
+
let embedding = emb;
|
|
131
|
+
// Client-side MRL Truncation:
|
|
132
|
+
// Voyage models returning 1024 dims can be safely sliced to 768 since they
|
|
133
|
+
// are trained with Matryoshka Representation Learning.
|
|
134
|
+
if (embedding.length > EMBEDDING_DIMS) {
|
|
135
|
+
embedding = embedding.slice(0, EMBEDDING_DIMS);
|
|
136
|
+
}
|
|
137
|
+
// Dimension guard: Prism's DB schema requires exactly 768 dims.
|
|
138
|
+
if (embedding.length !== EMBEDDING_DIMS) {
|
|
139
|
+
throw new Error(`[VoyageAdapter] Embedding dimension mismatch: expected ${EMBEDDING_DIMS}, ` +
|
|
140
|
+
`got ${embedding.length}. Make sure you are using a model that returns at least 768 dims.`);
|
|
141
|
+
}
|
|
142
|
+
return embedding;
|
|
143
|
+
});
|
|
144
|
+
debugLog(`[VoyageAdapter] Batch embeddings generated β count=${processedEmbeddings.length}, ` +
|
|
127
145
|
`tokens_used=${data.usage?.total_tokens ?? "unknown"}`);
|
|
128
|
-
return
|
|
146
|
+
return processedEmbeddings;
|
|
147
|
+
}
|
|
148
|
+
async generateEmbedding(text) {
|
|
149
|
+
if (!text || !text.trim()) {
|
|
150
|
+
throw new Error("[VoyageAdapter] generateEmbedding called with empty text");
|
|
151
|
+
}
|
|
152
|
+
const results = await this.generateEmbeddings([text]);
|
|
153
|
+
return results[0];
|
|
129
154
|
}
|
|
130
155
|
}
|
|
@@ -117,6 +117,10 @@ export function getLLMProvider() {
|
|
|
117
117
|
generateText: textAdapter.generateText.bind(textAdapter),
|
|
118
118
|
generateEmbedding: embedAdapter.generateEmbedding.bind(embedAdapter),
|
|
119
119
|
};
|
|
120
|
+
// Wire batch embeddings if the embed adapter supports it (e.g. VoyageAdapter).
|
|
121
|
+
if (embedAdapter.generateEmbeddings) {
|
|
122
|
+
composed.generateEmbeddings = embedAdapter.generateEmbeddings.bind(embedAdapter);
|
|
123
|
+
}
|
|
120
124
|
// Pass VLM support through from the text adapter if it exists.
|
|
121
125
|
// generateImageDescription is a text-generation concern (it calls the
|
|
122
126
|
// text/vision model, not the embedding model). The text adapter owns it.
|
|
@@ -141,6 +145,9 @@ export function getLLMProvider() {
|
|
|
141
145
|
generateText: fallback.generateText.bind(fallback),
|
|
142
146
|
generateEmbedding: fallback.generateEmbedding.bind(fallback),
|
|
143
147
|
};
|
|
148
|
+
if (typeof fallback.generateEmbeddings === 'function') {
|
|
149
|
+
fallbackComposed.generateEmbeddings = fallback.generateEmbeddings.bind(fallback);
|
|
150
|
+
}
|
|
144
151
|
if (fallback.generateImageDescription) {
|
|
145
152
|
fallbackComposed.generateImageDescription = fallback.generateImageDescription.bind(fallback);
|
|
146
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "7.8.
|
|
3
|
+
"version": "7.8.7",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-mcp",
|
|
5
5
|
"description": "The Mind Palace for AI Agents β a true Cognitive Architecture with Hebbian learning (episodicβsemantic consolidation), ACT-R spreading activation (multi-hop causal reasoning), uncertainty-aware rejection gates (agents that know when they don't know), adversarial evaluation (anti-sycophancy), fail-closed Dark Factory pipelines, persistent memory (SQLite/Supabase), multi-agent Hivemind, time travel & visual dashboard. Zero-config local mode.",
|
|
6
6
|
"module": "index.ts",
|