neoagent 2.4.4-beta.0 → 2.4.4-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/docs/capabilities.md +16 -7
- package/docs/index.md +1 -0
- package/docs/security-boundaries.md +122 -0
- package/docs/supermemory-memory-review.md +852 -0
- package/flutter_app/lib/features/memory/views/retrieval_inspector_view.dart +128 -0
- package/flutter_app/lib/main.dart +3 -0
- package/flutter_app/lib/main_app_shell.dart +22 -0
- package/flutter_app/lib/main_controller.dart +36 -1
- package/flutter_app/lib/main_operations.dart +13 -0
- package/flutter_app/lib/main_security.dart +971 -0
- package/flutter_app/lib/main_settings.dart +61 -0
- package/flutter_app/lib/src/backend_client.dart +60 -3
- package/flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +2 -0
- package/flutter_app/pubspec.lock +32 -0
- package/flutter_app/pubspec.yaml +1 -0
- package/lib/schema_migrations.js +237 -0
- package/package.json +4 -2
- package/server/db/database.js +3 -0
- package/server/http/routes.js +2 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +86 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +80911 -79117
- package/server/routes/memory.js +39 -2
- package/server/routes/security.js +112 -0
- package/server/services/ai/engine.js +267 -10
- package/server/services/ai/systemPrompt.js +13 -2
- package/server/services/cli/shell_worker.js +135 -0
- package/server/services/cli/shell_worker_pool.js +125 -0
- package/server/services/manager.js +20 -1
- package/server/services/memory/consolidation.js +111 -0
- package/server/services/memory/embedding_index.js +175 -0
- package/server/services/memory/embeddings.js +22 -2
- package/server/services/memory/evaluation.js +187 -0
- package/server/services/memory/ingestion_chunking.js +191 -0
- package/server/services/memory/ingestion_documents.js +96 -26
- package/server/services/memory/intelligence.js +3 -1
- package/server/services/memory/manager.js +855 -40
- package/server/services/memory/policy.js +0 -40
- package/server/services/memory/retrieval_reasoning.js +191 -0
- package/server/services/runtime/manager.js +7 -0
- package/server/services/security/approval_gate_service.js +93 -0
- package/server/services/security/tool_categories.js +105 -0
- package/server/services/security/tool_policy_service.js +92 -0
- package/server/services/security/tool_security_hook.js +77 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { performance } = require('node:perf_hooks');
|
|
4
|
+
|
|
5
|
+
function mean(values) {
|
|
6
|
+
if (!values.length) return 0;
|
|
7
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function percentile(values, fraction) {
|
|
11
|
+
if (!values.length) return 0;
|
|
12
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
13
|
+
const index = Math.min(sorted.length - 1, Math.ceil(sorted.length * fraction) - 1);
|
|
14
|
+
return sorted[Math.max(0, index)];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function calculateRetrievalMetrics(results, relevantKeys, k) {
|
|
18
|
+
const returned = results.slice(0, k);
|
|
19
|
+
const relevance = returned.map((result) => (
|
|
20
|
+
relevantKeys.has(`memory:${result.id}`)
|
|
21
|
+
|| relevantKeys.has(`source:${result.sourceRef?.sourceId || ''}`)
|
|
22
|
+
? 1
|
|
23
|
+
: 0
|
|
24
|
+
));
|
|
25
|
+
const relevantRetrieved = relevance.reduce((sum, value) => sum + value, 0);
|
|
26
|
+
const totalRelevant = relevantKeys.size;
|
|
27
|
+
const precisionAtK = returned.length ? relevantRetrieved / returned.length : 0;
|
|
28
|
+
const recallAtK = totalRelevant ? relevantRetrieved / totalRelevant : 0;
|
|
29
|
+
const firstRelevant = relevance.indexOf(1);
|
|
30
|
+
const dcg = relevance.reduce(
|
|
31
|
+
(sum, value, index) => sum + value / Math.log2(index + 2),
|
|
32
|
+
0,
|
|
33
|
+
);
|
|
34
|
+
let idealDcg = 0;
|
|
35
|
+
for (let index = 0; index < Math.min(totalRelevant, returned.length); index += 1) {
|
|
36
|
+
idealDcg += 1 / Math.log2(index + 2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
hitAtK: relevantRetrieved > 0 ? 1 : 0,
|
|
41
|
+
precisionAtK,
|
|
42
|
+
recallAtK,
|
|
43
|
+
f1AtK: precisionAtK + recallAtK
|
|
44
|
+
? (2 * precisionAtK * recallAtK) / (precisionAtK + recallAtK)
|
|
45
|
+
: 0,
|
|
46
|
+
mrr: firstRelevant >= 0 ? 1 / (firstRelevant + 1) : 0,
|
|
47
|
+
ndcg: idealDcg ? dcg / idealDcg : 0,
|
|
48
|
+
relevantRetrieved,
|
|
49
|
+
totalRelevant,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeQuestions(dataset) {
|
|
54
|
+
const questions = Array.isArray(dataset)
|
|
55
|
+
? dataset
|
|
56
|
+
: dataset?.questions || dataset?.queries;
|
|
57
|
+
if (!Array.isArray(questions) || !questions.length) {
|
|
58
|
+
throw new Error('Dataset must contain a non-empty questions or queries array.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return questions.map((question, index) => {
|
|
62
|
+
const query = String(question.query || question.question || '').trim();
|
|
63
|
+
const relevantMemoryIds = Array.isArray(question.relevantMemoryIds)
|
|
64
|
+
? question.relevantMemoryIds.map(String).filter(Boolean)
|
|
65
|
+
: [];
|
|
66
|
+
const relevantSourceIds = Array.isArray(question.relevantSourceIds)
|
|
67
|
+
? question.relevantSourceIds.map(String).filter(Boolean)
|
|
68
|
+
: Array.isArray(question.haystackSessionIds)
|
|
69
|
+
? question.haystackSessionIds.map(String).filter(Boolean)
|
|
70
|
+
: [];
|
|
71
|
+
if (!query) throw new Error(`Question ${index + 1} has no query.`);
|
|
72
|
+
if (!relevantMemoryIds.length && !relevantSourceIds.length) {
|
|
73
|
+
throw new Error(`Question ${index + 1} has no relevant memory or source IDs.`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
id: String(question.id || question.questionId || index + 1),
|
|
77
|
+
query,
|
|
78
|
+
category: String(question.category || question.questionType || 'uncategorized'),
|
|
79
|
+
relevantMemoryIds,
|
|
80
|
+
relevantSourceIds,
|
|
81
|
+
validAt: question.validAt || null,
|
|
82
|
+
knownAt: question.knownAt || null,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function aggregateEvaluations(evaluations, k) {
|
|
88
|
+
const metricNames = ['hitAtK', 'precisionAtK', 'recallAtK', 'f1AtK', 'mrr', 'ndcg'];
|
|
89
|
+
const retrieval = { k };
|
|
90
|
+
for (const metric of metricNames) {
|
|
91
|
+
retrieval[metric] = mean(evaluations.map((evaluation) => evaluation.metrics[metric]));
|
|
92
|
+
}
|
|
93
|
+
const latencies = evaluations.map((evaluation) => evaluation.latencyMs);
|
|
94
|
+
const contextTokens = evaluations.map((evaluation) => evaluation.contextTokensEstimate);
|
|
95
|
+
const candidateCounts = evaluations.map((evaluation) => evaluation.candidateCount);
|
|
96
|
+
return {
|
|
97
|
+
questions: evaluations.length,
|
|
98
|
+
retrieval,
|
|
99
|
+
latencyMs: {
|
|
100
|
+
mean: mean(latencies),
|
|
101
|
+
median: percentile(latencies, 0.5),
|
|
102
|
+
p95: percentile(latencies, 0.95),
|
|
103
|
+
p99: percentile(latencies, 0.99),
|
|
104
|
+
max: latencies.length ? Math.max(...latencies) : 0,
|
|
105
|
+
},
|
|
106
|
+
contextTokensEstimate: {
|
|
107
|
+
mean: mean(contextTokens),
|
|
108
|
+
p95: percentile(contextTokens, 0.95),
|
|
109
|
+
max: contextTokens.length ? Math.max(...contextTokens) : 0,
|
|
110
|
+
},
|
|
111
|
+
candidateCount: {
|
|
112
|
+
mean: mean(candidateCounts),
|
|
113
|
+
p95: percentile(candidateCounts, 0.95),
|
|
114
|
+
max: candidateCounts.length ? Math.max(...candidateCounts) : 0,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function runRetrievalBenchmark({
|
|
120
|
+
memoryManager,
|
|
121
|
+
userId,
|
|
122
|
+
agentId,
|
|
123
|
+
dataset,
|
|
124
|
+
k = 15,
|
|
125
|
+
}) {
|
|
126
|
+
const limit = Math.max(1, Math.min(Number(k) || 15, 50));
|
|
127
|
+
const questions = normalizeQuestions(dataset);
|
|
128
|
+
const evaluations = [];
|
|
129
|
+
|
|
130
|
+
for (const question of questions) {
|
|
131
|
+
const relevantKeys = new Set([
|
|
132
|
+
...question.relevantMemoryIds.map((id) => `memory:${id}`),
|
|
133
|
+
...question.relevantSourceIds.map((id) => `source:${id}`),
|
|
134
|
+
]);
|
|
135
|
+
const startedAt = performance.now();
|
|
136
|
+
const results = await memoryManager.recallMemory(userId, question.query, limit, {
|
|
137
|
+
agentId,
|
|
138
|
+
validAt: question.validAt,
|
|
139
|
+
knownAt: question.knownAt,
|
|
140
|
+
});
|
|
141
|
+
const latencyMs = performance.now() - startedAt;
|
|
142
|
+
const contextText = results
|
|
143
|
+
.map((result) => result.summary || result.content || '')
|
|
144
|
+
.join('\n');
|
|
145
|
+
|
|
146
|
+
evaluations.push({
|
|
147
|
+
id: question.id,
|
|
148
|
+
category: question.category,
|
|
149
|
+
query: question.query,
|
|
150
|
+
latencyMs,
|
|
151
|
+
contextTokensEstimate: Math.ceil(contextText.length / 4),
|
|
152
|
+
candidateCount: Math.max(
|
|
153
|
+
0,
|
|
154
|
+
...results.map((result) => Number(result.scoreBreakdown?.candidateCount) || 0),
|
|
155
|
+
),
|
|
156
|
+
retrievedMemoryIds: results.map((result) => result.id),
|
|
157
|
+
retrievedSourceIds: results
|
|
158
|
+
.map((result) => result.sourceRef?.sourceId)
|
|
159
|
+
.filter(Boolean),
|
|
160
|
+
metrics: calculateRetrievalMetrics(results, relevantKeys, limit),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const categories = {};
|
|
165
|
+
for (const category of new Set(evaluations.map((evaluation) => evaluation.category))) {
|
|
166
|
+
categories[category] = aggregateEvaluations(
|
|
167
|
+
evaluations.filter((evaluation) => evaluation.category === category),
|
|
168
|
+
limit,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
generatedAt: new Date().toISOString(),
|
|
174
|
+
dataset: String(dataset?.name || 'unnamed'),
|
|
175
|
+
userId,
|
|
176
|
+
agentId,
|
|
177
|
+
...aggregateEvaluations(evaluations, limit),
|
|
178
|
+
categories,
|
|
179
|
+
evaluations,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = {
|
|
184
|
+
calculateRetrievalMetrics,
|
|
185
|
+
normalizeQuestions,
|
|
186
|
+
runRetrievalBenchmark,
|
|
187
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
|
|
5
|
+
const TARGET_CHARS = 1400;
|
|
6
|
+
const MAX_CHARS = 2200;
|
|
7
|
+
|
|
8
|
+
function contentHash(content) {
|
|
9
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function messageBlocks(document) {
|
|
13
|
+
const messages = Array.isArray(document?.payload?.messages)
|
|
14
|
+
? document.payload.messages
|
|
15
|
+
: [];
|
|
16
|
+
return messages
|
|
17
|
+
.map((message) => {
|
|
18
|
+
const speaker = String(message.speaker || message.role || message.author || '').trim();
|
|
19
|
+
const timestamp = String(message.timestamp || message.createdAt || '').trim();
|
|
20
|
+
const content = String(message.content || message.text || message.body || '').trim();
|
|
21
|
+
if (!content) return null;
|
|
22
|
+
const prefix = [timestamp, speaker].filter(Boolean).join(' ');
|
|
23
|
+
return {
|
|
24
|
+
content,
|
|
25
|
+
rendered: prefix ? `${prefix}\n${content}` : content,
|
|
26
|
+
messageId: String(message.id || message.messageId || '').trim() || null,
|
|
27
|
+
};
|
|
28
|
+
})
|
|
29
|
+
.filter(Boolean);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function rawStructuralBlocks(text) {
|
|
33
|
+
return String(text || '')
|
|
34
|
+
.replace(/\r\n?/g, '\n')
|
|
35
|
+
.split(/\n{2,}|(?=^#{1,6}\s)/gm)
|
|
36
|
+
.map((block) => block.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function rawEmailBlocks(text) {
|
|
41
|
+
return String(text || '')
|
|
42
|
+
.replace(/\r\n?/g, '\n')
|
|
43
|
+
.split(/\n(?=On\s.*wrote:|\s*>\s|-----Original Message-----|________________________________)/g)
|
|
44
|
+
.map((block) => block.trim())
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function rawCodeBlocks(text) {
|
|
49
|
+
return String(text || '')
|
|
50
|
+
.replace(/\r\n?/g, '\n')
|
|
51
|
+
.split(/\n(?=(?:export\s+)?(?:class|function|const|let|var|import)\s)/g)
|
|
52
|
+
.map((block) => block.trim())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function findBlockPositions(originalContent, blocks) {
|
|
57
|
+
let searchFrom = 0;
|
|
58
|
+
return blocks.map((block) => {
|
|
59
|
+
const pos = originalContent.indexOf(block, searchFrom);
|
|
60
|
+
const charStart = pos >= 0 ? pos : searchFrom;
|
|
61
|
+
const charEnd = charStart + block.length;
|
|
62
|
+
searchFrom = charEnd;
|
|
63
|
+
return { content: block, charStart, charEnd };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function splitOversizedWithPos({ content, charStart }) {
|
|
68
|
+
if (content.length <= MAX_CHARS) {
|
|
69
|
+
return [{ content, charStart, charEnd: charStart + content.length }];
|
|
70
|
+
}
|
|
71
|
+
const parts = [];
|
|
72
|
+
let offset = 0;
|
|
73
|
+
while (offset < content.length) {
|
|
74
|
+
const slice = content.slice(offset, offset + MAX_CHARS);
|
|
75
|
+
parts.push({
|
|
76
|
+
content: slice,
|
|
77
|
+
charStart: charStart + offset,
|
|
78
|
+
charEnd: charStart + offset + slice.length,
|
|
79
|
+
});
|
|
80
|
+
offset += MAX_CHARS;
|
|
81
|
+
}
|
|
82
|
+
return parts;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function packBlocksWithPositions(blocksWithPos) {
|
|
86
|
+
const chunks = [];
|
|
87
|
+
let group = [];
|
|
88
|
+
let groupLen = 0;
|
|
89
|
+
|
|
90
|
+
for (const bwp of blocksWithPos.flatMap(splitOversizedWithPos)) {
|
|
91
|
+
const addLen = (groupLen ? 2 : 0) + bwp.content.length;
|
|
92
|
+
if (group.length && groupLen + addLen > TARGET_CHARS) {
|
|
93
|
+
chunks.push(group);
|
|
94
|
+
group = [bwp];
|
|
95
|
+
groupLen = bwp.content.length;
|
|
96
|
+
} else {
|
|
97
|
+
group.push(bwp);
|
|
98
|
+
groupLen += addLen;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (group.length) chunks.push(group);
|
|
102
|
+
|
|
103
|
+
return chunks.map((g) => ({
|
|
104
|
+
content: g.map((b) => b.content).join('\n\n'),
|
|
105
|
+
charStart: g[0].charStart,
|
|
106
|
+
charEnd: g[g.length - 1].charEnd,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function chunkDocument(document) {
|
|
111
|
+
const content = String(document?.content || '').trim();
|
|
112
|
+
if (!content) return [];
|
|
113
|
+
|
|
114
|
+
const messages = messageBlocks(document);
|
|
115
|
+
if (messages.length) {
|
|
116
|
+
let searchOffset = 0;
|
|
117
|
+
return messages.map((message, chunkIndex) => {
|
|
118
|
+
let charStart = content.indexOf(message.content, searchOffset);
|
|
119
|
+
if (charStart < 0) charStart = searchOffset;
|
|
120
|
+
const charEnd = Math.min(content.length, charStart + message.content.length);
|
|
121
|
+
searchOffset = charEnd;
|
|
122
|
+
return {
|
|
123
|
+
chunkIndex,
|
|
124
|
+
charStart,
|
|
125
|
+
charEnd,
|
|
126
|
+
content: message.rendered,
|
|
127
|
+
contentHash: contentHash(message.rendered),
|
|
128
|
+
metadata: {
|
|
129
|
+
boundary: 'message',
|
|
130
|
+
messageId: message.messageId,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const sourceType = String(document?.sourceType || document?.normalizedType || '').toLowerCase();
|
|
137
|
+
const title = String(document?.title || '').toLowerCase();
|
|
138
|
+
|
|
139
|
+
let rawBlocks;
|
|
140
|
+
let boundaryType;
|
|
141
|
+
|
|
142
|
+
if (sourceType.includes('email') || title.endsWith('.eml') || title.includes('message')) {
|
|
143
|
+
rawBlocks = rawEmailBlocks(content);
|
|
144
|
+
boundaryType = 'email_thread';
|
|
145
|
+
} else if (
|
|
146
|
+
sourceType.includes('code') ||
|
|
147
|
+
title.endsWith('.js') ||
|
|
148
|
+
title.endsWith('.ts') ||
|
|
149
|
+
title.endsWith('.py') ||
|
|
150
|
+
title.endsWith('.java')
|
|
151
|
+
) {
|
|
152
|
+
rawBlocks = rawCodeBlocks(content);
|
|
153
|
+
boundaryType = 'code_structure';
|
|
154
|
+
} else {
|
|
155
|
+
rawBlocks = rawStructuralBlocks(content);
|
|
156
|
+
boundaryType = 'structural';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const blocksWithPos = findBlockPositions(content, rawBlocks);
|
|
160
|
+
const packed = packBlocksWithPositions(blocksWithPos);
|
|
161
|
+
|
|
162
|
+
return packed.map((chunk, chunkIndex) => ({
|
|
163
|
+
chunkIndex,
|
|
164
|
+
charStart: chunk.charStart,
|
|
165
|
+
charEnd: chunk.charEnd,
|
|
166
|
+
content: chunk.content,
|
|
167
|
+
contentHash: contentHash(chunk.content),
|
|
168
|
+
metadata: { boundary: boundaryType },
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function overlapWindowChunks(chunks) {
|
|
173
|
+
if (chunks.length < 2) return [];
|
|
174
|
+
return chunks.slice(0, -1).map((chunk, i) => {
|
|
175
|
+
const next = chunks[i + 1];
|
|
176
|
+
const combined = `${chunk.content}\n\n${next.content}`;
|
|
177
|
+
return {
|
|
178
|
+
chunkIndex: -(i + 1),
|
|
179
|
+
charStart: chunk.charStart,
|
|
180
|
+
charEnd: next.charEnd,
|
|
181
|
+
content: combined.slice(0, MAX_CHARS * 2),
|
|
182
|
+
contentHash: contentHash(combined),
|
|
183
|
+
metadata: { boundary: 'overlap_window', baseChunkIndex: i },
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
chunkDocument,
|
|
190
|
+
overlapWindowChunks,
|
|
191
|
+
};
|
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
parseJsonObject,
|
|
12
12
|
safeTrim,
|
|
13
13
|
} = require('./ingestion_support');
|
|
14
|
+
const { chunkDocument, overlapWindowChunks } = require('./ingestion_chunking');
|
|
14
15
|
|
|
15
16
|
async function ingestDocuments(service, userId, documents = [], options = {}) {
|
|
16
17
|
const agentId = resolveAgentId(userId, options.agentId || options.agent_id || null);
|
|
@@ -50,35 +51,104 @@ async function ingestDocuments(service, userId, documents = [], options = {}) {
|
|
|
50
51
|
{ agentId },
|
|
51
52
|
);
|
|
52
53
|
documentIds.push(documentId);
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
const retainedChunkIds = [];
|
|
55
|
+
const chunks = chunkDocument(document);
|
|
56
|
+
const savedChunkMemoryIds = [];
|
|
57
|
+
for (const chunk of chunks) {
|
|
58
|
+
const memoryId = await service.memoryManager.saveMemory(
|
|
59
|
+
userId,
|
|
60
|
+
`${document.title}\n${chunk.content}`,
|
|
61
|
+
SOURCE_MEMORY_CATEGORIES[document.normalizedType]
|
|
62
|
+
|| SOURCE_MEMORY_CATEGORIES[document.sourceType]
|
|
63
|
+
|| 'episodic',
|
|
64
|
+
document.salience,
|
|
65
|
+
{
|
|
66
|
+
agentId,
|
|
67
|
+
staleAfterDays: getFreshnessPolicy(document.sourceType).staleAfterDays,
|
|
68
|
+
sourceRef: {
|
|
69
|
+
sourceType: 'memory_ingestion',
|
|
70
|
+
sourceId: document.externalObjectId,
|
|
71
|
+
sourceLabel: document.title,
|
|
72
|
+
},
|
|
73
|
+
scope: {
|
|
74
|
+
scopeType: 'agent',
|
|
75
|
+
scopeId: agentId,
|
|
76
|
+
},
|
|
77
|
+
metadata: {
|
|
78
|
+
ingestionJobId: jobId,
|
|
79
|
+
ingestionDocumentId: documentId,
|
|
80
|
+
providerKey: document.providerKey || null,
|
|
81
|
+
connectionId: document.connectionId || null,
|
|
82
|
+
sourceType: document.sourceType,
|
|
83
|
+
trustLevel: 'external_source',
|
|
84
|
+
chunkIndex: chunk.chunkIndex,
|
|
85
|
+
sourceSpan: {
|
|
86
|
+
charStart: chunk.charStart,
|
|
87
|
+
charEnd: chunk.charEnd,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
67
90
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
);
|
|
92
|
+
if (!memoryId) continue;
|
|
93
|
+
memoryIds.push(memoryId);
|
|
94
|
+
savedChunkMemoryIds.push(memoryId);
|
|
95
|
+
retainedChunkIds.push(service.memoryManager.replaceSourceChunk(
|
|
96
|
+
documentId,
|
|
97
|
+
chunk,
|
|
98
|
+
memoryId,
|
|
99
|
+
{
|
|
100
|
+
userId,
|
|
101
|
+
agentId,
|
|
102
|
+
sourceTimestamp: document.sourceTimestamp,
|
|
103
|
+
metadata: {
|
|
104
|
+
ingestionJobId: jobId,
|
|
105
|
+
externalObjectId: document.externalObjectId,
|
|
106
|
+
},
|
|
71
107
|
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const overlapChunks = overlapWindowChunks(chunks);
|
|
112
|
+
for (const window of overlapChunks) {
|
|
113
|
+
const windowMemoryId = await service.memoryManager.saveMemory(
|
|
114
|
+
userId,
|
|
115
|
+
`${document.title}\n${window.content}`,
|
|
116
|
+
SOURCE_MEMORY_CATEGORIES[document.normalizedType]
|
|
117
|
+
|| SOURCE_MEMORY_CATEGORIES[document.sourceType]
|
|
118
|
+
|| 'episodic',
|
|
119
|
+
document.salience,
|
|
120
|
+
{
|
|
121
|
+
agentId,
|
|
122
|
+
staleAfterDays: getFreshnessPolicy(document.sourceType).staleAfterDays,
|
|
123
|
+
sourceRef: {
|
|
124
|
+
sourceType: 'memory_ingestion',
|
|
125
|
+
sourceId: document.externalObjectId,
|
|
126
|
+
sourceLabel: document.title,
|
|
127
|
+
},
|
|
128
|
+
scope: {
|
|
129
|
+
scopeType: 'agent',
|
|
130
|
+
scopeId: agentId,
|
|
131
|
+
},
|
|
132
|
+
metadata: {
|
|
133
|
+
ingestionJobId: jobId,
|
|
134
|
+
ingestionDocumentId: documentId,
|
|
135
|
+
providerKey: document.providerKey || null,
|
|
136
|
+
connectionId: document.connectionId || null,
|
|
137
|
+
sourceType: document.sourceType,
|
|
138
|
+
trustLevel: 'external_source',
|
|
139
|
+
chunkIndex: window.chunkIndex,
|
|
140
|
+
sourceSpan: {
|
|
141
|
+
charStart: window.charStart,
|
|
142
|
+
charEnd: window.charEnd,
|
|
143
|
+
},
|
|
144
|
+
isOverlapWindow: true,
|
|
145
|
+
},
|
|
78
146
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
147
|
+
);
|
|
148
|
+
if (windowMemoryId) savedChunkMemoryIds.push(windowMemoryId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
service.memoryManager.pruneSourceChunks(documentId, retainedChunkIds);
|
|
82
152
|
}
|
|
83
153
|
|
|
84
154
|
service.memoryManager.recordIngestionJob(userId, {
|
|
@@ -157,6 +157,7 @@ function scoreMemoryCandidate({
|
|
|
157
157
|
entityRank = -1,
|
|
158
158
|
baseScore = 0,
|
|
159
159
|
importance = 5,
|
|
160
|
+
confidence = 0.7,
|
|
160
161
|
accessCount = 0,
|
|
161
162
|
freshness = 1,
|
|
162
163
|
} = {}) {
|
|
@@ -166,8 +167,9 @@ function scoreMemoryCandidate({
|
|
|
166
167
|
rankFuse(entityRank, 0.95)
|
|
167
168
|
) * 20;
|
|
168
169
|
const quality = 0.15 + clamp(importance, 1, 10, 5) / 22;
|
|
170
|
+
const confidenceMultiplier = 0.65 + clamp(confidence, 0, 1, 0.7) * 0.35;
|
|
169
171
|
const usage = Math.min(0.08, Math.log1p(Math.max(0, Number(accessCount) || 0)) / 50);
|
|
170
|
-
return Math.max(baseScore, fused + quality + usage) * freshness;
|
|
172
|
+
return Math.max(baseScore, fused + quality + usage) * freshness * confidenceMultiplier;
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
module.exports = {
|