Files
Kokorone/apps/api/src/memory/memory-consolidation.service.ts
T

59 lines
2.1 KiB
TypeScript

import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service.js";
import { WorkingMemoryService } from "./working-memory.service.js";
import { HIGH_EMOTION_THRESHOLD } from "./constants.js";
import { isUserTrivia } from "../personality/cuteness-behavior.js";
function normalize(content: string): string {
return content.trim().toLowerCase();
}
// C-4 睡眠固化:對話中只累積在工作記憶,session 結束時才批次萃取寫入長期記憶。
@Injectable()
export class MemoryConsolidationService {
constructor(
private readonly prisma: PrismaService,
private readonly workingMemory: WorkingMemoryService,
) {}
async consolidate(characterId: string, sessionId: string): Promise<void> {
const entries = await this.workingMemory.getContext(sessionId);
const contentCounts = new Map<string, number>();
for (const entry of entries) {
const key = normalize(entry.content);
contentCounts.set(key, (contentCounts.get(key) ?? 0) + 1);
}
for (const entry of entries) {
const emotionIntensity = entry.emotionIntensity ?? 0;
const isHighEmotion = emotionIntensity >= HIGH_EMOTION_THRESHOLD;
const isRepeated = (contentCounts.get(normalize(entry.content)) ?? 0) > 1;
if (!isHighEmotion && !isRepeated) {
// G-6 記住小事:使用者隨口提過的瑣事即使情緒強度低也額外加權保留(寫入語意記憶,日後可主動提起)。
if (entry.role === "user" && isUserTrivia(entry.content)) {
await this.prisma.client.semanticMemory.create({
data: { characterId, fact: entry.content, about: "使用者" },
});
}
continue;
}
await this.prisma.client.episodicMemory.create({
data: {
characterId,
content: entry.content,
occurredAt: entry.timestamp,
emotionTag: entry.emotionTag ?? "CALM",
emotionIntensity,
source: "INTERACTION",
weight: isHighEmotion ? 1 + emotionIntensity : 0.5,
},
});
}
await this.workingMemory.clear(sessionId);
}
}