324 lines
8.8 KiB
TypeScript
324 lines
8.8 KiB
TypeScript
import { Injectable, Inject } from '@nestjs/common';
|
||
import { EmbeddingService } from '../infrastructure/embedding/embedding.service';
|
||
import { Neo4jService } from '../infrastructure/database/neo4j/neo4j.service';
|
||
import {
|
||
IUserMemoryRepository,
|
||
ISystemExperienceRepository,
|
||
USER_MEMORY_REPOSITORY,
|
||
SYSTEM_EXPERIENCE_REPOSITORY,
|
||
} from '../domain/repositories/memory.repository.interface';
|
||
import { UserMemoryEntity, MemoryType } from '../domain/entities/user-memory.entity';
|
||
import {
|
||
SystemExperienceEntity,
|
||
ExperienceType,
|
||
VerificationStatus,
|
||
} from '../domain/entities/system-experience.entity';
|
||
|
||
/**
|
||
* 记忆管理服务
|
||
* 管理用户长期记忆和系统经验
|
||
*/
|
||
@Injectable()
|
||
export class MemoryService {
|
||
constructor(
|
||
private embeddingService: EmbeddingService,
|
||
private neo4jService: Neo4jService,
|
||
@Inject(USER_MEMORY_REPOSITORY)
|
||
private memoryRepo: IUserMemoryRepository,
|
||
@Inject(SYSTEM_EXPERIENCE_REPOSITORY)
|
||
private experienceRepo: ISystemExperienceRepository,
|
||
) {}
|
||
|
||
// ========== 用户记忆 ==========
|
||
|
||
/**
|
||
* 保存用户记忆
|
||
*/
|
||
async saveUserMemory(params: {
|
||
userId: string;
|
||
memoryType: MemoryType;
|
||
content: string;
|
||
importance?: number;
|
||
sourceConversationId?: string;
|
||
relatedCategory?: string;
|
||
}): Promise<UserMemoryEntity> {
|
||
const memory = UserMemoryEntity.create(params);
|
||
|
||
// 生成向量
|
||
const embedding = await this.embeddingService.getEmbedding(params.content);
|
||
memory.setEmbedding(embedding);
|
||
|
||
// 保存到PostgreSQL
|
||
await this.memoryRepo.save(memory);
|
||
|
||
// 同时记录到Neo4j时间线
|
||
await this.neo4jService.recordUserEvent({
|
||
userId: params.userId,
|
||
eventId: memory.id,
|
||
eventType: `MEMORY_${params.memoryType}`,
|
||
content: params.content,
|
||
metadata: {
|
||
importance: params.importance,
|
||
relatedCategory: params.relatedCategory,
|
||
},
|
||
});
|
||
|
||
console.log(`[MemoryService] Saved memory for user ${params.userId}: ${params.memoryType}`);
|
||
|
||
return memory;
|
||
}
|
||
|
||
/**
|
||
* 获取用户的相关记忆
|
||
*/
|
||
async getUserRelevantMemories(
|
||
userId: string,
|
||
query: string,
|
||
options?: { limit?: number; memoryTypes?: MemoryType[] },
|
||
): Promise<UserMemoryEntity[]> {
|
||
const embedding = await this.embeddingService.getEmbedding(query);
|
||
|
||
const results = await this.memoryRepo.searchByVector(userId, embedding, {
|
||
limit: options?.limit || 5,
|
||
minSimilarity: 0.6,
|
||
});
|
||
|
||
// 记录访问
|
||
for (const { memory } of results) {
|
||
memory.recordAccess();
|
||
await this.memoryRepo.update(memory);
|
||
}
|
||
|
||
return results.map(r => r.memory);
|
||
}
|
||
|
||
/**
|
||
* 获取用户最重要的记忆(用于对话上下文)
|
||
*/
|
||
async getUserTopMemories(userId: string, limit = 5): Promise<UserMemoryEntity[]> {
|
||
return this.memoryRepo.findTopMemories(userId, limit);
|
||
}
|
||
|
||
/**
|
||
* 获取用户所有记忆
|
||
*/
|
||
async getUserMemories(
|
||
userId: string,
|
||
options?: { memoryType?: MemoryType; includeExpired?: boolean },
|
||
): Promise<UserMemoryEntity[]> {
|
||
return this.memoryRepo.findByUserId(userId, options);
|
||
}
|
||
|
||
/**
|
||
* 更新记忆重要性
|
||
*/
|
||
async updateMemoryImportance(memoryId: string, importance: number): Promise<void> {
|
||
const memory = await this.memoryRepo.findById(memoryId);
|
||
if (memory) {
|
||
memory.updateImportance(importance);
|
||
await this.memoryRepo.update(memory);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 标记记忆为过期
|
||
*/
|
||
async expireMemory(memoryId: string): Promise<void> {
|
||
const memory = await this.memoryRepo.findById(memoryId);
|
||
if (memory) {
|
||
memory.markAsExpired();
|
||
await this.memoryRepo.update(memory);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 清理过期记忆
|
||
*/
|
||
async cleanupExpiredMemories(userId: string, olderThanDays = 180): Promise<number> {
|
||
return this.memoryRepo.markExpiredMemories(userId, olderThanDays);
|
||
}
|
||
|
||
/**
|
||
* 删除用户所有记忆(GDPR合规)
|
||
*/
|
||
async deleteUserMemories(userId: string): Promise<void> {
|
||
await this.memoryRepo.deleteByUserId(userId);
|
||
console.log(`[MemoryService] Deleted all memories for user ${userId}`);
|
||
}
|
||
|
||
// ========== 系统经验 ==========
|
||
|
||
/**
|
||
* 提取并保存系统经验
|
||
*/
|
||
async extractAndSaveExperience(params: {
|
||
experienceType: ExperienceType;
|
||
content: string;
|
||
scenario: string;
|
||
relatedCategory?: string;
|
||
sourceConversationId: string;
|
||
confidence?: number;
|
||
}): Promise<SystemExperienceEntity> {
|
||
// 生成向量
|
||
const embedding = await this.embeddingService.getEmbedding(
|
||
`${params.scenario}\n${params.content}`,
|
||
);
|
||
|
||
// 查找相似经验(用于合并)
|
||
const similarExperiences = await this.experienceRepo.findSimilarExperiences(
|
||
embedding,
|
||
0.9, // 高阈值,只合并非常相似的
|
||
);
|
||
|
||
if (similarExperiences.length > 0) {
|
||
// 合并到现有经验
|
||
const existingExperience = similarExperiences[0];
|
||
existingExperience.addSourceConversation(params.sourceConversationId);
|
||
existingExperience.setEmbedding(embedding); // 可以选择更新向量
|
||
await this.experienceRepo.update(existingExperience);
|
||
|
||
console.log(`[MemoryService] Merged experience into ${existingExperience.id}`);
|
||
return existingExperience;
|
||
}
|
||
|
||
// 创建新经验
|
||
const experience = SystemExperienceEntity.create(params);
|
||
experience.setEmbedding(embedding);
|
||
await this.experienceRepo.save(experience);
|
||
|
||
console.log(`[MemoryService] Created new experience: ${experience.id}`);
|
||
return experience;
|
||
}
|
||
|
||
/**
|
||
* 获取相关系统经验
|
||
*/
|
||
async getRelevantExperiences(
|
||
query: string,
|
||
options?: {
|
||
experienceType?: ExperienceType;
|
||
category?: string;
|
||
limit?: number;
|
||
},
|
||
): Promise<SystemExperienceEntity[]> {
|
||
const embedding = await this.embeddingService.getEmbedding(query);
|
||
|
||
const results = await this.experienceRepo.searchByVector(embedding, {
|
||
experienceType: options?.experienceType,
|
||
activeOnly: true,
|
||
limit: options?.limit || 5,
|
||
minSimilarity: 0.7,
|
||
});
|
||
|
||
// 记录使用
|
||
for (const { experience } of results) {
|
||
experience.recordUsage();
|
||
await this.experienceRepo.update(experience);
|
||
}
|
||
|
||
return results.map(r => r.experience);
|
||
}
|
||
|
||
/**
|
||
* 获取待验证的经验
|
||
*/
|
||
async getPendingExperiences(options?: {
|
||
experienceType?: ExperienceType;
|
||
page?: number;
|
||
pageSize?: number;
|
||
}): Promise<{
|
||
items: SystemExperienceEntity[];
|
||
total: number;
|
||
}> {
|
||
const page = options?.page || 1;
|
||
const pageSize = options?.pageSize || 20;
|
||
|
||
const items = await this.experienceRepo.findPendingExperiences({
|
||
experienceType: options?.experienceType,
|
||
limit: pageSize,
|
||
offset: (page - 1) * pageSize,
|
||
});
|
||
|
||
// 简单计数(实际应该有专门的count方法)
|
||
const stats = await this.experienceRepo.getStatistics();
|
||
const total = stats.byStatus[VerificationStatus.PENDING] || 0;
|
||
|
||
return { items, total };
|
||
}
|
||
|
||
/**
|
||
* 审批经验
|
||
*/
|
||
async approveExperience(experienceId: string, adminId: string): Promise<void> {
|
||
const experience = await this.experienceRepo.findById(experienceId);
|
||
if (!experience) {
|
||
throw new Error(`Experience not found: ${experienceId}`);
|
||
}
|
||
|
||
experience.approve(adminId);
|
||
await this.experienceRepo.update(experience);
|
||
|
||
console.log(`[MemoryService] Experience ${experienceId} approved by ${adminId}`);
|
||
}
|
||
|
||
/**
|
||
* 拒绝经验
|
||
*/
|
||
async rejectExperience(experienceId: string, adminId: string): Promise<void> {
|
||
const experience = await this.experienceRepo.findById(experienceId);
|
||
if (!experience) {
|
||
throw new Error(`Experience not found: ${experienceId}`);
|
||
}
|
||
|
||
experience.reject(adminId);
|
||
await this.experienceRepo.update(experience);
|
||
|
||
console.log(`[MemoryService] Experience ${experienceId} rejected by ${adminId}`);
|
||
}
|
||
|
||
/**
|
||
* 记录经验反馈
|
||
*/
|
||
async recordExperienceFeedback(experienceId: string, positive: boolean): Promise<void> {
|
||
const experience = await this.experienceRepo.findById(experienceId);
|
||
if (experience) {
|
||
experience.recordFeedback(positive);
|
||
await this.experienceRepo.update(experience);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取经验统计
|
||
*/
|
||
async getExperienceStatistics(): Promise<{
|
||
total: number;
|
||
byStatus: Record<VerificationStatus, number>;
|
||
byType: Record<ExperienceType, number>;
|
||
}> {
|
||
return this.experienceRepo.getStatistics();
|
||
}
|
||
|
||
// ========== 用户时间线(Neo4j) ==========
|
||
|
||
/**
|
||
* 获取用户时间线
|
||
*/
|
||
async getUserTimeline(
|
||
userId: string,
|
||
options?: {
|
||
limit?: number;
|
||
beforeDate?: Date;
|
||
eventTypes?: string[];
|
||
},
|
||
) {
|
||
return this.neo4jService.getUserTimeline(userId, options);
|
||
}
|
||
|
||
/**
|
||
* 初始化用户节点
|
||
*/
|
||
async initializeUserNode(userId: string, properties?: Record<string, unknown>): Promise<void> {
|
||
await this.neo4jService.createUserNode(userId, properties);
|
||
}
|
||
}
|