iconsulting/packages/services/knowledge-service/src/memory/memory.service.ts

324 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}