P0-P2: heartbeat→mind wiring, structured JSON output, bge-m3 embeddings
This commit is contained in:
1 parent
702700dcb9
commit
a6fde100ca
9 files changed
+184
-35
No files matched your search
+22
-15
@@ -7,6 +7,8 @@
|
||||
* long-term — в LanceDB (или Qdrant на a2000 для прода).
|
||||
*/
|
||||
|
||||
import { EmbeddingClient, cosineSimilarity } from './embedding.js';
|
||||
|
||||
export interface Thought {
|
||||
id: string;
|
||||
text: string;
|
||||
@@ -39,8 +41,12 @@ export class MemorySystem {
|
||||
private notepads = new Map<string, ContactNotepad>();
|
||||
private selfObservationsList: SelfObservation[] = [];
|
||||
private longTerm: LongTermMemory[] = [];
|
||||
/** Embedding-клиент (Ollama bge-m3). Если null — long-term без векторного поиска. */
|
||||
private embeddingClient: EmbeddingClient | null;
|
||||
|
||||
constructor(private recentCount: number) {}
|
||||
constructor(private recentCount: number, embeddingClient?: EmbeddingClient) {
|
||||
this.embeddingClient = embeddingClient ?? null;
|
||||
}
|
||||
|
||||
// === Short-term: последние мысли/опыт/сообщения ===
|
||||
addThought(text: string): Thought {
|
||||
@@ -74,19 +80,26 @@ export class MemorySystem {
|
||||
return this.selfObservationsList;
|
||||
}
|
||||
|
||||
// === Long-term: факты/опыт/процедуры/исходы + векторный поиск ===
|
||||
addLongTerm(text: string, type: LongTermMemory['type']): LongTermMemory {
|
||||
// === Long-term: факты/опыт/процедуры/исходы + эмбедд для векторного поиска ===
|
||||
async addLongTerm(text: string, type: LongTermMemory['type']): Promise<LongTermMemory> {
|
||||
const m: LongTermMemory = { id: `lt-${Date.now()}`, text, type, createdAt: Date.now() };
|
||||
if (this.embeddingClient) {
|
||||
m.embedding = await this.embeddingClient.embed(text);
|
||||
}
|
||||
this.longTerm.push(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Поиск по long-term по эмбедду (в прода заменяется на LanceDB/Qdrant query). */
|
||||
searchLongTerm(queryEmbedding: number[], topK = 3): LongTermMemory[] {
|
||||
// cosine similarity — заглушка, в прода: LanceDB.search
|
||||
/** Поиск по long-term по тексту через эмбедд (в прода заменяется на LanceDB/Qdrant). */
|
||||
async searchLongTerm(query: string, topK = 3): Promise<LongTermMemory[]> {
|
||||
if (!this.embeddingClient) {
|
||||
// без embedding-клиента — простой fallback
|
||||
return this.longTerm.slice(0, topK);
|
||||
}
|
||||
const q = await this.embeddingClient.embed(query);
|
||||
const scored = this.longTerm
|
||||
.filter(m => m.embedding)
|
||||
.map(m => ({ m, score: cosine(queryEmbedding, m.embedding!) }))
|
||||
.map(m => ({ m, score: cosineSimilarity(q, m.embedding!) }))
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, topK)
|
||||
.map(x => x.m);
|
||||
@@ -94,16 +107,10 @@ export class MemorySystem {
|
||||
}
|
||||
|
||||
/** Консолидация: перенос недавних мыслей в long-term (по спецификации Animus). */
|
||||
consolidate(recent: Thought[]): void {
|
||||
async consolidate(recent: Thought[]): Promise<void> {
|
||||
for (const t of recent) {
|
||||
// эвристика: только «значимые» мысли (по тегам/ключевым словам)
|
||||
this.addLongTerm(t.text, 'experience');
|
||||
await this.addLongTerm(t.text, 'experience');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cosine(a: number[], b: number[]): number {
|
||||
let dot = 0, na = 0, nb = 0;
|
||||
for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i]; }
|
||||
return Math.sqrt(na) * Math.sqrt(nb) ? dot / (Math.sqrt(na) * Math.sqrt(nb)) : 0;
|
||||
}
|
||||
Reference in new issue
Block a user