@twelvehart/supermemory-runtime 1.0.0-next.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 +57 -0
- package/README.md +374 -0
- package/dist/index.js +189 -0
- package/dist/mcp/index.js +1132 -0
- package/docker-compose.prod.yml +91 -0
- package/docker-compose.yml +358 -0
- package/drizzle/0000_dapper_the_professor.sql +159 -0
- package/drizzle/0001_api_keys.sql +51 -0
- package/drizzle/meta/0000_snapshot.json +1532 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +20 -0
- package/package.json +114 -0
- package/scripts/add-extraction-job.ts +122 -0
- package/scripts/benchmark-pgvector.ts +122 -0
- package/scripts/bootstrap.sh +209 -0
- package/scripts/check-runtime-pack.ts +111 -0
- package/scripts/claude-mcp-config.ts +336 -0
- package/scripts/docker-entrypoint.sh +183 -0
- package/scripts/doctor.ts +377 -0
- package/scripts/init-db.sql +33 -0
- package/scripts/install.sh +1110 -0
- package/scripts/mcp-setup.ts +271 -0
- package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
- package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
- package/scripts/migrations/003_create_hnsw_index.sql +94 -0
- package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
- package/scripts/migrations/005_create_chunks_table.sql +95 -0
- package/scripts/migrations/006_create_processing_queue.sql +45 -0
- package/scripts/migrations/generate_test_data.sql +42 -0
- package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
- package/scripts/migrations/run_migrations.sh +286 -0
- package/scripts/migrations/test_hnsw_index.sql +255 -0
- package/scripts/pre-commit-secrets +282 -0
- package/scripts/run-extraction-worker.ts +46 -0
- package/scripts/run-phase1-tests.sh +291 -0
- package/scripts/setup.ts +222 -0
- package/scripts/smoke-install.sh +12 -0
- package/scripts/test-health-endpoint.sh +328 -0
- package/src/api/index.ts +2 -0
- package/src/api/middleware/auth.ts +80 -0
- package/src/api/middleware/csrf.ts +308 -0
- package/src/api/middleware/errorHandler.ts +166 -0
- package/src/api/middleware/rateLimit.ts +360 -0
- package/src/api/middleware/validation.ts +514 -0
- package/src/api/routes/documents.ts +286 -0
- package/src/api/routes/profiles.ts +237 -0
- package/src/api/routes/search.ts +71 -0
- package/src/api/stores/index.ts +58 -0
- package/src/config/bootstrap-env.ts +3 -0
- package/src/config/env.ts +71 -0
- package/src/config/feature-flags.ts +25 -0
- package/src/config/index.ts +140 -0
- package/src/config/secrets.config.ts +291 -0
- package/src/db/client.ts +92 -0
- package/src/db/index.ts +73 -0
- package/src/db/postgres.ts +72 -0
- package/src/db/schema/chunks.schema.ts +31 -0
- package/src/db/schema/containers.schema.ts +46 -0
- package/src/db/schema/documents.schema.ts +49 -0
- package/src/db/schema/embeddings.schema.ts +32 -0
- package/src/db/schema/index.ts +11 -0
- package/src/db/schema/memories.schema.ts +72 -0
- package/src/db/schema/profiles.schema.ts +34 -0
- package/src/db/schema/queue.schema.ts +59 -0
- package/src/db/schema/relationships.schema.ts +42 -0
- package/src/db/schema.ts +223 -0
- package/src/db/worker-connection.ts +47 -0
- package/src/index.ts +235 -0
- package/src/mcp/CLAUDE.md +1 -0
- package/src/mcp/index.ts +1380 -0
- package/src/mcp/legacyState.ts +22 -0
- package/src/mcp/rateLimit.ts +358 -0
- package/src/mcp/resources.ts +309 -0
- package/src/mcp/results.ts +104 -0
- package/src/mcp/tools.ts +401 -0
- package/src/queues/config.ts +119 -0
- package/src/queues/index.ts +289 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/errors.ts +266 -0
- package/src/sdk/http.ts +560 -0
- package/src/sdk/index.ts +244 -0
- package/src/sdk/resources/base.ts +65 -0
- package/src/sdk/resources/connections.ts +204 -0
- package/src/sdk/resources/documents.ts +163 -0
- package/src/sdk/resources/index.ts +10 -0
- package/src/sdk/resources/memories.ts +150 -0
- package/src/sdk/resources/search.ts +60 -0
- package/src/sdk/resources/settings.ts +36 -0
- package/src/sdk/types.ts +674 -0
- package/src/services/chunking/index.ts +451 -0
- package/src/services/chunking.service.ts +650 -0
- package/src/services/csrf.service.ts +252 -0
- package/src/services/documents.repository.ts +219 -0
- package/src/services/documents.service.ts +191 -0
- package/src/services/embedding.service.ts +404 -0
- package/src/services/extraction.service.ts +300 -0
- package/src/services/extractors/code.extractor.ts +451 -0
- package/src/services/extractors/index.ts +9 -0
- package/src/services/extractors/markdown.extractor.ts +461 -0
- package/src/services/extractors/pdf.extractor.ts +315 -0
- package/src/services/extractors/text.extractor.ts +118 -0
- package/src/services/extractors/url.extractor.ts +243 -0
- package/src/services/index.ts +235 -0
- package/src/services/ingestion.service.ts +177 -0
- package/src/services/llm/anthropic.ts +400 -0
- package/src/services/llm/base.ts +460 -0
- package/src/services/llm/contradiction-detector.service.ts +526 -0
- package/src/services/llm/heuristics.ts +148 -0
- package/src/services/llm/index.ts +309 -0
- package/src/services/llm/memory-classifier.service.ts +383 -0
- package/src/services/llm/memory-extension-detector.service.ts +523 -0
- package/src/services/llm/mock.ts +470 -0
- package/src/services/llm/openai.ts +398 -0
- package/src/services/llm/prompts.ts +438 -0
- package/src/services/llm/types.ts +373 -0
- package/src/services/memory.repository.ts +1769 -0
- package/src/services/memory.service.ts +1338 -0
- package/src/services/memory.types.ts +234 -0
- package/src/services/persistence/index.ts +295 -0
- package/src/services/pipeline.service.ts +509 -0
- package/src/services/profile.repository.ts +436 -0
- package/src/services/profile.service.ts +560 -0
- package/src/services/profile.types.ts +270 -0
- package/src/services/relationships/detector.ts +1128 -0
- package/src/services/relationships/index.ts +268 -0
- package/src/services/relationships/memory-integration.ts +459 -0
- package/src/services/relationships/strategies.ts +132 -0
- package/src/services/relationships/types.ts +370 -0
- package/src/services/search.service.ts +761 -0
- package/src/services/search.types.ts +220 -0
- package/src/services/secrets.service.ts +384 -0
- package/src/services/vectorstore/base.ts +327 -0
- package/src/services/vectorstore/index.ts +444 -0
- package/src/services/vectorstore/memory.ts +286 -0
- package/src/services/vectorstore/migration.ts +295 -0
- package/src/services/vectorstore/mock.ts +403 -0
- package/src/services/vectorstore/pgvector.ts +695 -0
- package/src/services/vectorstore/types.ts +247 -0
- package/src/startup.ts +389 -0
- package/src/types/api.types.ts +193 -0
- package/src/types/document.types.ts +103 -0
- package/src/types/index.ts +241 -0
- package/src/types/profile.base.ts +133 -0
- package/src/utils/errors.ts +447 -0
- package/src/utils/id.ts +15 -0
- package/src/utils/index.ts +101 -0
- package/src/utils/logger.ts +313 -0
- package/src/utils/sanitization.ts +501 -0
- package/src/utils/secret-validation.ts +273 -0
- package/src/utils/synonyms.ts +188 -0
- package/src/utils/validation.ts +581 -0
- package/src/workers/chunking.worker.ts +242 -0
- package/src/workers/embedding.worker.ts +358 -0
- package/src/workers/extraction.worker.ts +346 -0
- package/src/workers/indexing.worker.ts +505 -0
- package/tsconfig.json +38 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Prompts for Memory Extraction and Relationship Detection
|
|
3
|
+
*
|
|
4
|
+
* Contains carefully crafted prompts with few-shot examples for accurate
|
|
5
|
+
* memory extraction and classification.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { MemoryType } from '../../types/index.js'
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Memory Extraction Prompts
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* System prompt for memory extraction
|
|
16
|
+
*/
|
|
17
|
+
export const MEMORY_EXTRACTION_SYSTEM_PROMPT = `You are an expert memory extraction system. Your task is to extract discrete, standalone facts, preferences, skills, and episodic memories from text content.
|
|
18
|
+
|
|
19
|
+
For each extracted memory, you must:
|
|
20
|
+
1. Create a clear, standalone statement that makes sense without context
|
|
21
|
+
2. Classify it into the correct type
|
|
22
|
+
3. Assign a confidence score (0.0-1.0) based on clarity and reliability
|
|
23
|
+
4. Extract relevant entities (people, places, organizations, dates)
|
|
24
|
+
5. Identify key keywords
|
|
25
|
+
|
|
26
|
+
Memory Types:
|
|
27
|
+
- fact: Objective information, statements of truth, definitions
|
|
28
|
+
- event: Time-bound occurrences, meetings, experiences
|
|
29
|
+
- preference: Personal likes, dislikes, preferences, opinions
|
|
30
|
+
- skill: Abilities, capabilities, expertise, knowledge areas
|
|
31
|
+
- relationship: Interpersonal connections, social bonds
|
|
32
|
+
- context: Current situations, states, or ongoing activities
|
|
33
|
+
- note: General notes, reminders, todos
|
|
34
|
+
|
|
35
|
+
Guidelines:
|
|
36
|
+
- Each memory should be self-contained and understandable alone
|
|
37
|
+
- Be precise and avoid vague statements
|
|
38
|
+
- Include relevant context in the memory itself
|
|
39
|
+
- Higher confidence for explicit statements, lower for inferences
|
|
40
|
+
- Extract multiple memories from complex sentences`
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Few-shot examples for memory extraction
|
|
44
|
+
*/
|
|
45
|
+
export const MEMORY_EXTRACTION_EXAMPLES = `
|
|
46
|
+
Example 1:
|
|
47
|
+
Input: "I've been using TypeScript for 3 years now and really prefer it over plain JavaScript. Currently working on a React project for my company Acme Inc."
|
|
48
|
+
|
|
49
|
+
Output:
|
|
50
|
+
{
|
|
51
|
+
"memories": [
|
|
52
|
+
{
|
|
53
|
+
"content": "Has been using TypeScript for 3 years",
|
|
54
|
+
"type": "skill",
|
|
55
|
+
"confidence": 0.95,
|
|
56
|
+
"entities": [{"name": "TypeScript", "type": "concept"}],
|
|
57
|
+
"keywords": ["typescript", "programming", "experience"]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"content": "Prefers TypeScript over plain JavaScript for development",
|
|
61
|
+
"type": "preference",
|
|
62
|
+
"confidence": 0.90,
|
|
63
|
+
"entities": [{"name": "TypeScript", "type": "concept"}, {"name": "JavaScript", "type": "concept"}],
|
|
64
|
+
"keywords": ["typescript", "javascript", "preference"]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"content": "Currently working on a React project",
|
|
68
|
+
"type": "context",
|
|
69
|
+
"confidence": 0.85,
|
|
70
|
+
"entities": [{"name": "React", "type": "concept"}],
|
|
71
|
+
"keywords": ["react", "project", "current"]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"content": "Works for Acme Inc.",
|
|
75
|
+
"type": "relationship",
|
|
76
|
+
"confidence": 0.90,
|
|
77
|
+
"entities": [{"name": "Acme Inc.", "type": "organization"}],
|
|
78
|
+
"keywords": ["employment", "company", "work"]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Example 2:
|
|
84
|
+
Input: "Note: Remember to update the API docs before the Friday release. The authentication endpoint was changed last week."
|
|
85
|
+
|
|
86
|
+
Output:
|
|
87
|
+
{
|
|
88
|
+
"memories": [
|
|
89
|
+
{
|
|
90
|
+
"content": "Need to update API docs before Friday release",
|
|
91
|
+
"type": "note",
|
|
92
|
+
"confidence": 0.85,
|
|
93
|
+
"entities": [],
|
|
94
|
+
"keywords": ["api", "documentation", "release", "friday"]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"content": "Authentication endpoint was modified recently",
|
|
98
|
+
"type": "event",
|
|
99
|
+
"confidence": 0.90,
|
|
100
|
+
"entities": [],
|
|
101
|
+
"keywords": ["authentication", "endpoint", "change", "api"]
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
Example 3:
|
|
107
|
+
Input: "Dr. Sarah Chen, my mentor at Stanford, taught me that clean code is more important than clever code."
|
|
108
|
+
|
|
109
|
+
Output:
|
|
110
|
+
{
|
|
111
|
+
"memories": [
|
|
112
|
+
{
|
|
113
|
+
"content": "Dr. Sarah Chen is a mentor",
|
|
114
|
+
"type": "relationship",
|
|
115
|
+
"confidence": 0.95,
|
|
116
|
+
"entities": [{"name": "Dr. Sarah Chen", "type": "person"}, {"name": "Stanford", "type": "organization"}],
|
|
117
|
+
"keywords": ["mentor", "relationship"]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"content": "Believes clean code is more important than clever code",
|
|
121
|
+
"type": "preference",
|
|
122
|
+
"confidence": 0.85,
|
|
123
|
+
"entities": [],
|
|
124
|
+
"keywords": ["clean code", "programming", "philosophy"]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"content": "Has connection to Stanford",
|
|
128
|
+
"type": "relationship",
|
|
129
|
+
"confidence": 0.80,
|
|
130
|
+
"entities": [{"name": "Stanford", "type": "organization"}],
|
|
131
|
+
"keywords": ["stanford", "education"]
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}`
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate the user prompt for memory extraction
|
|
138
|
+
*/
|
|
139
|
+
export function generateExtractionPrompt(
|
|
140
|
+
text: string,
|
|
141
|
+
options?: {
|
|
142
|
+
containerTag?: string
|
|
143
|
+
context?: string
|
|
144
|
+
maxMemories?: number
|
|
145
|
+
minConfidence?: number
|
|
146
|
+
}
|
|
147
|
+
): string {
|
|
148
|
+
let prompt = `Extract memories from the following text. Return a JSON object with a "memories" array.\n\n`
|
|
149
|
+
|
|
150
|
+
if (options?.containerTag) {
|
|
151
|
+
prompt += `Container/Category: ${options.containerTag}\n`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (options?.context) {
|
|
155
|
+
prompt += `Additional Context: ${options.context}\n`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (options?.maxMemories) {
|
|
159
|
+
prompt += `Maximum memories to extract: ${options.maxMemories}\n`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (options?.minConfidence) {
|
|
163
|
+
prompt += `Minimum confidence threshold: ${options.minConfidence}\n`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
prompt += `\nText to analyze:\n"""\n${text}\n"""\n\n`
|
|
167
|
+
prompt += `Respond with ONLY a valid JSON object in this exact format:
|
|
168
|
+
{
|
|
169
|
+
"memories": [
|
|
170
|
+
{
|
|
171
|
+
"content": "string - standalone statement",
|
|
172
|
+
"type": "fact|event|preference|skill|relationship|context|note",
|
|
173
|
+
"confidence": 0.0-1.0,
|
|
174
|
+
"entities": [{"name": "string", "type": "person|place|organization|date|concept|other"}],
|
|
175
|
+
"keywords": ["string"]
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}`
|
|
179
|
+
|
|
180
|
+
return prompt
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Relationship Detection Prompts
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* System prompt for relationship detection
|
|
189
|
+
*/
|
|
190
|
+
export const RELATIONSHIP_DETECTION_SYSTEM_PROMPT = `You are an expert at detecting semantic relationships between pieces of information.
|
|
191
|
+
|
|
192
|
+
Given a NEW memory and a list of EXISTING memories, determine what relationships exist.
|
|
193
|
+
|
|
194
|
+
Relationship Types:
|
|
195
|
+
- updates: NEW contradicts or corrects OLD, making OLD outdated
|
|
196
|
+
- extends: NEW adds detail or elaboration to OLD without contradicting
|
|
197
|
+
- derives: NEW is a logical consequence or inference from OLD
|
|
198
|
+
- contradicts: NEW directly conflicts with OLD (both may be valid from different times)
|
|
199
|
+
- related: NEW is semantically similar or topically connected to OLD
|
|
200
|
+
- supersedes: NEW completely replaces OLD (OLD should be archived)
|
|
201
|
+
|
|
202
|
+
Guidelines:
|
|
203
|
+
- Only identify relationships with confidence >= 0.6
|
|
204
|
+
- "updates" and "supersedes" should mark the old memory for supersession
|
|
205
|
+
- "contradicts" does NOT mean the old memory should be removed (both may be valid)
|
|
206
|
+
- Consider temporal context when detecting updates
|
|
207
|
+
- Be conservative - prefer no relationship over a weak one`
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Few-shot examples for relationship detection
|
|
211
|
+
*/
|
|
212
|
+
export const RELATIONSHIP_DETECTION_EXAMPLES = `
|
|
213
|
+
Example 1:
|
|
214
|
+
NEW Memory: { "id": "new1", "content": "Uses Python 3.11 for all projects", "type": "preference" }
|
|
215
|
+
EXISTING Memories: [
|
|
216
|
+
{ "id": "old1", "content": "Prefers Python 3.9", "type": "preference" },
|
|
217
|
+
{ "id": "old2", "content": "Expert in Python programming", "type": "skill" }
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
Output:
|
|
221
|
+
{
|
|
222
|
+
"relationships": [
|
|
223
|
+
{
|
|
224
|
+
"sourceMemoryId": "new1",
|
|
225
|
+
"targetMemoryId": "old1",
|
|
226
|
+
"type": "updates",
|
|
227
|
+
"confidence": 0.90,
|
|
228
|
+
"reason": "New version preference supersedes old version preference"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"sourceMemoryId": "new1",
|
|
232
|
+
"targetMemoryId": "old2",
|
|
233
|
+
"type": "related",
|
|
234
|
+
"confidence": 0.75,
|
|
235
|
+
"reason": "Both relate to Python programming"
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
"supersededMemoryIds": ["old1"]
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
Example 2:
|
|
242
|
+
NEW Memory: { "id": "new2", "content": "The API now supports batch operations", "type": "fact" }
|
|
243
|
+
EXISTING Memories: [
|
|
244
|
+
{ "id": "old3", "content": "The API only supports single-item operations", "type": "fact" },
|
|
245
|
+
{ "id": "old4", "content": "Working on adding batch support to the API", "type": "context" }
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
Output:
|
|
249
|
+
{
|
|
250
|
+
"relationships": [
|
|
251
|
+
{
|
|
252
|
+
"sourceMemoryId": "new2",
|
|
253
|
+
"targetMemoryId": "old3",
|
|
254
|
+
"type": "supersedes",
|
|
255
|
+
"confidence": 0.95,
|
|
256
|
+
"reason": "New capability statement makes old limitation statement obsolete"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"sourceMemoryId": "new2",
|
|
260
|
+
"targetMemoryId": "old4",
|
|
261
|
+
"type": "derives",
|
|
262
|
+
"confidence": 0.85,
|
|
263
|
+
"reason": "Batch support being complete is a result of the work mentioned"
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
"supersededMemoryIds": ["old3", "old4"]
|
|
267
|
+
}`
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Generate the user prompt for relationship detection
|
|
271
|
+
*/
|
|
272
|
+
export function generateRelationshipPrompt(
|
|
273
|
+
newMemory: { id: string; content: string; type: MemoryType },
|
|
274
|
+
existingMemories: Array<{ id: string; content: string; type: MemoryType }>,
|
|
275
|
+
options?: {
|
|
276
|
+
maxRelationships?: number
|
|
277
|
+
minConfidence?: number
|
|
278
|
+
}
|
|
279
|
+
): string {
|
|
280
|
+
let prompt = `Analyze the relationship between the NEW memory and EXISTING memories.\n\n`
|
|
281
|
+
|
|
282
|
+
prompt += `NEW Memory:\n${JSON.stringify(newMemory, null, 2)}\n\n`
|
|
283
|
+
prompt += `EXISTING Memories:\n${JSON.stringify(existingMemories, null, 2)}\n\n`
|
|
284
|
+
|
|
285
|
+
if (options?.minConfidence) {
|
|
286
|
+
prompt += `Only include relationships with confidence >= ${options.minConfidence}\n`
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (options?.maxRelationships) {
|
|
290
|
+
prompt += `Return at most ${options.maxRelationships} relationships\n`
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
prompt += `\nRespond with ONLY a valid JSON object in this exact format:
|
|
294
|
+
{
|
|
295
|
+
"relationships": [
|
|
296
|
+
{
|
|
297
|
+
"sourceMemoryId": "string - always the NEW memory id",
|
|
298
|
+
"targetMemoryId": "string - an EXISTING memory id",
|
|
299
|
+
"type": "updates|extends|derives|contradicts|related|supersedes",
|
|
300
|
+
"confidence": 0.0-1.0,
|
|
301
|
+
"reason": "string - brief explanation"
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
"supersededMemoryIds": ["string - ids of memories that should be marked as outdated"]
|
|
305
|
+
}`
|
|
306
|
+
|
|
307
|
+
return prompt
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// Response Parsing
|
|
312
|
+
// ============================================================================
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Normalize LLM JSON responses by stripping markdown fences and common wrappers.
|
|
316
|
+
*/
|
|
317
|
+
export function normalizeJsonResponse(response: string): string {
|
|
318
|
+
const trimmed = response.trim()
|
|
319
|
+
|
|
320
|
+
// Prefer fenced JSON blocks when present (allows explanatory prose around JSON).
|
|
321
|
+
const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)
|
|
322
|
+
if (fencedMatch?.[1]) {
|
|
323
|
+
return fencedMatch[1].trim()
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Fallback: extract the outermost JSON object if surrounded by extra text.
|
|
327
|
+
const firstBrace = trimmed.indexOf('{')
|
|
328
|
+
const lastBrace = trimmed.lastIndexOf('}')
|
|
329
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
330
|
+
return trimmed.slice(firstBrace, lastBrace + 1).trim()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return trimmed
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Parse and validate extraction response from LLM
|
|
338
|
+
*/
|
|
339
|
+
export function parseExtractionResponse(response: string): {
|
|
340
|
+
memories: Array<{
|
|
341
|
+
content: string
|
|
342
|
+
type: MemoryType
|
|
343
|
+
confidence: number
|
|
344
|
+
entities: Array<{ name: string; type: string }>
|
|
345
|
+
keywords: string[]
|
|
346
|
+
}>
|
|
347
|
+
} {
|
|
348
|
+
const cleaned = normalizeJsonResponse(response)
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const parsed = JSON.parse(cleaned)
|
|
352
|
+
|
|
353
|
+
if (!parsed.memories || !Array.isArray(parsed.memories)) {
|
|
354
|
+
throw new Error('Response missing memories array')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Validate and clean each memory
|
|
358
|
+
const validTypes: MemoryType[] = ['fact', 'event', 'preference', 'skill', 'relationship', 'context', 'note']
|
|
359
|
+
|
|
360
|
+
const memories = parsed.memories
|
|
361
|
+
.filter((m: unknown) => {
|
|
362
|
+
if (!m || typeof m !== 'object') return false
|
|
363
|
+
const mem = m as Record<string, unknown>
|
|
364
|
+
return (
|
|
365
|
+
typeof mem.content === 'string' &&
|
|
366
|
+
mem.content.length > 0 &&
|
|
367
|
+
typeof mem.type === 'string' &&
|
|
368
|
+
validTypes.includes(mem.type as MemoryType)
|
|
369
|
+
)
|
|
370
|
+
})
|
|
371
|
+
.map((m: Record<string, unknown>) => ({
|
|
372
|
+
content: String(m.content).trim(),
|
|
373
|
+
type: m.type as MemoryType,
|
|
374
|
+
confidence: typeof m.confidence === 'number' ? Math.max(0, Math.min(1, m.confidence)) : 0.5,
|
|
375
|
+
entities: Array.isArray(m.entities)
|
|
376
|
+
? m.entities.filter(
|
|
377
|
+
(e: unknown) => e && typeof e === 'object' && 'name' in (e as object) && 'type' in (e as object)
|
|
378
|
+
)
|
|
379
|
+
: [],
|
|
380
|
+
keywords: Array.isArray(m.keywords) ? m.keywords.filter((k: unknown) => typeof k === 'string') : [],
|
|
381
|
+
}))
|
|
382
|
+
|
|
383
|
+
return { memories }
|
|
384
|
+
} catch (error) {
|
|
385
|
+
throw new Error(`Failed to parse extraction response: ${error instanceof Error ? error.message : String(error)}`)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Parse and validate relationship response from LLM
|
|
391
|
+
*/
|
|
392
|
+
export function parseRelationshipResponse(response: string): {
|
|
393
|
+
relationships: Array<{
|
|
394
|
+
sourceMemoryId: string
|
|
395
|
+
targetMemoryId: string
|
|
396
|
+
type: string
|
|
397
|
+
confidence: number
|
|
398
|
+
reason: string
|
|
399
|
+
}>
|
|
400
|
+
supersededMemoryIds: string[]
|
|
401
|
+
} {
|
|
402
|
+
const cleaned = normalizeJsonResponse(response)
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const parsed = JSON.parse(cleaned)
|
|
406
|
+
|
|
407
|
+
const validTypes = ['updates', 'extends', 'derives', 'contradicts', 'related', 'supersedes']
|
|
408
|
+
|
|
409
|
+
const relationships = Array.isArray(parsed.relationships)
|
|
410
|
+
? parsed.relationships
|
|
411
|
+
.filter((r: unknown) => {
|
|
412
|
+
if (!r || typeof r !== 'object') return false
|
|
413
|
+
const rel = r as Record<string, unknown>
|
|
414
|
+
return (
|
|
415
|
+
typeof rel.sourceMemoryId === 'string' &&
|
|
416
|
+
typeof rel.targetMemoryId === 'string' &&
|
|
417
|
+
typeof rel.type === 'string' &&
|
|
418
|
+
validTypes.includes(rel.type)
|
|
419
|
+
)
|
|
420
|
+
})
|
|
421
|
+
.map((r: Record<string, unknown>) => ({
|
|
422
|
+
sourceMemoryId: String(r.sourceMemoryId),
|
|
423
|
+
targetMemoryId: String(r.targetMemoryId),
|
|
424
|
+
type: String(r.type),
|
|
425
|
+
confidence: typeof r.confidence === 'number' ? Math.max(0, Math.min(1, r.confidence)) : 0.5,
|
|
426
|
+
reason: typeof r.reason === 'string' ? r.reason : 'No reason provided',
|
|
427
|
+
}))
|
|
428
|
+
: []
|
|
429
|
+
|
|
430
|
+
const supersededMemoryIds = Array.isArray(parsed.supersededMemoryIds)
|
|
431
|
+
? parsed.supersededMemoryIds.filter((id: unknown) => typeof id === 'string')
|
|
432
|
+
: []
|
|
433
|
+
|
|
434
|
+
return { relationships, supersededMemoryIds }
|
|
435
|
+
} catch (error) {
|
|
436
|
+
throw new Error(`Failed to parse relationship response: ${error instanceof Error ? error.message : String(error)}`)
|
|
437
|
+
}
|
|
438
|
+
}
|