@prmichaelsen/remember-mcp 0.1.0 → 0.2.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/agent/progress.yaml +96 -36
- package/dist/server-factory.js +1075 -1
- package/dist/server.js +1075 -1
- package/dist/tools/create-relationship.d.ts +79 -0
- package/dist/tools/delete-relationship.d.ts +41 -0
- package/dist/tools/find-similar.d.ts +77 -0
- package/dist/tools/query-memory.d.ts +115 -0
- package/dist/tools/search-relationship.d.ts +86 -0
- package/dist/tools/update-memory.d.ts +92 -0
- package/dist/tools/update-relationship.d.ts +74 -0
- package/package.json +1 -1
- package/src/server-factory.ts +46 -0
- package/src/server.ts +46 -0
- package/src/tools/create-relationship.ts +242 -0
- package/src/tools/delete-relationship.ts +160 -0
- package/src/tools/find-similar.ts +199 -0
- package/src/tools/query-memory.ts +284 -0
- package/src/tools/search-relationship.ts +228 -0
- package/src/tools/update-memory.ts +230 -0
- package/src/tools/update-relationship.ts +189 -0
package/src/server.ts
CHANGED
|
@@ -17,6 +17,15 @@ import { logger } from './utils/logger.js';
|
|
|
17
17
|
import { createMemoryTool, handleCreateMemory } from './tools/create-memory.js';
|
|
18
18
|
import { searchMemoryTool, handleSearchMemory } from './tools/search-memory.js';
|
|
19
19
|
import { deleteMemoryTool, handleDeleteMemory } from './tools/delete-memory.js';
|
|
20
|
+
import { updateMemoryTool, handleUpdateMemory } from './tools/update-memory.js';
|
|
21
|
+
import { findSimilarTool, handleFindSimilar } from './tools/find-similar.js';
|
|
22
|
+
import { queryMemoryTool, handleQueryMemory } from './tools/query-memory.js';
|
|
23
|
+
|
|
24
|
+
// Import relationship tools
|
|
25
|
+
import { createRelationshipTool, handleCreateRelationship } from './tools/create-relationship.js';
|
|
26
|
+
import { updateRelationshipTool, handleUpdateRelationship } from './tools/update-relationship.js';
|
|
27
|
+
import { searchRelationshipTool, handleSearchRelationship } from './tools/search-relationship.js';
|
|
28
|
+
import { deleteRelationshipTool, handleDeleteRelationship } from './tools/delete-relationship.js';
|
|
20
29
|
|
|
21
30
|
/**
|
|
22
31
|
* Initialize remember-mcp server
|
|
@@ -78,9 +87,18 @@ function registerHandlers(server: Server): void {
|
|
|
78
87
|
properties: {},
|
|
79
88
|
},
|
|
80
89
|
},
|
|
90
|
+
// Memory tools
|
|
81
91
|
createMemoryTool,
|
|
82
92
|
searchMemoryTool,
|
|
83
93
|
deleteMemoryTool,
|
|
94
|
+
updateMemoryTool,
|
|
95
|
+
findSimilarTool,
|
|
96
|
+
queryMemoryTool,
|
|
97
|
+
// Relationship tools
|
|
98
|
+
createRelationshipTool,
|
|
99
|
+
updateRelationshipTool,
|
|
100
|
+
searchRelationshipTool,
|
|
101
|
+
deleteRelationshipTool,
|
|
84
102
|
],
|
|
85
103
|
};
|
|
86
104
|
});
|
|
@@ -113,6 +131,34 @@ function registerHandlers(server: Server): void {
|
|
|
113
131
|
result = await handleDeleteMemory(args as any, userId);
|
|
114
132
|
break;
|
|
115
133
|
|
|
134
|
+
case 'remember_update_memory':
|
|
135
|
+
result = await handleUpdateMemory(args as any, userId);
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
case 'remember_find_similar':
|
|
139
|
+
result = await handleFindSimilar(args as any, userId);
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case 'remember_query_memory':
|
|
143
|
+
result = await handleQueryMemory(args as any, userId);
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case 'remember_create_relationship':
|
|
147
|
+
result = await handleCreateRelationship(args as any, userId);
|
|
148
|
+
break;
|
|
149
|
+
|
|
150
|
+
case 'remember_update_relationship':
|
|
151
|
+
result = await handleUpdateRelationship(args as any, userId);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case 'remember_search_relationship':
|
|
155
|
+
result = await handleSearchRelationship(args as any, userId);
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case 'remember_delete_relationship':
|
|
159
|
+
result = await handleDeleteRelationship(args as any, userId);
|
|
160
|
+
break;
|
|
161
|
+
|
|
116
162
|
default:
|
|
117
163
|
throw new McpError(
|
|
118
164
|
ErrorCode.MethodNotFound,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remember_create_relationship tool
|
|
3
|
+
* Create a relationship connecting 2...N memories
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Relationship, MemoryContext } from '../types/memory.js';
|
|
7
|
+
import { ensureMemoryCollection, getMemoryCollection } from '../weaviate/schema.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tool definition for remember_create_relationship
|
|
12
|
+
*/
|
|
13
|
+
export const createRelationshipTool = {
|
|
14
|
+
name: 'remember_create_relationship',
|
|
15
|
+
description: `Create a relationship connecting 2 or more memories.
|
|
16
|
+
|
|
17
|
+
Relationships describe how memories are connected with free-form types.
|
|
18
|
+
Each relationship has an observation (description), strength, and confidence.
|
|
19
|
+
Bidirectional: connected memories are automatically updated with the relationship ID.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
- "The Yosemite trip inspired my Sequoia planning"
|
|
23
|
+
- "My tent purchase was caused by the camping trip"
|
|
24
|
+
- "This recipe contradicts my previous cooking notes"
|
|
25
|
+
`,
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
memory_ids: {
|
|
30
|
+
type: 'array',
|
|
31
|
+
items: { type: 'string' },
|
|
32
|
+
description: 'Array of 2 or more memory IDs to connect',
|
|
33
|
+
minItems: 2,
|
|
34
|
+
},
|
|
35
|
+
relationship_type: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Type of relationship (free-form): "inspired_by", "contradicts", "caused_by", "related_to", etc.',
|
|
38
|
+
},
|
|
39
|
+
observation: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Description of the connection (will be vectorized for semantic search)',
|
|
42
|
+
},
|
|
43
|
+
strength: {
|
|
44
|
+
type: 'number',
|
|
45
|
+
description: 'Strength of the relationship (0-1, default: 0.5)',
|
|
46
|
+
minimum: 0,
|
|
47
|
+
maximum: 1,
|
|
48
|
+
},
|
|
49
|
+
confidence: {
|
|
50
|
+
type: 'number',
|
|
51
|
+
description: 'Confidence in this relationship (0-1, default: 0.8)',
|
|
52
|
+
minimum: 0,
|
|
53
|
+
maximum: 1,
|
|
54
|
+
},
|
|
55
|
+
tags: {
|
|
56
|
+
type: 'array',
|
|
57
|
+
items: { type: 'string' },
|
|
58
|
+
description: 'Tags for organization',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: ['memory_ids', 'relationship_type', 'observation'],
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create relationship arguments
|
|
67
|
+
*/
|
|
68
|
+
export interface CreateRelationshipArgs {
|
|
69
|
+
memory_ids: string[];
|
|
70
|
+
relationship_type: string;
|
|
71
|
+
observation: string;
|
|
72
|
+
strength?: number;
|
|
73
|
+
confidence?: number;
|
|
74
|
+
tags?: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create relationship result
|
|
79
|
+
*/
|
|
80
|
+
export interface CreateRelationshipResult {
|
|
81
|
+
relationship_id: string;
|
|
82
|
+
memory_ids: string[];
|
|
83
|
+
relationship_type: string;
|
|
84
|
+
created_at: string;
|
|
85
|
+
message: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Handle remember_create_relationship tool
|
|
90
|
+
*/
|
|
91
|
+
export async function handleCreateRelationship(
|
|
92
|
+
args: CreateRelationshipArgs,
|
|
93
|
+
userId: string,
|
|
94
|
+
context?: Partial<MemoryContext>
|
|
95
|
+
): Promise<string> {
|
|
96
|
+
try {
|
|
97
|
+
logger.info('Creating relationship', {
|
|
98
|
+
userId,
|
|
99
|
+
type: args.relationship_type,
|
|
100
|
+
memoryCount: args.memory_ids.length
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Validate memory_ids count
|
|
104
|
+
if (args.memory_ids.length < 2) {
|
|
105
|
+
throw new Error('At least 2 memory IDs are required to create a relationship');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Ensure collection exists
|
|
109
|
+
await ensureMemoryCollection(userId);
|
|
110
|
+
const collection = getMemoryCollection(userId);
|
|
111
|
+
|
|
112
|
+
// Verify all memories exist and belong to user
|
|
113
|
+
const memoryChecks = await Promise.all(
|
|
114
|
+
args.memory_ids.map(async (memoryId) => {
|
|
115
|
+
try {
|
|
116
|
+
const memory = await collection.query.fetchObjectById(memoryId, {
|
|
117
|
+
returnProperties: ['user_id', 'doc_type', 'relationships'],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!memory) {
|
|
121
|
+
return { memoryId, error: 'Memory not found' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (memory.properties.user_id !== userId) {
|
|
125
|
+
return { memoryId, error: 'Unauthorized: Memory belongs to another user' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (memory.properties.doc_type !== 'memory') {
|
|
129
|
+
return { memoryId, error: 'Cannot create relationship with non-memory document' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
memoryId,
|
|
134
|
+
memory,
|
|
135
|
+
relationships: (memory.properties.relationships as string[]) || []
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return { memoryId, error: `Failed to fetch memory: ${error}` };
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Check for errors
|
|
144
|
+
const errors = memoryChecks.filter(check => check.error);
|
|
145
|
+
if (errors.length > 0) {
|
|
146
|
+
const errorMessages = errors.map(e => `${e.memoryId}: ${e.error}`).join('; ');
|
|
147
|
+
throw new Error(`Memory validation failed: ${errorMessages}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Build relationship object
|
|
151
|
+
const now = new Date().toISOString();
|
|
152
|
+
const relationship: Omit<Relationship, 'id'> = {
|
|
153
|
+
// Core identity
|
|
154
|
+
user_id: userId,
|
|
155
|
+
doc_type: 'relationship',
|
|
156
|
+
|
|
157
|
+
// Connection
|
|
158
|
+
memory_ids: args.memory_ids,
|
|
159
|
+
relationship_type: args.relationship_type,
|
|
160
|
+
|
|
161
|
+
// Observation
|
|
162
|
+
observation: args.observation,
|
|
163
|
+
strength: args.strength ?? 0.5,
|
|
164
|
+
confidence: args.confidence ?? 0.8,
|
|
165
|
+
|
|
166
|
+
// Context
|
|
167
|
+
context: {
|
|
168
|
+
timestamp: now,
|
|
169
|
+
source: {
|
|
170
|
+
type: 'api',
|
|
171
|
+
platform: 'mcp',
|
|
172
|
+
},
|
|
173
|
+
summary: context?.summary || 'Relationship created via MCP',
|
|
174
|
+
conversation_id: context?.conversation_id,
|
|
175
|
+
...context,
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Metadata
|
|
179
|
+
created_at: now,
|
|
180
|
+
updated_at: now,
|
|
181
|
+
version: 1,
|
|
182
|
+
tags: args.tags || [],
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Insert relationship into Weaviate
|
|
186
|
+
const relationshipId = await collection.data.insert(relationship as any);
|
|
187
|
+
|
|
188
|
+
logger.info('Relationship created, updating connected memories', {
|
|
189
|
+
relationshipId,
|
|
190
|
+
userId
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Update all connected memories with bidirectional reference
|
|
194
|
+
const updatePromises = memoryChecks
|
|
195
|
+
.filter(check => !check.error && check.memory)
|
|
196
|
+
.map(async (check) => {
|
|
197
|
+
try {
|
|
198
|
+
const existingRelationships = check.relationships || [];
|
|
199
|
+
const updatedRelationships = [...existingRelationships, relationshipId];
|
|
200
|
+
|
|
201
|
+
await collection.data.update({
|
|
202
|
+
id: check.memoryId,
|
|
203
|
+
properties: {
|
|
204
|
+
relationships: updatedRelationships,
|
|
205
|
+
updated_at: now,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return { memoryId: check.memoryId, success: true };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
logger.warn(`Failed to update memory ${check.memoryId} with relationship:`, error);
|
|
212
|
+
return { memoryId: check.memoryId, success: false, error };
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const updateResults = await Promise.all(updatePromises);
|
|
217
|
+
const failedUpdates = updateResults.filter(r => !r.success);
|
|
218
|
+
|
|
219
|
+
if (failedUpdates.length > 0) {
|
|
220
|
+
logger.warn('Some memory updates failed', { failedUpdates });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
logger.info('Relationship created successfully', {
|
|
224
|
+
relationshipId,
|
|
225
|
+
userId,
|
|
226
|
+
updatedMemories: updateResults.filter(r => r.success).length
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const response: CreateRelationshipResult = {
|
|
230
|
+
relationship_id: relationshipId,
|
|
231
|
+
memory_ids: args.memory_ids,
|
|
232
|
+
relationship_type: args.relationship_type,
|
|
233
|
+
created_at: now,
|
|
234
|
+
message: `Relationship created successfully with ID: ${relationshipId}. Connected ${args.memory_ids.length} memories.`,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return JSON.stringify(response, null, 2);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
logger.error('Failed to create relationship:', error);
|
|
240
|
+
throw new Error(`Failed to create relationship: ${error instanceof Error ? error.message : String(error)}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remember_delete_relationship tool
|
|
3
|
+
* Delete a relationship and clean up references in connected memories
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getMemoryCollection } from '../weaviate/schema.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tool definition for remember_delete_relationship
|
|
11
|
+
*/
|
|
12
|
+
export const deleteRelationshipTool = {
|
|
13
|
+
name: 'remember_delete_relationship',
|
|
14
|
+
description: `Delete a relationship from your collection.
|
|
15
|
+
|
|
16
|
+
Automatically removes the relationship reference from all connected memories.
|
|
17
|
+
This action cannot be undone.
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
- "Delete that inspiration relationship"
|
|
21
|
+
- "Remove the connection between those memories"
|
|
22
|
+
`,
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
relationship_id: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'ID of the relationship to delete',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
required: ['relationship_id'],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Delete relationship arguments
|
|
37
|
+
*/
|
|
38
|
+
export interface DeleteRelationshipArgs {
|
|
39
|
+
relationship_id: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Delete relationship result
|
|
44
|
+
*/
|
|
45
|
+
export interface DeleteRelationshipResult {
|
|
46
|
+
relationship_id: string;
|
|
47
|
+
deleted: boolean;
|
|
48
|
+
memories_updated: number;
|
|
49
|
+
message: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Handle remember_delete_relationship tool
|
|
54
|
+
*/
|
|
55
|
+
export async function handleDeleteRelationship(
|
|
56
|
+
args: DeleteRelationshipArgs,
|
|
57
|
+
userId: string
|
|
58
|
+
): Promise<string> {
|
|
59
|
+
try {
|
|
60
|
+
logger.info('Deleting relationship', { userId, relationshipId: args.relationship_id });
|
|
61
|
+
|
|
62
|
+
const collection = getMemoryCollection(userId);
|
|
63
|
+
|
|
64
|
+
// Get relationship to verify ownership and get connected memory IDs
|
|
65
|
+
const relationship = await collection.query.fetchObjectById(args.relationship_id, {
|
|
66
|
+
returnProperties: ['user_id', 'doc_type', 'memory_ids', 'relationship_type'],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!relationship) {
|
|
70
|
+
throw new Error(`Relationship not found: ${args.relationship_id}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Verify ownership
|
|
74
|
+
if (relationship.properties.user_id !== userId) {
|
|
75
|
+
throw new Error('Unauthorized: Cannot delete another user\'s relationship');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Verify it's a relationship (not a memory)
|
|
79
|
+
if (relationship.properties.doc_type !== 'relationship') {
|
|
80
|
+
throw new Error('Cannot delete memories using this tool. Use remember_delete_memory instead.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const memoryIds = (relationship.properties.memory_ids as string[]) || [];
|
|
84
|
+
let memoriesUpdated = 0;
|
|
85
|
+
|
|
86
|
+
// Remove relationship reference from all connected memories
|
|
87
|
+
if (memoryIds.length > 0) {
|
|
88
|
+
logger.info('Cleaning up relationship references from connected memories', {
|
|
89
|
+
relationshipId: args.relationship_id,
|
|
90
|
+
memoryCount: memoryIds.length,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const updatePromises = memoryIds.map(async (memoryId) => {
|
|
94
|
+
try {
|
|
95
|
+
// Get current memory to read relationships array
|
|
96
|
+
const memory = await collection.query.fetchObjectById(memoryId, {
|
|
97
|
+
returnProperties: ['relationships', 'doc_type'],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!memory || memory.properties.doc_type !== 'memory') {
|
|
101
|
+
logger.warn(`Memory ${memoryId} not found or not a memory, skipping cleanup`);
|
|
102
|
+
return { memoryId, success: false };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const currentRelationships = (memory.properties.relationships as string[]) || [];
|
|
106
|
+
const updatedRelationships = currentRelationships.filter(
|
|
107
|
+
(relId) => relId !== args.relationship_id
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Only update if there was a change
|
|
111
|
+
if (updatedRelationships.length !== currentRelationships.length) {
|
|
112
|
+
await collection.data.update({
|
|
113
|
+
id: memoryId,
|
|
114
|
+
properties: {
|
|
115
|
+
relationships: updatedRelationships,
|
|
116
|
+
updated_at: new Date().toISOString(),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return { memoryId, success: true };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { memoryId, success: false };
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.warn(`Failed to update memory ${memoryId}:`, error);
|
|
125
|
+
return { memoryId, success: false, error };
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const updateResults = await Promise.all(updatePromises);
|
|
130
|
+
memoriesUpdated = updateResults.filter((r) => r.success).length;
|
|
131
|
+
|
|
132
|
+
logger.info('Memory cleanup completed', {
|
|
133
|
+
relationshipId: args.relationship_id,
|
|
134
|
+
memoriesUpdated,
|
|
135
|
+
totalMemories: memoryIds.length,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Delete the relationship
|
|
140
|
+
await collection.data.deleteById(args.relationship_id);
|
|
141
|
+
|
|
142
|
+
logger.info('Relationship deleted successfully', {
|
|
143
|
+
userId,
|
|
144
|
+
relationshipId: args.relationship_id,
|
|
145
|
+
memoriesUpdated,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const result: DeleteRelationshipResult = {
|
|
149
|
+
relationship_id: args.relationship_id,
|
|
150
|
+
deleted: true,
|
|
151
|
+
memories_updated: memoriesUpdated,
|
|
152
|
+
message: `Relationship deleted successfully${memoriesUpdated > 0 ? ` (${memoriesUpdated} memories updated)` : ''}`,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return JSON.stringify(result, null, 2);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error('Failed to delete relationship:', error);
|
|
158
|
+
throw new Error(`Failed to delete relationship: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* remember_find_similar tool
|
|
3
|
+
* Find similar memories using vector similarity search
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Memory } from '../types/memory.js';
|
|
7
|
+
import { getMemoryCollection } from '../weaviate/schema.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tool definition for remember_find_similar
|
|
12
|
+
*/
|
|
13
|
+
export const findSimilarTool = {
|
|
14
|
+
name: 'remember_find_similar',
|
|
15
|
+
description: `Find memories similar to a given memory or text using vector similarity.
|
|
16
|
+
|
|
17
|
+
Uses pure semantic similarity (vector distance) to find related memories.
|
|
18
|
+
This is different from hybrid search - it finds memories with similar meaning,
|
|
19
|
+
even if they don't share keywords.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
- "Find memories similar to this camping trip note"
|
|
23
|
+
- "Show me memories related to this recipe"
|
|
24
|
+
- "What other memories are like this meeting note?"
|
|
25
|
+
`,
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
memory_id: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'ID of memory to find similar memories for (provide either memory_id or text)',
|
|
32
|
+
},
|
|
33
|
+
text: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Text to find similar memories for (provide either memory_id or text)',
|
|
36
|
+
},
|
|
37
|
+
limit: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
description: 'Maximum number of results. Default: 10',
|
|
40
|
+
minimum: 1,
|
|
41
|
+
maximum: 100,
|
|
42
|
+
default: 10,
|
|
43
|
+
},
|
|
44
|
+
min_similarity: {
|
|
45
|
+
type: 'number',
|
|
46
|
+
description: 'Minimum similarity score (0-1). Default: 0.7',
|
|
47
|
+
minimum: 0,
|
|
48
|
+
maximum: 1,
|
|
49
|
+
default: 0.7,
|
|
50
|
+
},
|
|
51
|
+
include_relationships: {
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
description: 'Include relationships in results. Default: false',
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find similar arguments
|
|
62
|
+
*/
|
|
63
|
+
export interface FindSimilarArgs {
|
|
64
|
+
memory_id?: string;
|
|
65
|
+
text?: string;
|
|
66
|
+
limit?: number;
|
|
67
|
+
min_similarity?: number;
|
|
68
|
+
include_relationships?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Similar memory result
|
|
73
|
+
*/
|
|
74
|
+
export interface SimilarMemory extends Partial<Memory> {
|
|
75
|
+
similarity: number; // 0-1 similarity score
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find similar result
|
|
80
|
+
*/
|
|
81
|
+
export interface FindSimilarResult {
|
|
82
|
+
query: {
|
|
83
|
+
memory_id?: string;
|
|
84
|
+
text?: string;
|
|
85
|
+
};
|
|
86
|
+
similar_memories: SimilarMemory[];
|
|
87
|
+
total: number;
|
|
88
|
+
min_similarity: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Handle remember_find_similar tool
|
|
93
|
+
*/
|
|
94
|
+
export async function handleFindSimilar(
|
|
95
|
+
args: FindSimilarArgs,
|
|
96
|
+
userId: string
|
|
97
|
+
): Promise<string> {
|
|
98
|
+
try {
|
|
99
|
+
logger.info('Finding similar memories', { userId, memoryId: args.memory_id, hasText: !!args.text });
|
|
100
|
+
|
|
101
|
+
// Validate input
|
|
102
|
+
if (!args.memory_id && !args.text) {
|
|
103
|
+
throw new Error('Either memory_id or text must be provided');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (args.memory_id && args.text) {
|
|
107
|
+
throw new Error('Provide either memory_id or text, not both');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const collection = getMemoryCollection(userId);
|
|
111
|
+
const limit = args.limit ?? 10;
|
|
112
|
+
const minSimilarity = args.min_similarity ?? 0.7;
|
|
113
|
+
|
|
114
|
+
let results: any;
|
|
115
|
+
|
|
116
|
+
if (args.memory_id) {
|
|
117
|
+
// Find similar to existing memory
|
|
118
|
+
// First get the memory to verify ownership
|
|
119
|
+
const memory = await collection.query.fetchObjectById(args.memory_id, {
|
|
120
|
+
returnProperties: ['user_id', 'doc_type', 'content'],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!memory) {
|
|
124
|
+
throw new Error(`Memory not found: ${args.memory_id}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Verify ownership
|
|
128
|
+
if (memory.properties.user_id !== userId) {
|
|
129
|
+
throw new Error('Unauthorized: Cannot access another user\'s memory');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Verify it's a memory
|
|
133
|
+
if (memory.properties.doc_type !== 'memory') {
|
|
134
|
+
throw new Error('Can only find similar memories for memory documents, not relationships');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Find similar using nearObject
|
|
138
|
+
results = await collection.query.nearObject(args.memory_id, {
|
|
139
|
+
limit: limit + 1, // +1 to exclude the source memory itself
|
|
140
|
+
distance: 1 - minSimilarity, // Convert similarity to distance
|
|
141
|
+
returnMetadata: ['distance'],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Filter out the source memory
|
|
145
|
+
results.objects = results.objects.filter((obj: any) => obj.uuid !== args.memory_id);
|
|
146
|
+
} else {
|
|
147
|
+
// Find similar to text
|
|
148
|
+
results = await collection.query.nearText(args.text!, {
|
|
149
|
+
limit: limit,
|
|
150
|
+
distance: 1 - minSimilarity,
|
|
151
|
+
returnMetadata: ['distance'],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Filter to only memories (not relationships) unless requested
|
|
156
|
+
if (!args.include_relationships) {
|
|
157
|
+
results.objects = results.objects.filter(
|
|
158
|
+
(obj: any) => obj.properties.doc_type === 'memory'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Format results with similarity scores
|
|
163
|
+
const similarMemories: SimilarMemory[] = results.objects.map((obj: any) => {
|
|
164
|
+
const similarity = 1 - (obj.metadata?.distance ?? 0); // Convert distance back to similarity
|
|
165
|
+
return {
|
|
166
|
+
id: obj.uuid,
|
|
167
|
+
...obj.properties,
|
|
168
|
+
similarity: Math.max(0, Math.min(1, similarity)), // Clamp to [0, 1]
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Sort by similarity (highest first)
|
|
173
|
+
similarMemories.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
174
|
+
|
|
175
|
+
// Limit results
|
|
176
|
+
const limitedResults = similarMemories.slice(0, limit);
|
|
177
|
+
|
|
178
|
+
logger.info('Similar memories found', {
|
|
179
|
+
userId,
|
|
180
|
+
query: args.memory_id || args.text,
|
|
181
|
+
results: limitedResults.length,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const result: FindSimilarResult = {
|
|
185
|
+
query: {
|
|
186
|
+
memory_id: args.memory_id,
|
|
187
|
+
text: args.text,
|
|
188
|
+
},
|
|
189
|
+
similar_memories: limitedResults,
|
|
190
|
+
total: limitedResults.length,
|
|
191
|
+
min_similarity: minSimilarity,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return JSON.stringify(result, null, 2);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
logger.error('Failed to find similar memories:', error);
|
|
197
|
+
throw new Error(`Failed to find similar memories: ${error instanceof Error ? error.message : String(error)}`);
|
|
198
|
+
}
|
|
199
|
+
}
|