vesper-memory 0.3.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/.env.example +28 -0
- package/CHANGELOG.md +137 -0
- package/LICENSE +21 -0
- package/README.md +665 -0
- package/config/sqlite-schema.sql +413 -0
- package/dist/benchmark/index.d.ts +18 -0
- package/dist/benchmark/index.d.ts.map +1 -0
- package/dist/benchmark/index.js +19 -0
- package/dist/benchmark/index.js.map +1 -0
- package/dist/benchmark/metrics-collector.d.ts +90 -0
- package/dist/benchmark/metrics-collector.d.ts.map +1 -0
- package/dist/benchmark/metrics-collector.js +196 -0
- package/dist/benchmark/metrics-collector.js.map +1 -0
- package/dist/benchmark/report-generator.d.ts +35 -0
- package/dist/benchmark/report-generator.d.ts.map +1 -0
- package/dist/benchmark/report-generator.js +221 -0
- package/dist/benchmark/report-generator.js.map +1 -0
- package/dist/benchmark/runner.d.ts +30 -0
- package/dist/benchmark/runner.d.ts.map +1 -0
- package/dist/benchmark/runner.js +308 -0
- package/dist/benchmark/runner.js.map +1 -0
- package/dist/benchmark/scenarios/context-recall.d.ts +65 -0
- package/dist/benchmark/scenarios/context-recall.d.ts.map +1 -0
- package/dist/benchmark/scenarios/context-recall.js +180 -0
- package/dist/benchmark/scenarios/context-recall.js.map +1 -0
- package/dist/benchmark/scenarios/index.d.ts +34 -0
- package/dist/benchmark/scenarios/index.d.ts.map +1 -0
- package/dist/benchmark/scenarios/index.js +230 -0
- package/dist/benchmark/scenarios/index.js.map +1 -0
- package/dist/benchmark/statistics.d.ts +125 -0
- package/dist/benchmark/statistics.d.ts.map +1 -0
- package/dist/benchmark/statistics.js +520 -0
- package/dist/benchmark/statistics.js.map +1 -0
- package/dist/benchmark/types.d.ts +458 -0
- package/dist/benchmark/types.d.ts.map +1 -0
- package/dist/benchmark/types.js +297 -0
- package/dist/benchmark/types.js.map +1 -0
- package/dist/cli 2.js +583 -0
- package/dist/cli.d 2.ts +8 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts 2.map +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +583 -0
- package/dist/cli.js 2.map +1 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/rate-limits.d.ts +76 -0
- package/dist/config/rate-limits.d.ts.map +1 -0
- package/dist/config/rate-limits.js +161 -0
- package/dist/config/rate-limits.js.map +1 -0
- package/dist/config 2/rate-limits.d.ts +76 -0
- package/dist/config 2/rate-limits.d.ts.map +1 -0
- package/dist/config 2/rate-limits.js +161 -0
- package/dist/config 2/rate-limits.js.map +1 -0
- package/dist/consolidation/pipeline.d.ts +52 -0
- package/dist/consolidation/pipeline.d.ts.map +1 -0
- package/dist/consolidation/pipeline.js +193 -0
- package/dist/consolidation/pipeline.js.map +1 -0
- package/dist/consolidation 2/pipeline.d.ts +52 -0
- package/dist/consolidation 2/pipeline.d.ts.map +1 -0
- package/dist/consolidation 2/pipeline.js +193 -0
- package/dist/consolidation 2/pipeline.js.map +1 -0
- package/dist/embeddings/client.d.ts +95 -0
- package/dist/embeddings/client.d.ts.map +1 -0
- package/dist/embeddings/client.js +162 -0
- package/dist/embeddings/client.js.map +1 -0
- package/dist/embeddings 2/client.d.ts +94 -0
- package/dist/embeddings 2/client.d.ts.map +1 -0
- package/dist/embeddings 2/client.js +144 -0
- package/dist/embeddings 2/client.js.map +1 -0
- package/dist/memory-layers/semantic-memory.d.ts +76 -0
- package/dist/memory-layers/semantic-memory.d.ts.map +1 -0
- package/dist/memory-layers/semantic-memory.js +231 -0
- package/dist/memory-layers/semantic-memory.js.map +1 -0
- package/dist/memory-layers/skill-library.d.ts +25 -0
- package/dist/memory-layers/skill-library.d.ts.map +1 -0
- package/dist/memory-layers/skill-library.js +54 -0
- package/dist/memory-layers/skill-library.js.map +1 -0
- package/dist/memory-layers/working-memory.d.ts +57 -0
- package/dist/memory-layers/working-memory.d.ts.map +1 -0
- package/dist/memory-layers/working-memory.js +153 -0
- package/dist/memory-layers/working-memory.js.map +1 -0
- package/dist/memory-layers 2/semantic-memory.d.ts +33 -0
- package/dist/memory-layers 2/semantic-memory.d.ts.map +1 -0
- package/dist/memory-layers 2/semantic-memory.js +114 -0
- package/dist/memory-layers 2/semantic-memory.js.map +1 -0
- package/dist/memory-layers 2/skill-library.d.ts +25 -0
- package/dist/memory-layers 2/skill-library.d.ts.map +1 -0
- package/dist/memory-layers 2/skill-library.js +54 -0
- package/dist/memory-layers 2/skill-library.js.map +1 -0
- package/dist/memory-layers 2/working-memory.d.ts +57 -0
- package/dist/memory-layers 2/working-memory.d.ts.map +1 -0
- package/dist/memory-layers 2/working-memory.js +153 -0
- package/dist/memory-layers 2/working-memory.js.map +1 -0
- package/dist/middleware/auth.d.ts +287 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +610 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware 2/auth.d.ts +287 -0
- package/dist/middleware 2/auth.d.ts.map +1 -0
- package/dist/middleware 2/auth.js +610 -0
- package/dist/middleware 2/auth.js.map +1 -0
- package/dist/monitoring/health.d.ts +241 -0
- package/dist/monitoring/health.d.ts.map +1 -0
- package/dist/monitoring/health.js +649 -0
- package/dist/monitoring/health.js.map +1 -0
- package/dist/monitoring/metrics.d.ts +153 -0
- package/dist/monitoring/metrics.d.ts.map +1 -0
- package/dist/monitoring/metrics.js +349 -0
- package/dist/monitoring/metrics.js.map +1 -0
- package/dist/monitoring 2/health.d.ts +241 -0
- package/dist/monitoring 2/health.d.ts.map +1 -0
- package/dist/monitoring 2/health.js +649 -0
- package/dist/monitoring 2/health.js.map +1 -0
- package/dist/monitoring 2/metrics.d.ts +153 -0
- package/dist/monitoring 2/metrics.d.ts.map +1 -0
- package/dist/monitoring 2/metrics.js +349 -0
- package/dist/monitoring 2/metrics.js.map +1 -0
- package/dist/retrieval/hybrid-search.d.ts +186 -0
- package/dist/retrieval/hybrid-search.d.ts.map +1 -0
- package/dist/retrieval/hybrid-search.js +330 -0
- package/dist/retrieval/hybrid-search.js.map +1 -0
- package/dist/retrieval 2/hybrid-search.d.ts +186 -0
- package/dist/retrieval 2/hybrid-search.d.ts.map +1 -0
- package/dist/retrieval 2/hybrid-search.js +327 -0
- package/dist/retrieval 2/hybrid-search.js.map +1 -0
- package/dist/router/smart-router.d.ts +157 -0
- package/dist/router/smart-router.d.ts.map +1 -0
- package/dist/router/smart-router.js +524 -0
- package/dist/router/smart-router.js.map +1 -0
- package/dist/router 2/smart-router.d.ts +111 -0
- package/dist/router 2/smart-router.d.ts.map +1 -0
- package/dist/router 2/smart-router.js +383 -0
- package/dist/router 2/smart-router.js.map +1 -0
- package/dist/security/rate-limit-middleware.d.ts +119 -0
- package/dist/security/rate-limit-middleware.d.ts.map +1 -0
- package/dist/security/rate-limit-middleware.js +193 -0
- package/dist/security/rate-limit-middleware.js.map +1 -0
- package/dist/security 2/rate-limit-middleware.d.ts +119 -0
- package/dist/security 2/rate-limit-middleware.d.ts.map +1 -0
- package/dist/security 2/rate-limit-middleware.js +193 -0
- package/dist/security 2/rate-limit-middleware.js.map +1 -0
- package/dist/server 2.js +861 -0
- package/dist/server-wrapper 2.js +170 -0
- package/dist/server-wrapper.d 2.ts +12 -0
- package/dist/server-wrapper.d.ts +12 -0
- package/dist/server-wrapper.d.ts 2.map +1 -0
- package/dist/server-wrapper.d.ts.map +1 -0
- package/dist/server-wrapper.js +170 -0
- package/dist/server-wrapper.js 2.map +1 -0
- package/dist/server-wrapper.js.map +1 -0
- package/dist/server.d 2.ts +14 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts 2.map +1 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +656 -0
- package/dist/server.js 2.map +1 -0
- package/dist/server.js.map +1 -0
- package/dist/synthesis/conflict-detector.d.ts +43 -0
- package/dist/synthesis/conflict-detector.d.ts.map +1 -0
- package/dist/synthesis/conflict-detector.js +125 -0
- package/dist/synthesis/conflict-detector.js.map +1 -0
- package/dist/synthesis 2/conflict-detector.d.ts +43 -0
- package/dist/synthesis 2/conflict-detector.d.ts.map +1 -0
- package/dist/synthesis 2/conflict-detector.js +125 -0
- package/dist/synthesis 2/conflict-detector.js.map +1 -0
- package/dist/utils/monitoring.d.ts +25 -0
- package/dist/utils/monitoring.d.ts.map +1 -0
- package/dist/utils/monitoring.js +41 -0
- package/dist/utils/monitoring.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +97 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +166 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/validation.d.ts +139 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +195 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils 2/monitoring.d.ts +25 -0
- package/dist/utils 2/monitoring.d.ts.map +1 -0
- package/dist/utils 2/monitoring.js +41 -0
- package/dist/utils 2/monitoring.js.map +1 -0
- package/dist/utils 2/rate-limiter.d.ts +97 -0
- package/dist/utils 2/rate-limiter.d.ts.map +1 -0
- package/dist/utils 2/rate-limiter.js +166 -0
- package/dist/utils 2/rate-limiter.js.map +1 -0
- package/dist/utils 2/validation.d.ts +139 -0
- package/dist/utils 2/validation.d.ts.map +1 -0
- package/dist/utils 2/validation.js +195 -0
- package/dist/utils 2/validation.js.map +1 -0
- package/docker-compose.yml +65 -0
- package/embedding-service/Dockerfile +29 -0
- package/embedding-service/README.md +68 -0
- package/embedding-service/requirements.txt +6 -0
- package/embedding-service/server.py +110 -0
- package/package.json +92 -0
- package/scripts/FRESH_INSTALL.md +280 -0
- package/scripts/README.md +101 -0
- package/scripts/ensure-infrastructure.sh +38 -0
- package/scripts/fresh-install-auto.sh +96 -0
- package/scripts/fresh-install.sh +167 -0
- package/scripts/health-check.sh +72 -0
- package/scripts/init-qdrant.ts +201 -0
- package/scripts/postinstall.js +94 -0
- package/scripts/run-accuracy-benchmark.ts +1237 -0
- package/scripts/run-real-benchmark.ts +581 -0
- package/scripts/test-mcp-server.sh +38 -0
|
@@ -0,0 +1,1237 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Accuracy-Focused Vesper Benchmark
|
|
4
|
+
*
|
|
5
|
+
* Measures the REAL VALUE of memory: answer quality, not just latency.
|
|
6
|
+
*
|
|
7
|
+
* This benchmark answers the question:
|
|
8
|
+
* "Does having memory make responses more accurate and helpful?"
|
|
9
|
+
*
|
|
10
|
+
* Methodology:
|
|
11
|
+
* - Enabled: Store facts, query them, measure if responses contain those facts
|
|
12
|
+
* - Disabled: Query without stored facts, measure baseline accuracy
|
|
13
|
+
* - Compare: Precision, Recall, F1 Score, Answer Quality
|
|
14
|
+
*
|
|
15
|
+
* Test Scenarios:
|
|
16
|
+
* 1. Factual Recall - Can it remember specific facts?
|
|
17
|
+
* 2. Preference Memory - Can it remember user preferences?
|
|
18
|
+
* 3. Temporal Context - Can it remember dated information?
|
|
19
|
+
* 4. Multi-hop Reasoning - Can it chain facts together?
|
|
20
|
+
* 5. Contradiction Detection - Can it flag conflicting information?
|
|
21
|
+
*
|
|
22
|
+
* Usage: npm run benchmark:accuracy
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { WorkingMemoryLayer, WorkingMemory } from "../src/memory-layers/working-memory.js";
|
|
26
|
+
import { SemanticMemoryLayer } from "../src/memory-layers/semantic-memory.js";
|
|
27
|
+
import { HybridSearchEngine } from "../src/retrieval/hybrid-search.js";
|
|
28
|
+
import { EmbeddingClient } from "../src/embeddings/client.js";
|
|
29
|
+
import Redis from "ioredis";
|
|
30
|
+
import Database from "better-sqlite3";
|
|
31
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
32
|
+
import { join } from "path";
|
|
33
|
+
import { randomUUID } from "crypto";
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Types
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
interface TestCase {
|
|
40
|
+
id: string;
|
|
41
|
+
category: "factual" | "preference" | "temporal" | "multi-hop" | "contradiction";
|
|
42
|
+
facts: string[];
|
|
43
|
+
query: string;
|
|
44
|
+
expectedKeywords: string[];
|
|
45
|
+
expectedAnswer?: string;
|
|
46
|
+
isContradiction?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface TestResult {
|
|
50
|
+
testCase: TestCase;
|
|
51
|
+
enabled: {
|
|
52
|
+
response: string;
|
|
53
|
+
keywordsFound: string[];
|
|
54
|
+
keywordsMissed: string[];
|
|
55
|
+
accuracy: number;
|
|
56
|
+
memoryHit: boolean;
|
|
57
|
+
latencyMs: number;
|
|
58
|
+
};
|
|
59
|
+
disabled: {
|
|
60
|
+
response: string;
|
|
61
|
+
keywordsFound: string[];
|
|
62
|
+
keywordsMissed: string[];
|
|
63
|
+
accuracy: number;
|
|
64
|
+
latencyMs: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface ScenarioResult {
|
|
69
|
+
category: string;
|
|
70
|
+
testCount: number;
|
|
71
|
+
enabledMetrics: {
|
|
72
|
+
precision: number;
|
|
73
|
+
recall: number;
|
|
74
|
+
f1Score: number;
|
|
75
|
+
avgAccuracy: number;
|
|
76
|
+
memoryHitRate: number;
|
|
77
|
+
avgLatencyMs: number;
|
|
78
|
+
};
|
|
79
|
+
disabledMetrics: {
|
|
80
|
+
precision: number;
|
|
81
|
+
recall: number;
|
|
82
|
+
f1Score: number;
|
|
83
|
+
avgAccuracy: number;
|
|
84
|
+
avgLatencyMs: number;
|
|
85
|
+
};
|
|
86
|
+
improvementDelta: {
|
|
87
|
+
precision: number;
|
|
88
|
+
recall: number;
|
|
89
|
+
f1Score: number;
|
|
90
|
+
accuracy: number;
|
|
91
|
+
};
|
|
92
|
+
statisticalSignificance: {
|
|
93
|
+
pValue: number;
|
|
94
|
+
significant: boolean;
|
|
95
|
+
effectSize: number;
|
|
96
|
+
interpretation: string;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface AccuracyBenchmarkResult {
|
|
101
|
+
id: string;
|
|
102
|
+
timestamp: string;
|
|
103
|
+
duration: number;
|
|
104
|
+
scenarios: ScenarioResult[];
|
|
105
|
+
overall: {
|
|
106
|
+
enabledF1: number;
|
|
107
|
+
disabledF1: number;
|
|
108
|
+
improvementPercent: number;
|
|
109
|
+
statisticallySignificant: boolean;
|
|
110
|
+
conclusion: string;
|
|
111
|
+
};
|
|
112
|
+
testResults: TestResult[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// =============================================================================
|
|
116
|
+
// Test Data
|
|
117
|
+
// =============================================================================
|
|
118
|
+
|
|
119
|
+
const TEST_CASES: TestCase[] = [
|
|
120
|
+
// Factual Recall (5 tests)
|
|
121
|
+
{
|
|
122
|
+
id: "factual-1",
|
|
123
|
+
category: "factual",
|
|
124
|
+
facts: ["The user's name is David and they are based in San Francisco"],
|
|
125
|
+
query: "What is my name?",
|
|
126
|
+
expectedKeywords: ["David"],
|
|
127
|
+
expectedAnswer: "David",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "factual-2",
|
|
131
|
+
category: "factual",
|
|
132
|
+
facts: ["The project is called Vesper, which is a memory system for AI agents"],
|
|
133
|
+
query: "What project am I working on?",
|
|
134
|
+
expectedKeywords: ["Vesper", "memory"],
|
|
135
|
+
expectedAnswer: "Vesper",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "factual-3",
|
|
139
|
+
category: "factual",
|
|
140
|
+
facts: ["The database uses SQLite for the knowledge graph and Redis for caching"],
|
|
141
|
+
query: "What database technologies are we using?",
|
|
142
|
+
expectedKeywords: ["SQLite", "Redis"],
|
|
143
|
+
expectedAnswer: "SQLite and Redis",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "factual-4",
|
|
147
|
+
category: "factual",
|
|
148
|
+
facts: ["The embedding model is BGE-large-en-v1.5 which produces 1024-dimensional vectors"],
|
|
149
|
+
query: "What embedding model do we use?",
|
|
150
|
+
expectedKeywords: ["BGE", "1024"],
|
|
151
|
+
expectedAnswer: "BGE-large-en-v1.5",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "factual-5",
|
|
155
|
+
category: "factual",
|
|
156
|
+
facts: ["The target latency is under 200 milliseconds at P95"],
|
|
157
|
+
query: "What is the target latency?",
|
|
158
|
+
expectedKeywords: ["200", "milliseconds", "P95"],
|
|
159
|
+
expectedAnswer: "200ms P95",
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// Preference Memory (5 tests)
|
|
163
|
+
{
|
|
164
|
+
id: "preference-1",
|
|
165
|
+
category: "preference",
|
|
166
|
+
facts: ["I prefer TypeScript over JavaScript because of type safety"],
|
|
167
|
+
query: "What programming language do I prefer?",
|
|
168
|
+
expectedKeywords: ["TypeScript", "type", "safety"],
|
|
169
|
+
expectedAnswer: "TypeScript",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "preference-2",
|
|
173
|
+
category: "preference",
|
|
174
|
+
facts: ["I prefer functional programming style over object-oriented programming"],
|
|
175
|
+
query: "What programming paradigm do I favor?",
|
|
176
|
+
expectedKeywords: ["functional"],
|
|
177
|
+
expectedAnswer: "functional programming",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "preference-3",
|
|
181
|
+
category: "preference",
|
|
182
|
+
facts: ["I want test coverage to be at least 80 percent for all code"],
|
|
183
|
+
query: "What are my testing requirements?",
|
|
184
|
+
expectedKeywords: ["80", "percent", "coverage"],
|
|
185
|
+
expectedAnswer: "80% coverage",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "preference-4",
|
|
189
|
+
category: "preference",
|
|
190
|
+
facts: ["I prefer dark mode for all my development tools and editors"],
|
|
191
|
+
query: "What color theme do I prefer?",
|
|
192
|
+
expectedKeywords: ["dark", "mode"],
|
|
193
|
+
expectedAnswer: "dark mode",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "preference-5",
|
|
197
|
+
category: "preference",
|
|
198
|
+
facts: ["I prefer pragmatic solutions over theoretically perfect ones"],
|
|
199
|
+
query: "What is my approach to problem solving?",
|
|
200
|
+
expectedKeywords: ["pragmatic"],
|
|
201
|
+
expectedAnswer: "pragmatic solutions",
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Temporal Context (5 tests)
|
|
205
|
+
{
|
|
206
|
+
id: "temporal-1",
|
|
207
|
+
category: "temporal",
|
|
208
|
+
facts: ["Last week we decided to use Qdrant for vector storage"],
|
|
209
|
+
query: "What did we decide about vector storage recently?",
|
|
210
|
+
expectedKeywords: ["Qdrant", "vector"],
|
|
211
|
+
expectedAnswer: "Qdrant",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "temporal-2",
|
|
215
|
+
category: "temporal",
|
|
216
|
+
facts: ["Yesterday we fixed a critical bug in the authentication system"],
|
|
217
|
+
query: "What bug did we fix recently?",
|
|
218
|
+
expectedKeywords: ["authentication", "bug"],
|
|
219
|
+
expectedAnswer: "authentication bug",
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "temporal-3",
|
|
223
|
+
category: "temporal",
|
|
224
|
+
facts: ["This morning we deployed version 2.0 to production"],
|
|
225
|
+
query: "What did we deploy today?",
|
|
226
|
+
expectedKeywords: ["version", "2.0", "production"],
|
|
227
|
+
expectedAnswer: "version 2.0",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "temporal-4",
|
|
231
|
+
category: "temporal",
|
|
232
|
+
facts: ["Last month we migrated from PostgreSQL to SQLite for simplicity"],
|
|
233
|
+
query: "What database migration did we do?",
|
|
234
|
+
expectedKeywords: ["PostgreSQL", "SQLite", "migrated"],
|
|
235
|
+
expectedAnswer: "PostgreSQL to SQLite",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "temporal-5",
|
|
239
|
+
category: "temporal",
|
|
240
|
+
facts: ["Earlier this week we added support for batch embedding requests"],
|
|
241
|
+
query: "What embedding feature did we add recently?",
|
|
242
|
+
expectedKeywords: ["batch", "embedding"],
|
|
243
|
+
expectedAnswer: "batch embedding",
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// Multi-hop Reasoning (5 tests)
|
|
247
|
+
{
|
|
248
|
+
id: "multihop-1",
|
|
249
|
+
category: "multi-hop",
|
|
250
|
+
facts: [
|
|
251
|
+
"The Vesper project uses MCP protocol",
|
|
252
|
+
"MCP stands for Model Context Protocol",
|
|
253
|
+
],
|
|
254
|
+
query: "What protocol standard does Vesper follow?",
|
|
255
|
+
expectedKeywords: ["MCP", "Model", "Context", "Protocol"],
|
|
256
|
+
expectedAnswer: "Model Context Protocol (MCP)",
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "multihop-2",
|
|
260
|
+
category: "multi-hop",
|
|
261
|
+
facts: [
|
|
262
|
+
"David is the lead developer",
|
|
263
|
+
"The lead developer makes architecture decisions",
|
|
264
|
+
],
|
|
265
|
+
query: "Who makes architecture decisions?",
|
|
266
|
+
expectedKeywords: ["David", "lead", "developer"],
|
|
267
|
+
expectedAnswer: "David",
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: "multihop-3",
|
|
271
|
+
category: "multi-hop",
|
|
272
|
+
facts: [
|
|
273
|
+
"The embedding service runs on port 8000",
|
|
274
|
+
"Port 8000 is exposed via Docker",
|
|
275
|
+
],
|
|
276
|
+
query: "How is the embedding service accessed?",
|
|
277
|
+
expectedKeywords: ["8000", "Docker"],
|
|
278
|
+
expectedAnswer: "port 8000 via Docker",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "multihop-4",
|
|
282
|
+
category: "multi-hop",
|
|
283
|
+
facts: [
|
|
284
|
+
"Working memory uses Redis",
|
|
285
|
+
"Redis provides sub-5ms latency",
|
|
286
|
+
],
|
|
287
|
+
query: "What is the latency of working memory?",
|
|
288
|
+
expectedKeywords: ["5ms", "Redis"],
|
|
289
|
+
expectedAnswer: "sub-5ms via Redis",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: "multihop-5",
|
|
293
|
+
category: "multi-hop",
|
|
294
|
+
facts: [
|
|
295
|
+
"The knowledge graph uses HippoRAG",
|
|
296
|
+
"HippoRAG implements Personalized PageRank",
|
|
297
|
+
],
|
|
298
|
+
query: "What algorithm does the knowledge graph use?",
|
|
299
|
+
expectedKeywords: ["HippoRAG", "PageRank"],
|
|
300
|
+
expectedAnswer: "Personalized PageRank via HippoRAG",
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
// Contradiction Detection (5 tests)
|
|
304
|
+
{
|
|
305
|
+
id: "contradiction-1",
|
|
306
|
+
category: "contradiction",
|
|
307
|
+
facts: [
|
|
308
|
+
"The project uses TypeScript",
|
|
309
|
+
"The project uses Python instead of TypeScript",
|
|
310
|
+
],
|
|
311
|
+
query: "What language does the project use?",
|
|
312
|
+
expectedKeywords: ["TypeScript", "Python", "conflict"],
|
|
313
|
+
isContradiction: true,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "contradiction-2",
|
|
317
|
+
category: "contradiction",
|
|
318
|
+
facts: [
|
|
319
|
+
"The target latency is 200ms",
|
|
320
|
+
"The target latency is 500ms",
|
|
321
|
+
],
|
|
322
|
+
query: "What is the target latency?",
|
|
323
|
+
expectedKeywords: ["200", "500", "conflict"],
|
|
324
|
+
isContradiction: true,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "contradiction-3",
|
|
328
|
+
category: "contradiction",
|
|
329
|
+
facts: [
|
|
330
|
+
"We decided to use MongoDB",
|
|
331
|
+
"We decided to use PostgreSQL instead of MongoDB",
|
|
332
|
+
],
|
|
333
|
+
query: "What database did we choose?",
|
|
334
|
+
expectedKeywords: ["MongoDB", "PostgreSQL", "conflict"],
|
|
335
|
+
isContradiction: true,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: "contradiction-4",
|
|
339
|
+
category: "contradiction",
|
|
340
|
+
facts: [
|
|
341
|
+
"The API uses REST",
|
|
342
|
+
"The API uses GraphQL, not REST",
|
|
343
|
+
],
|
|
344
|
+
query: "What API style do we use?",
|
|
345
|
+
expectedKeywords: ["REST", "GraphQL", "conflict"],
|
|
346
|
+
isContradiction: true,
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: "contradiction-5",
|
|
350
|
+
category: "contradiction",
|
|
351
|
+
facts: [
|
|
352
|
+
"Deploy on Fridays is allowed",
|
|
353
|
+
"Deploy on Fridays is strictly forbidden",
|
|
354
|
+
],
|
|
355
|
+
query: "Can we deploy on Fridays?",
|
|
356
|
+
expectedKeywords: ["Friday", "deploy", "conflict"],
|
|
357
|
+
isContradiction: true,
|
|
358
|
+
},
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
// =============================================================================
|
|
362
|
+
// Memory Client for Benchmarking
|
|
363
|
+
// =============================================================================
|
|
364
|
+
|
|
365
|
+
class AccuracyBenchmarkClient {
|
|
366
|
+
private workingMemory: WorkingMemoryLayer;
|
|
367
|
+
private semanticMemory: SemanticMemoryLayer;
|
|
368
|
+
private hybridSearch: HybridSearchEngine;
|
|
369
|
+
private embeddingClient: EmbeddingClient;
|
|
370
|
+
private redis: Redis;
|
|
371
|
+
private sessionId: string;
|
|
372
|
+
|
|
373
|
+
constructor(
|
|
374
|
+
redis: Redis,
|
|
375
|
+
db: Database.Database,
|
|
376
|
+
embeddingClient: EmbeddingClient,
|
|
377
|
+
hybridSearch: HybridSearchEngine
|
|
378
|
+
) {
|
|
379
|
+
this.redis = redis;
|
|
380
|
+
this.workingMemory = new WorkingMemoryLayer(redis);
|
|
381
|
+
this.semanticMemory = new SemanticMemoryLayer(db);
|
|
382
|
+
this.hybridSearch = hybridSearch;
|
|
383
|
+
this.embeddingClient = embeddingClient;
|
|
384
|
+
this.sessionId = `accuracy-${randomUUID().slice(0, 8)}`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Store facts for a test case
|
|
389
|
+
*/
|
|
390
|
+
async storeFacts(facts: string[], testId: string): Promise<void> {
|
|
391
|
+
for (let i = 0; i < facts.length; i++) {
|
|
392
|
+
const fact = facts[i];
|
|
393
|
+
const memoryId = randomUUID();
|
|
394
|
+
const conversationId = `conv-${this.sessionId}-${testId}-${i}`;
|
|
395
|
+
|
|
396
|
+
// Generate embedding
|
|
397
|
+
const embedding = await this.embeddingClient.embed(fact);
|
|
398
|
+
|
|
399
|
+
// Store in working memory
|
|
400
|
+
const workingMemoryEntry: WorkingMemory = {
|
|
401
|
+
conversationId,
|
|
402
|
+
timestamp: new Date(),
|
|
403
|
+
fullText: fact,
|
|
404
|
+
embedding,
|
|
405
|
+
keyEntities: this.extractEntities(fact),
|
|
406
|
+
topics: this.extractTopics(fact),
|
|
407
|
+
userIntent: "factual",
|
|
408
|
+
};
|
|
409
|
+
await this.workingMemory.store(workingMemoryEntry);
|
|
410
|
+
|
|
411
|
+
// Store in semantic memory
|
|
412
|
+
const entities = this.extractEntities(fact);
|
|
413
|
+
for (const entityName of entities) {
|
|
414
|
+
this.semanticMemory.upsertEntity({
|
|
415
|
+
name: entityName,
|
|
416
|
+
type: "concept",
|
|
417
|
+
description: fact,
|
|
418
|
+
confidence: 0.9,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Index in Qdrant
|
|
423
|
+
await this.hybridSearch.upsertMemory(memoryId, embedding, {
|
|
424
|
+
content: fact,
|
|
425
|
+
testId,
|
|
426
|
+
factIndex: i,
|
|
427
|
+
sessionId: this.sessionId,
|
|
428
|
+
timestamp: Date.now(),
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Query memory and return response
|
|
435
|
+
*/
|
|
436
|
+
async query(queryText: string): Promise<{
|
|
437
|
+
response: string;
|
|
438
|
+
memoryHit: boolean;
|
|
439
|
+
latencyMs: number;
|
|
440
|
+
sources: string[];
|
|
441
|
+
}> {
|
|
442
|
+
const start = performance.now();
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
// Check working memory first
|
|
446
|
+
const workingResults = await this.workingMemory.search(queryText, 3);
|
|
447
|
+
|
|
448
|
+
if (workingResults.length > 0 && workingResults[0].similarity > 0.5) {
|
|
449
|
+
const latencyMs = performance.now() - start;
|
|
450
|
+
return {
|
|
451
|
+
response: workingResults.map((r) => r.memory.fullText).join(" | "),
|
|
452
|
+
memoryHit: true,
|
|
453
|
+
latencyMs,
|
|
454
|
+
sources: ["working-memory"],
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Fall back to hybrid search
|
|
459
|
+
const queryEmbedding = await this.embeddingClient.embed(queryText);
|
|
460
|
+
const searchResults = await this.hybridSearch.hybridSearch(queryEmbedding, 3);
|
|
461
|
+
|
|
462
|
+
const latencyMs = performance.now() - start;
|
|
463
|
+
|
|
464
|
+
if (searchResults.length > 0) {
|
|
465
|
+
const contents = searchResults
|
|
466
|
+
.map((r) => (r.payload?.content as string) || "")
|
|
467
|
+
.filter((c) => c.length > 0);
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
response: contents.join(" | "),
|
|
471
|
+
memoryHit: true,
|
|
472
|
+
latencyMs,
|
|
473
|
+
sources: ["semantic-search"],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
response: "No relevant information found in memory.",
|
|
479
|
+
memoryHit: false,
|
|
480
|
+
latencyMs,
|
|
481
|
+
sources: [],
|
|
482
|
+
};
|
|
483
|
+
} catch (error) {
|
|
484
|
+
const latencyMs = performance.now() - start;
|
|
485
|
+
console.error("Query error:", error);
|
|
486
|
+
return {
|
|
487
|
+
response: "Error retrieving information.",
|
|
488
|
+
memoryHit: false,
|
|
489
|
+
latencyMs,
|
|
490
|
+
sources: [],
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Clear all memories for this session
|
|
497
|
+
*/
|
|
498
|
+
async clearSession(): Promise<void> {
|
|
499
|
+
try {
|
|
500
|
+
const keys = await this.redis.keys("working:*");
|
|
501
|
+
if (keys.length > 0) {
|
|
502
|
+
await this.redis.del(...keys);
|
|
503
|
+
}
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error("Clear session error:", error);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Generate new session ID
|
|
511
|
+
*/
|
|
512
|
+
newSession(): void {
|
|
513
|
+
this.sessionId = `accuracy-${randomUUID().slice(0, 8)}`;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private extractEntities(content: string): string[] {
|
|
517
|
+
const entities: string[] = [];
|
|
518
|
+
const lowerContent = content.toLowerCase();
|
|
519
|
+
|
|
520
|
+
const techTerms = [
|
|
521
|
+
"typescript", "javascript", "python", "rust", "redis", "qdrant",
|
|
522
|
+
"sqlite", "postgresql", "mongodb", "docker", "kubernetes", "vesper",
|
|
523
|
+
"claude", "mcp", "api", "rest", "graphql", "bge", "hipporag",
|
|
524
|
+
];
|
|
525
|
+
|
|
526
|
+
for (const term of techTerms) {
|
|
527
|
+
if (lowerContent.includes(term)) {
|
|
528
|
+
entities.push(term);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return [...new Set(entities)].slice(0, 5);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private extractTopics(content: string): string[] {
|
|
536
|
+
const topics: string[] = [];
|
|
537
|
+
const lowerContent = content.toLowerCase();
|
|
538
|
+
|
|
539
|
+
const topicPatterns = [
|
|
540
|
+
{ pattern: /prefer|like|want|favor/i, topic: "preferences" },
|
|
541
|
+
{ pattern: /decided|chose|selected/i, topic: "decisions" },
|
|
542
|
+
{ pattern: /yesterday|today|last week|recently/i, topic: "temporal" },
|
|
543
|
+
{ pattern: /bug|fix|error|issue/i, topic: "debugging" },
|
|
544
|
+
{ pattern: /deploy|production|release/i, topic: "deployment" },
|
|
545
|
+
];
|
|
546
|
+
|
|
547
|
+
for (const { pattern, topic } of topicPatterns) {
|
|
548
|
+
if (pattern.test(lowerContent)) {
|
|
549
|
+
topics.push(topic);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return topics;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// =============================================================================
|
|
558
|
+
// Evaluation Functions
|
|
559
|
+
// =============================================================================
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Evaluate response accuracy based on expected keywords
|
|
563
|
+
*/
|
|
564
|
+
function evaluateAccuracy(
|
|
565
|
+
response: string,
|
|
566
|
+
expectedKeywords: string[]
|
|
567
|
+
): { found: string[]; missed: string[]; accuracy: number } {
|
|
568
|
+
const responseLower = response.toLowerCase();
|
|
569
|
+
const found: string[] = [];
|
|
570
|
+
const missed: string[] = [];
|
|
571
|
+
|
|
572
|
+
for (const keyword of expectedKeywords) {
|
|
573
|
+
// Skip "conflict" keyword for contradiction detection
|
|
574
|
+
if (keyword.toLowerCase() === "conflict") {
|
|
575
|
+
// Check if response indicates any uncertainty or conflict
|
|
576
|
+
if (
|
|
577
|
+
responseLower.includes("conflict") ||
|
|
578
|
+
responseLower.includes("contradict") ||
|
|
579
|
+
responseLower.includes("inconsistent") ||
|
|
580
|
+
responseLower.includes("|") // Multiple results indicate ambiguity
|
|
581
|
+
) {
|
|
582
|
+
found.push(keyword);
|
|
583
|
+
} else {
|
|
584
|
+
missed.push(keyword);
|
|
585
|
+
}
|
|
586
|
+
} else if (responseLower.includes(keyword.toLowerCase())) {
|
|
587
|
+
found.push(keyword);
|
|
588
|
+
} else {
|
|
589
|
+
missed.push(keyword);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const accuracy = expectedKeywords.length > 0
|
|
594
|
+
? found.length / expectedKeywords.length
|
|
595
|
+
: 0;
|
|
596
|
+
|
|
597
|
+
return { found, missed, accuracy };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Calculate precision, recall, and F1 score
|
|
602
|
+
*/
|
|
603
|
+
function calculateMetrics(results: TestResult[], enabled: boolean): {
|
|
604
|
+
precision: number;
|
|
605
|
+
recall: number;
|
|
606
|
+
f1Score: number;
|
|
607
|
+
avgAccuracy: number;
|
|
608
|
+
memoryHitRate?: number;
|
|
609
|
+
avgLatencyMs: number;
|
|
610
|
+
} {
|
|
611
|
+
let totalKeywordsFound = 0;
|
|
612
|
+
let totalKeywordsExpected = 0;
|
|
613
|
+
let totalAccuracy = 0;
|
|
614
|
+
let totalLatency = 0;
|
|
615
|
+
let memoryHits = 0;
|
|
616
|
+
|
|
617
|
+
for (const result of results) {
|
|
618
|
+
const data = enabled ? result.enabled : result.disabled;
|
|
619
|
+
totalKeywordsFound += data.keywordsFound.length;
|
|
620
|
+
totalKeywordsExpected += result.testCase.expectedKeywords.length;
|
|
621
|
+
totalAccuracy += data.accuracy;
|
|
622
|
+
totalLatency += data.latencyMs;
|
|
623
|
+
if (enabled && data.memoryHit) {
|
|
624
|
+
memoryHits++;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Precision: of all keywords found, how many were expected?
|
|
629
|
+
// (In our case, we only count expected keywords, so precision = recall)
|
|
630
|
+
const precision = totalKeywordsExpected > 0
|
|
631
|
+
? totalKeywordsFound / totalKeywordsExpected
|
|
632
|
+
: 0;
|
|
633
|
+
|
|
634
|
+
// Recall: of all expected keywords, how many were found?
|
|
635
|
+
const recall = totalKeywordsExpected > 0
|
|
636
|
+
? totalKeywordsFound / totalKeywordsExpected
|
|
637
|
+
: 0;
|
|
638
|
+
|
|
639
|
+
// F1 Score: harmonic mean of precision and recall
|
|
640
|
+
const f1Score = precision + recall > 0
|
|
641
|
+
? (2 * precision * recall) / (precision + recall)
|
|
642
|
+
: 0;
|
|
643
|
+
|
|
644
|
+
const avgAccuracy = results.length > 0
|
|
645
|
+
? totalAccuracy / results.length
|
|
646
|
+
: 0;
|
|
647
|
+
|
|
648
|
+
const avgLatencyMs = results.length > 0
|
|
649
|
+
? totalLatency / results.length
|
|
650
|
+
: 0;
|
|
651
|
+
|
|
652
|
+
const result: ReturnType<typeof calculateMetrics> = {
|
|
653
|
+
precision,
|
|
654
|
+
recall,
|
|
655
|
+
f1Score,
|
|
656
|
+
avgAccuracy,
|
|
657
|
+
avgLatencyMs,
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
if (enabled) {
|
|
661
|
+
result.memoryHitRate = results.length > 0
|
|
662
|
+
? memoryHits / results.length
|
|
663
|
+
: 0;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return result;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Welch's t-test for comparing accuracy between conditions
|
|
671
|
+
*/
|
|
672
|
+
function welchTTest(
|
|
673
|
+
group1: number[],
|
|
674
|
+
group2: number[],
|
|
675
|
+
alpha: number = 0.05
|
|
676
|
+
): { tStatistic: number; pValue: number; significant: boolean } {
|
|
677
|
+
const n1 = group1.length;
|
|
678
|
+
const n2 = group2.length;
|
|
679
|
+
|
|
680
|
+
if (n1 < 2 || n2 < 2) {
|
|
681
|
+
return { tStatistic: 0, pValue: 1, significant: false };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const mean1 = group1.reduce((a, b) => a + b, 0) / n1;
|
|
685
|
+
const mean2 = group2.reduce((a, b) => a + b, 0) / n2;
|
|
686
|
+
|
|
687
|
+
const var1 = group1.reduce((sum, x) => sum + (x - mean1) ** 2, 0) / (n1 - 1);
|
|
688
|
+
const var2 = group2.reduce((sum, x) => sum + (x - mean2) ** 2, 0) / (n2 - 1);
|
|
689
|
+
|
|
690
|
+
// Handle zero variance
|
|
691
|
+
if (var1 === 0 && var2 === 0) {
|
|
692
|
+
return {
|
|
693
|
+
tStatistic: mean1 === mean2 ? 0 : Infinity,
|
|
694
|
+
pValue: mean1 === mean2 ? 1 : 0,
|
|
695
|
+
significant: mean1 !== mean2,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const se1 = var1 / n1;
|
|
700
|
+
const se2 = var2 / n2;
|
|
701
|
+
const tStatistic = (mean1 - mean2) / Math.sqrt(se1 + se2);
|
|
702
|
+
|
|
703
|
+
// Welch-Satterthwaite degrees of freedom
|
|
704
|
+
const df = (se1 + se2) ** 2 / (se1 ** 2 / (n1 - 1) + se2 ** 2 / (n2 - 1));
|
|
705
|
+
|
|
706
|
+
// Approximate p-value using normal distribution for large df
|
|
707
|
+
const pValue = 2 * (1 - normalCDF(Math.abs(tStatistic)));
|
|
708
|
+
|
|
709
|
+
return {
|
|
710
|
+
tStatistic,
|
|
711
|
+
pValue,
|
|
712
|
+
significant: pValue < alpha,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Cohen's d effect size
|
|
718
|
+
*/
|
|
719
|
+
function cohensD(group1: number[], group2: number[]): number {
|
|
720
|
+
const n1 = group1.length;
|
|
721
|
+
const n2 = group2.length;
|
|
722
|
+
|
|
723
|
+
if (n1 < 2 || n2 < 2) return 0;
|
|
724
|
+
|
|
725
|
+
const mean1 = group1.reduce((a, b) => a + b, 0) / n1;
|
|
726
|
+
const mean2 = group2.reduce((a, b) => a + b, 0) / n2;
|
|
727
|
+
|
|
728
|
+
const var1 = group1.reduce((sum, x) => sum + (x - mean1) ** 2, 0) / (n1 - 1);
|
|
729
|
+
const var2 = group2.reduce((sum, x) => sum + (x - mean2) ** 2, 0) / (n2 - 1);
|
|
730
|
+
|
|
731
|
+
const pooledVar = ((n1 - 1) * var1 + (n2 - 1) * var2) / (n1 + n2 - 2);
|
|
732
|
+
const pooledSD = Math.sqrt(pooledVar);
|
|
733
|
+
|
|
734
|
+
if (pooledSD === 0) return 0;
|
|
735
|
+
|
|
736
|
+
return (mean1 - mean2) / pooledSD;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function interpretEffectSize(d: number): string {
|
|
740
|
+
const absD = Math.abs(d);
|
|
741
|
+
if (absD < 0.2) return "negligible";
|
|
742
|
+
if (absD < 0.5) return "small";
|
|
743
|
+
if (absD < 0.8) return "medium";
|
|
744
|
+
return "large";
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function normalCDF(x: number): number {
|
|
748
|
+
const a1 = 0.254829592;
|
|
749
|
+
const a2 = -0.284496736;
|
|
750
|
+
const a3 = 1.421413741;
|
|
751
|
+
const a4 = -1.453152027;
|
|
752
|
+
const a5 = 1.061405429;
|
|
753
|
+
const p = 0.3275911;
|
|
754
|
+
|
|
755
|
+
const sign = x < 0 ? -1 : 1;
|
|
756
|
+
x = Math.abs(x) / Math.sqrt(2);
|
|
757
|
+
|
|
758
|
+
const t = 1 / (1 + p * x);
|
|
759
|
+
const y = 1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
|
|
760
|
+
|
|
761
|
+
return 0.5 * (1 + sign * y);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// =============================================================================
|
|
765
|
+
// Report Generator
|
|
766
|
+
// =============================================================================
|
|
767
|
+
|
|
768
|
+
function generateAccuracyReport(result: AccuracyBenchmarkResult): string {
|
|
769
|
+
const lines: string[] = [];
|
|
770
|
+
|
|
771
|
+
lines.push("# Vesper Accuracy Benchmark Report");
|
|
772
|
+
lines.push("");
|
|
773
|
+
lines.push(`**Generated:** ${result.timestamp}`);
|
|
774
|
+
lines.push(`**Benchmark ID:** ${result.id}`);
|
|
775
|
+
lines.push(`**Duration:** ${(result.duration / 1000).toFixed(1)}s`);
|
|
776
|
+
lines.push("");
|
|
777
|
+
|
|
778
|
+
// Executive Summary
|
|
779
|
+
lines.push("## Executive Summary");
|
|
780
|
+
lines.push("");
|
|
781
|
+
lines.push("This benchmark measures the **real value** of memory: answer quality and accuracy,");
|
|
782
|
+
lines.push("not just latency. It answers: *Does having memory make responses more accurate?*");
|
|
783
|
+
lines.push("");
|
|
784
|
+
|
|
785
|
+
const verdict = result.overall.improvementPercent > 50
|
|
786
|
+
? "**STRONG IMPROVEMENT**"
|
|
787
|
+
: result.overall.improvementPercent > 20
|
|
788
|
+
? "**MODERATE IMPROVEMENT**"
|
|
789
|
+
: result.overall.improvementPercent > 0
|
|
790
|
+
? "**MARGINAL IMPROVEMENT**"
|
|
791
|
+
: "**NO IMPROVEMENT**";
|
|
792
|
+
|
|
793
|
+
lines.push(`### Verdict: ${verdict}`);
|
|
794
|
+
lines.push("");
|
|
795
|
+
lines.push(`- **Enabled F1 Score:** ${(result.overall.enabledF1 * 100).toFixed(1)}%`);
|
|
796
|
+
lines.push(`- **Disabled F1 Score:** ${(result.overall.disabledF1 * 100).toFixed(1)}%`);
|
|
797
|
+
lines.push(`- **Improvement:** +${result.overall.improvementPercent.toFixed(1)}%`);
|
|
798
|
+
lines.push(`- **Statistically Significant:** ${result.overall.statisticallySignificant ? "Yes (p < 0.05)" : "No"}`);
|
|
799
|
+
lines.push("");
|
|
800
|
+
lines.push(`> ${result.overall.conclusion}`);
|
|
801
|
+
lines.push("");
|
|
802
|
+
|
|
803
|
+
// Overall Results Table
|
|
804
|
+
lines.push("## Overall Results");
|
|
805
|
+
lines.push("");
|
|
806
|
+
lines.push("| Metric | Vesper Enabled | Vesper Disabled | Improvement |");
|
|
807
|
+
lines.push("| --- | --- | --- | --- |");
|
|
808
|
+
|
|
809
|
+
const enabledAvg = result.scenarios.reduce((sum, s) => sum + s.enabledMetrics.avgAccuracy, 0) / result.scenarios.length;
|
|
810
|
+
const disabledAvg = result.scenarios.reduce((sum, s) => sum + s.disabledMetrics.avgAccuracy, 0) / result.scenarios.length;
|
|
811
|
+
const enabledF1Avg = result.scenarios.reduce((sum, s) => sum + s.enabledMetrics.f1Score, 0) / result.scenarios.length;
|
|
812
|
+
const disabledF1Avg = result.scenarios.reduce((sum, s) => sum + s.disabledMetrics.f1Score, 0) / result.scenarios.length;
|
|
813
|
+
const enabledHitRate = result.scenarios.reduce((sum, s) => sum + (s.enabledMetrics.memoryHitRate || 0), 0) / result.scenarios.length;
|
|
814
|
+
|
|
815
|
+
lines.push(`| Average Accuracy | ${(enabledAvg * 100).toFixed(1)}% | ${(disabledAvg * 100).toFixed(1)}% | +${((enabledAvg - disabledAvg) * 100).toFixed(1)}% |`);
|
|
816
|
+
lines.push(`| F1 Score | ${(enabledF1Avg * 100).toFixed(1)}% | ${(disabledF1Avg * 100).toFixed(1)}% | +${((enabledF1Avg - disabledF1Avg) * 100).toFixed(1)}% |`);
|
|
817
|
+
lines.push(`| Memory Hit Rate | ${(enabledHitRate * 100).toFixed(1)}% | N/A | - |`);
|
|
818
|
+
lines.push("");
|
|
819
|
+
|
|
820
|
+
// Scenario Results
|
|
821
|
+
lines.push("## Results by Scenario");
|
|
822
|
+
lines.push("");
|
|
823
|
+
|
|
824
|
+
for (const scenario of result.scenarios) {
|
|
825
|
+
const categoryName = scenario.category.charAt(0).toUpperCase() + scenario.category.slice(1).replace("-", " ");
|
|
826
|
+
lines.push(`### ${categoryName}`);
|
|
827
|
+
lines.push("");
|
|
828
|
+
|
|
829
|
+
// Winner determination
|
|
830
|
+
const winner = scenario.enabledMetrics.f1Score > scenario.disabledMetrics.f1Score
|
|
831
|
+
? "**Winner: Vesper Enabled**"
|
|
832
|
+
: scenario.enabledMetrics.f1Score < scenario.disabledMetrics.f1Score
|
|
833
|
+
? "**Winner: Vesper Disabled**"
|
|
834
|
+
: "**Result: Tie**";
|
|
835
|
+
lines.push(winner);
|
|
836
|
+
lines.push("");
|
|
837
|
+
|
|
838
|
+
lines.push("| Metric | Enabled | Disabled | Delta |");
|
|
839
|
+
lines.push("| --- | --- | --- | --- |");
|
|
840
|
+
lines.push(`| Accuracy | ${(scenario.enabledMetrics.avgAccuracy * 100).toFixed(1)}% | ${(scenario.disabledMetrics.avgAccuracy * 100).toFixed(1)}% | +${(scenario.improvementDelta.accuracy * 100).toFixed(1)}% |`);
|
|
841
|
+
lines.push(`| Precision | ${(scenario.enabledMetrics.precision * 100).toFixed(1)}% | ${(scenario.disabledMetrics.precision * 100).toFixed(1)}% | +${(scenario.improvementDelta.precision * 100).toFixed(1)}% |`);
|
|
842
|
+
lines.push(`| Recall | ${(scenario.enabledMetrics.recall * 100).toFixed(1)}% | ${(scenario.disabledMetrics.recall * 100).toFixed(1)}% | +${(scenario.improvementDelta.recall * 100).toFixed(1)}% |`);
|
|
843
|
+
lines.push(`| F1 Score | ${(scenario.enabledMetrics.f1Score * 100).toFixed(1)}% | ${(scenario.disabledMetrics.f1Score * 100).toFixed(1)}% | +${(scenario.improvementDelta.f1Score * 100).toFixed(1)}% |`);
|
|
844
|
+
lines.push(`| Memory Hit Rate | ${((scenario.enabledMetrics.memoryHitRate || 0) * 100).toFixed(1)}% | N/A | - |`);
|
|
845
|
+
lines.push(`| Avg Latency | ${scenario.enabledMetrics.avgLatencyMs.toFixed(1)}ms | ${scenario.disabledMetrics.avgLatencyMs.toFixed(1)}ms | - |`);
|
|
846
|
+
lines.push("");
|
|
847
|
+
|
|
848
|
+
lines.push("**Statistical Analysis:**");
|
|
849
|
+
lines.push(`- p-value: ${scenario.statisticalSignificance.pValue.toFixed(4)}`);
|
|
850
|
+
lines.push(`- Significant (p < 0.05): ${scenario.statisticalSignificance.significant ? "Yes" : "No"}`);
|
|
851
|
+
lines.push(`- Effect Size (Cohen's d): ${scenario.statisticalSignificance.effectSize.toFixed(3)} (${scenario.statisticalSignificance.interpretation})`);
|
|
852
|
+
lines.push("");
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Individual Test Results
|
|
856
|
+
lines.push("## Individual Test Results");
|
|
857
|
+
lines.push("");
|
|
858
|
+
lines.push("### Sample Test Cases");
|
|
859
|
+
lines.push("");
|
|
860
|
+
|
|
861
|
+
// Show first 3 tests from each category
|
|
862
|
+
const categories = [...new Set(result.testResults.map((r) => r.testCase.category))];
|
|
863
|
+
for (const category of categories) {
|
|
864
|
+
const categoryResults = result.testResults.filter((r) => r.testCase.category === category).slice(0, 2);
|
|
865
|
+
|
|
866
|
+
for (const test of categoryResults) {
|
|
867
|
+
lines.push(`#### ${test.testCase.id}`);
|
|
868
|
+
lines.push("");
|
|
869
|
+
lines.push(`**Facts Stored:** ${test.testCase.facts.join(" | ")}`);
|
|
870
|
+
lines.push("");
|
|
871
|
+
lines.push(`**Query:** "${test.testCase.query}"`);
|
|
872
|
+
lines.push("");
|
|
873
|
+
lines.push(`**Expected Keywords:** ${test.testCase.expectedKeywords.join(", ")}`);
|
|
874
|
+
lines.push("");
|
|
875
|
+
lines.push("| Mode | Response | Keywords Found | Accuracy |");
|
|
876
|
+
lines.push("| --- | --- | --- | --- |");
|
|
877
|
+
|
|
878
|
+
const enabledResponse = test.enabled.response.length > 60
|
|
879
|
+
? test.enabled.response.slice(0, 60) + "..."
|
|
880
|
+
: test.enabled.response;
|
|
881
|
+
const disabledResponse = test.disabled.response.length > 60
|
|
882
|
+
? test.disabled.response.slice(0, 60) + "..."
|
|
883
|
+
: test.disabled.response;
|
|
884
|
+
|
|
885
|
+
lines.push(`| Enabled | ${enabledResponse} | ${test.enabled.keywordsFound.join(", ") || "none"} | ${(test.enabled.accuracy * 100).toFixed(0)}% |`);
|
|
886
|
+
lines.push(`| Disabled | ${disabledResponse} | ${test.disabled.keywordsFound.join(", ") || "none"} | ${(test.disabled.accuracy * 100).toFixed(0)}% |`);
|
|
887
|
+
lines.push("");
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Methodology
|
|
892
|
+
lines.push("## Methodology");
|
|
893
|
+
lines.push("");
|
|
894
|
+
lines.push("### Test Design");
|
|
895
|
+
lines.push("");
|
|
896
|
+
lines.push("1. **Enabled Mode**: Store facts in memory, then query. Measure if response contains expected keywords.");
|
|
897
|
+
lines.push("2. **Disabled Mode**: Query without storing facts. Measure baseline (should find nothing).");
|
|
898
|
+
lines.push("3. **Metrics**: Precision, Recall, F1 Score, Memory Hit Rate");
|
|
899
|
+
lines.push("4. **Statistical Tests**: Welch's t-test (p < 0.05), Cohen's d effect size");
|
|
900
|
+
lines.push("");
|
|
901
|
+
lines.push("### Test Categories");
|
|
902
|
+
lines.push("");
|
|
903
|
+
lines.push("- **Factual Recall** (5 tests): Can it remember specific facts?");
|
|
904
|
+
lines.push("- **Preference Memory** (5 tests): Can it remember user preferences?");
|
|
905
|
+
lines.push("- **Temporal Context** (5 tests): Can it remember dated information?");
|
|
906
|
+
lines.push("- **Multi-hop Reasoning** (5 tests): Can it chain facts together?");
|
|
907
|
+
lines.push("- **Contradiction Detection** (5 tests): Can it flag conflicting information?");
|
|
908
|
+
lines.push("");
|
|
909
|
+
lines.push("### Scoring");
|
|
910
|
+
lines.push("");
|
|
911
|
+
lines.push("- **Accuracy**: Percentage of expected keywords found in response");
|
|
912
|
+
lines.push("- **Precision**: True positives / (True positives + False positives)");
|
|
913
|
+
lines.push("- **Recall**: True positives / (True positives + False negatives)");
|
|
914
|
+
lines.push("- **F1 Score**: Harmonic mean of Precision and Recall");
|
|
915
|
+
lines.push("");
|
|
916
|
+
|
|
917
|
+
// Footer
|
|
918
|
+
lines.push("---");
|
|
919
|
+
lines.push("");
|
|
920
|
+
lines.push("*Report generated by Vesper Accuracy Benchmark System*");
|
|
921
|
+
lines.push("");
|
|
922
|
+
lines.push("*This benchmark measures the VALUE of memory (accuracy improvement), not just the COST (latency overhead).*");
|
|
923
|
+
|
|
924
|
+
return lines.join("\n");
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// =============================================================================
|
|
928
|
+
// Database Initialization
|
|
929
|
+
// =============================================================================
|
|
930
|
+
|
|
931
|
+
function initializeDatabase(dbPath: string): Database.Database {
|
|
932
|
+
const dataDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
933
|
+
if (dataDir && !existsSync(dataDir)) {
|
|
934
|
+
mkdirSync(dataDir, { recursive: true });
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const db = new Database(dbPath);
|
|
938
|
+
|
|
939
|
+
db.exec(`
|
|
940
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
941
|
+
id TEXT PRIMARY KEY,
|
|
942
|
+
name TEXT NOT NULL,
|
|
943
|
+
type TEXT NOT NULL,
|
|
944
|
+
description TEXT,
|
|
945
|
+
confidence REAL DEFAULT 1.0,
|
|
946
|
+
created_at TEXT NOT NULL,
|
|
947
|
+
last_accessed TEXT NOT NULL,
|
|
948
|
+
access_count INTEGER DEFAULT 1
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
952
|
+
id TEXT PRIMARY KEY,
|
|
953
|
+
source_id TEXT NOT NULL,
|
|
954
|
+
target_id TEXT NOT NULL,
|
|
955
|
+
relation_type TEXT NOT NULL,
|
|
956
|
+
strength REAL DEFAULT 0.8,
|
|
957
|
+
evidence TEXT,
|
|
958
|
+
created_at TEXT NOT NULL,
|
|
959
|
+
last_reinforced TEXT NOT NULL,
|
|
960
|
+
FOREIGN KEY (source_id) REFERENCES entities(id),
|
|
961
|
+
FOREIGN KEY (target_id) REFERENCES entities(id)
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
965
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
966
|
+
`);
|
|
967
|
+
|
|
968
|
+
return db;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// =============================================================================
|
|
972
|
+
// Main Benchmark Runner
|
|
973
|
+
// =============================================================================
|
|
974
|
+
|
|
975
|
+
async function main() {
|
|
976
|
+
console.log("=".repeat(60));
|
|
977
|
+
console.log(" Vesper Accuracy Benchmark");
|
|
978
|
+
console.log("=".repeat(60));
|
|
979
|
+
console.log("");
|
|
980
|
+
console.log("This benchmark measures the REAL VALUE of memory:");
|
|
981
|
+
console.log(" - Does memory improve answer accuracy?");
|
|
982
|
+
console.log(" - Can it recall stored facts?");
|
|
983
|
+
console.log(" - Can it detect contradictions?");
|
|
984
|
+
console.log("");
|
|
985
|
+
console.log("Test Categories:");
|
|
986
|
+
console.log(" - Factual Recall (5 tests)");
|
|
987
|
+
console.log(" - Preference Memory (5 tests)");
|
|
988
|
+
console.log(" - Temporal Context (5 tests)");
|
|
989
|
+
console.log(" - Multi-hop Reasoning (5 tests)");
|
|
990
|
+
console.log(" - Contradiction Detection (5 tests)");
|
|
991
|
+
console.log("");
|
|
992
|
+
|
|
993
|
+
const startTime = Date.now();
|
|
994
|
+
|
|
995
|
+
// Initialize infrastructure
|
|
996
|
+
console.log("Connecting to infrastructure...");
|
|
997
|
+
|
|
998
|
+
const redis = new Redis({
|
|
999
|
+
host: process.env.REDIS_HOST || "localhost",
|
|
1000
|
+
port: parseInt(process.env.REDIS_PORT || "6379"),
|
|
1001
|
+
password: process.env.REDIS_PASSWORD,
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
const dbPath = process.env.SQLITE_DB || "./data/memory.db";
|
|
1005
|
+
const db = initializeDatabase(dbPath);
|
|
1006
|
+
|
|
1007
|
+
const embeddingClient = new EmbeddingClient({
|
|
1008
|
+
serviceUrl: process.env.EMBEDDING_SERVICE_URL || "http://localhost:8000",
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
const hybridSearch = new HybridSearchEngine(
|
|
1012
|
+
process.env.QDRANT_URL || "http://localhost:6333",
|
|
1013
|
+
"memory-vectors",
|
|
1014
|
+
1024,
|
|
1015
|
+
process.env.QDRANT_API_KEY
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
// Test connections
|
|
1019
|
+
try {
|
|
1020
|
+
await redis.ping();
|
|
1021
|
+
console.log(" [OK] Redis connected");
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
console.error(" [FAIL] Redis:", error);
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
try {
|
|
1028
|
+
await embeddingClient.health();
|
|
1029
|
+
console.log(" [OK] Embedding service connected");
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
console.error(" [FAIL] Embedding service:", error);
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
try {
|
|
1036
|
+
await hybridSearch.initializeCollection();
|
|
1037
|
+
} catch {
|
|
1038
|
+
// Collection may exist
|
|
1039
|
+
}
|
|
1040
|
+
console.log(" [OK] Qdrant connected");
|
|
1041
|
+
console.log(" [OK] SQLite connected");
|
|
1042
|
+
console.log("");
|
|
1043
|
+
|
|
1044
|
+
// Create client
|
|
1045
|
+
const client = new AccuracyBenchmarkClient(redis, db, embeddingClient, hybridSearch);
|
|
1046
|
+
|
|
1047
|
+
// Run tests
|
|
1048
|
+
console.log("Running accuracy tests...");
|
|
1049
|
+
console.log("");
|
|
1050
|
+
|
|
1051
|
+
const testResults: TestResult[] = [];
|
|
1052
|
+
|
|
1053
|
+
for (const testCase of TEST_CASES) {
|
|
1054
|
+
process.stdout.write(` Testing ${testCase.id}... `);
|
|
1055
|
+
|
|
1056
|
+
// Clear session for clean state
|
|
1057
|
+
client.newSession();
|
|
1058
|
+
await client.clearSession();
|
|
1059
|
+
|
|
1060
|
+
// ENABLED TEST: Store facts, then query
|
|
1061
|
+
await client.storeFacts(testCase.facts, testCase.id);
|
|
1062
|
+
const enabledResult = await client.query(testCase.query);
|
|
1063
|
+
const enabledEval = evaluateAccuracy(enabledResult.response, testCase.expectedKeywords);
|
|
1064
|
+
|
|
1065
|
+
// Clear for disabled test
|
|
1066
|
+
client.newSession();
|
|
1067
|
+
await client.clearSession();
|
|
1068
|
+
|
|
1069
|
+
// DISABLED TEST: Query without storing facts
|
|
1070
|
+
const disabledStart = performance.now();
|
|
1071
|
+
const disabledResult = {
|
|
1072
|
+
response: "No information available - memory is disabled.",
|
|
1073
|
+
memoryHit: false,
|
|
1074
|
+
latencyMs: performance.now() - disabledStart,
|
|
1075
|
+
};
|
|
1076
|
+
const disabledEval = evaluateAccuracy(disabledResult.response, testCase.expectedKeywords);
|
|
1077
|
+
|
|
1078
|
+
testResults.push({
|
|
1079
|
+
testCase,
|
|
1080
|
+
enabled: {
|
|
1081
|
+
response: enabledResult.response,
|
|
1082
|
+
keywordsFound: enabledEval.found,
|
|
1083
|
+
keywordsMissed: enabledEval.missed,
|
|
1084
|
+
accuracy: enabledEval.accuracy,
|
|
1085
|
+
memoryHit: enabledResult.memoryHit,
|
|
1086
|
+
latencyMs: enabledResult.latencyMs,
|
|
1087
|
+
},
|
|
1088
|
+
disabled: {
|
|
1089
|
+
response: disabledResult.response,
|
|
1090
|
+
keywordsFound: disabledEval.found,
|
|
1091
|
+
keywordsMissed: disabledEval.missed,
|
|
1092
|
+
accuracy: disabledEval.accuracy,
|
|
1093
|
+
latencyMs: disabledResult.latencyMs,
|
|
1094
|
+
},
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
const status = enabledEval.accuracy > disabledEval.accuracy ? "[PASS]" : "[----]";
|
|
1098
|
+
console.log(`${status} enabled=${(enabledEval.accuracy * 100).toFixed(0)}% disabled=${(disabledEval.accuracy * 100).toFixed(0)}%`);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
console.log("");
|
|
1102
|
+
|
|
1103
|
+
// Calculate scenario results
|
|
1104
|
+
const categories = ["factual", "preference", "temporal", "multi-hop", "contradiction"] as const;
|
|
1105
|
+
const scenarioResults: ScenarioResult[] = [];
|
|
1106
|
+
|
|
1107
|
+
for (const category of categories) {
|
|
1108
|
+
const categoryResults = testResults.filter((r) => r.testCase.category === category);
|
|
1109
|
+
|
|
1110
|
+
const enabledMetrics = calculateMetrics(categoryResults, true);
|
|
1111
|
+
const disabledMetrics = calculateMetrics(categoryResults, false);
|
|
1112
|
+
|
|
1113
|
+
// Statistical significance
|
|
1114
|
+
const enabledAccuracies = categoryResults.map((r) => r.enabled.accuracy);
|
|
1115
|
+
const disabledAccuracies = categoryResults.map((r) => r.disabled.accuracy);
|
|
1116
|
+
const tTest = welchTTest(enabledAccuracies, disabledAccuracies);
|
|
1117
|
+
const effectSize = cohensD(enabledAccuracies, disabledAccuracies);
|
|
1118
|
+
|
|
1119
|
+
scenarioResults.push({
|
|
1120
|
+
category,
|
|
1121
|
+
testCount: categoryResults.length,
|
|
1122
|
+
enabledMetrics: {
|
|
1123
|
+
...enabledMetrics,
|
|
1124
|
+
memoryHitRate: enabledMetrics.memoryHitRate || 0,
|
|
1125
|
+
},
|
|
1126
|
+
disabledMetrics,
|
|
1127
|
+
improvementDelta: {
|
|
1128
|
+
precision: enabledMetrics.precision - disabledMetrics.precision,
|
|
1129
|
+
recall: enabledMetrics.recall - disabledMetrics.recall,
|
|
1130
|
+
f1Score: enabledMetrics.f1Score - disabledMetrics.f1Score,
|
|
1131
|
+
accuracy: enabledMetrics.avgAccuracy - disabledMetrics.avgAccuracy,
|
|
1132
|
+
},
|
|
1133
|
+
statisticalSignificance: {
|
|
1134
|
+
pValue: tTest.pValue,
|
|
1135
|
+
significant: tTest.significant,
|
|
1136
|
+
effectSize,
|
|
1137
|
+
interpretation: interpretEffectSize(effectSize),
|
|
1138
|
+
},
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Calculate overall results
|
|
1143
|
+
const overallEnabledF1 = scenarioResults.reduce((sum, s) => sum + s.enabledMetrics.f1Score, 0) / scenarioResults.length;
|
|
1144
|
+
const overallDisabledF1 = scenarioResults.reduce((sum, s) => sum + s.disabledMetrics.f1Score, 0) / scenarioResults.length;
|
|
1145
|
+
const improvementPercent = overallDisabledF1 > 0
|
|
1146
|
+
? ((overallEnabledF1 - overallDisabledF1) / overallDisabledF1) * 100
|
|
1147
|
+
: overallEnabledF1 * 100;
|
|
1148
|
+
|
|
1149
|
+
const significantCount = scenarioResults.filter((s) => s.statisticalSignificance.significant).length;
|
|
1150
|
+
const statisticallySignificant = significantCount >= scenarioResults.length / 2;
|
|
1151
|
+
|
|
1152
|
+
let conclusion: string;
|
|
1153
|
+
if (overallEnabledF1 >= 0.8 && improvementPercent >= 50) {
|
|
1154
|
+
conclusion = "Vesper memory provides STRONG value. Enabled mode significantly outperforms disabled mode with high accuracy and statistical significance.";
|
|
1155
|
+
} else if (overallEnabledF1 >= 0.6 && improvementPercent >= 20) {
|
|
1156
|
+
conclusion = "Vesper memory provides MODERATE value. Enabled mode shows meaningful improvement over disabled mode.";
|
|
1157
|
+
} else if (overallEnabledF1 >= 0.4) {
|
|
1158
|
+
conclusion = "Vesper memory provides LIMITED value. Some improvement visible but accuracy needs enhancement.";
|
|
1159
|
+
} else {
|
|
1160
|
+
conclusion = "Vesper memory needs improvement. Current accuracy is below acceptable thresholds.";
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
const duration = Date.now() - startTime;
|
|
1164
|
+
|
|
1165
|
+
const benchmarkResult: AccuracyBenchmarkResult = {
|
|
1166
|
+
id: `accuracy-${randomUUID().slice(0, 8)}`,
|
|
1167
|
+
timestamp: new Date().toISOString(),
|
|
1168
|
+
duration,
|
|
1169
|
+
scenarios: scenarioResults,
|
|
1170
|
+
overall: {
|
|
1171
|
+
enabledF1: overallEnabledF1,
|
|
1172
|
+
disabledF1: overallDisabledF1,
|
|
1173
|
+
improvementPercent,
|
|
1174
|
+
statisticallySignificant,
|
|
1175
|
+
conclusion,
|
|
1176
|
+
},
|
|
1177
|
+
testResults,
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
// Generate and save report
|
|
1181
|
+
console.log("Generating report...");
|
|
1182
|
+
const report = generateAccuracyReport(benchmarkResult);
|
|
1183
|
+
|
|
1184
|
+
const outputDir = "./benchmarks";
|
|
1185
|
+
mkdirSync(outputDir, { recursive: true });
|
|
1186
|
+
|
|
1187
|
+
const reportPath = join(outputDir, "accuracy-results.md");
|
|
1188
|
+
writeFileSync(reportPath, report);
|
|
1189
|
+
|
|
1190
|
+
console.log(` Report saved to ${reportPath}`);
|
|
1191
|
+
console.log("");
|
|
1192
|
+
|
|
1193
|
+
// Print summary
|
|
1194
|
+
console.log("=".repeat(60));
|
|
1195
|
+
console.log(" Summary");
|
|
1196
|
+
console.log("=".repeat(60));
|
|
1197
|
+
console.log("");
|
|
1198
|
+
|
|
1199
|
+
for (const scenario of scenarioResults) {
|
|
1200
|
+
const winner = scenario.enabledMetrics.f1Score > scenario.disabledMetrics.f1Score
|
|
1201
|
+
? "[WIN]"
|
|
1202
|
+
: scenario.enabledMetrics.f1Score < scenario.disabledMetrics.f1Score
|
|
1203
|
+
? "[LOSS]"
|
|
1204
|
+
: "[TIE]";
|
|
1205
|
+
|
|
1206
|
+
console.log(`${scenario.category}:`);
|
|
1207
|
+
console.log(` ${winner} Enabled: ${(scenario.enabledMetrics.avgAccuracy * 100).toFixed(0)}% vs Disabled: ${(scenario.disabledMetrics.avgAccuracy * 100).toFixed(0)}%`);
|
|
1208
|
+
console.log(` F1 Score: ${(scenario.enabledMetrics.f1Score * 100).toFixed(0)}% vs ${(scenario.disabledMetrics.f1Score * 100).toFixed(0)}%`);
|
|
1209
|
+
console.log(` Significant: ${scenario.statisticalSignificance.significant ? "Yes" : "No"} (p=${scenario.statisticalSignificance.pValue.toFixed(4)})`);
|
|
1210
|
+
console.log("");
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
console.log("=".repeat(60));
|
|
1214
|
+
console.log(" Overall Results");
|
|
1215
|
+
console.log("=".repeat(60));
|
|
1216
|
+
console.log(` Enabled F1: ${(overallEnabledF1 * 100).toFixed(1)}%`);
|
|
1217
|
+
console.log(` Disabled F1: ${(overallDisabledF1 * 100).toFixed(1)}%`);
|
|
1218
|
+
console.log(` Improvement: +${improvementPercent.toFixed(1)}%`);
|
|
1219
|
+
console.log(` Statistically Significant: ${statisticallySignificant ? "Yes" : "No"}`);
|
|
1220
|
+
console.log("");
|
|
1221
|
+
console.log(`Conclusion: ${conclusion}`);
|
|
1222
|
+
console.log("");
|
|
1223
|
+
console.log(`Full report: ${reportPath}`);
|
|
1224
|
+
console.log("");
|
|
1225
|
+
|
|
1226
|
+
// Cleanup
|
|
1227
|
+
await redis.quit();
|
|
1228
|
+
db.close();
|
|
1229
|
+
|
|
1230
|
+
console.log("Benchmark complete!");
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// Run benchmark
|
|
1234
|
+
main().catch((error) => {
|
|
1235
|
+
console.error("Benchmark failed:", error);
|
|
1236
|
+
process.exit(1);
|
|
1237
|
+
});
|