import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, MoreThan, LessThan } from 'typeorm'; import { ExperienceExtractorService } from '../infrastructure/claude/experience-extractor.service'; import { ConversationORM } from '../infrastructure/database/entities/conversation.orm'; import { MessageORM } from '../infrastructure/database/entities/message.orm'; import { SystemExperienceORM } from '../infrastructure/database/entities/system-experience.orm'; import { v4 as uuidv4 } from 'uuid'; /** * 进化任务结果 */ export interface EvolutionTaskResult { taskId: string; status: 'success' | 'partial' | 'failed'; conversationsAnalyzed: number; experiencesExtracted: number; knowledgeGapsFound: number; errors: string[]; } /** * 进化服务 * 负责系统的自我学习和进化 */ @Injectable() export class EvolutionService { constructor( @InjectRepository(ConversationORM) private conversationRepo: Repository, @InjectRepository(MessageORM) private messageRepo: Repository, @InjectRepository(SystemExperienceORM) private experienceRepo: Repository, private experienceExtractor: ExperienceExtractorService, ) {} /** * 执行进化任务 - 分析最近的对话并提取经验 */ async runEvolutionTask(options?: { hoursBack?: number; limit?: number; minMessageCount?: number; }): Promise { const taskId = uuidv4(); const hoursBack = options?.hoursBack || 24; const limit = options?.limit || 50; const minMessageCount = options?.minMessageCount || 4; const result: EvolutionTaskResult = { taskId, status: 'success', conversationsAnalyzed: 0, experiencesExtracted: 0, knowledgeGapsFound: 0, errors: [], }; console.log(`[Evolution] Starting task ${taskId}`); try { // 1. 获取待分析的对话 const cutoffTime = new Date(); cutoffTime.setHours(cutoffTime.getHours() - hoursBack); const conversations = await this.conversationRepo.find({ where: { status: 'ENDED', createdAt: MoreThan(cutoffTime), messageCount: MoreThan(minMessageCount), }, order: { createdAt: 'DESC' }, take: limit, }); console.log(`[Evolution] Found ${conversations.length} conversations to analyze`); // 2. 分析每个对话 const allKnowledgeGaps: string[] = []; for (const conversation of conversations) { try { // 获取对话消息 const messages = await this.messageRepo.find({ where: { conversationId: conversation.id }, order: { createdAt: 'ASC' }, }); // 分析对话 const analysis = await this.experienceExtractor.analyzeConversation({ conversationId: conversation.id, messages: messages.map(m => ({ role: m.role as 'user' | 'assistant', content: m.content, })), category: conversation.category, hasConverted: conversation.hasConverted, rating: conversation.rating, }); // 保存提取的经验 for (const exp of analysis.experiences) { await this.saveExperience({ experienceType: exp.type, content: exp.content, scenario: exp.scenario, confidence: exp.confidence, relatedCategory: exp.relatedCategory, sourceConversationId: conversation.id, }); result.experiencesExtracted++; } // 收集知识缺口 allKnowledgeGaps.push(...analysis.knowledgeGaps); result.conversationsAnalyzed++; } catch (error) { result.errors.push(`Conversation ${conversation.id}: ${(error as Error).message}`); } } // 3. 汇总知识缺口 const uniqueGaps = [...new Set(allKnowledgeGaps)]; result.knowledgeGapsFound = uniqueGaps.length; // 可以在这里调用知识服务创建待处理的知识缺口任务 console.log(`[Evolution] Task ${taskId} completed:`, result); if (result.errors.length > 0) { result.status = 'partial'; } return result; } catch (error) { console.error(`[Evolution] Task ${taskId} failed:`, error); result.status = 'failed'; result.errors.push((error as Error).message); return result; } } /** * 保存经验(带去重逻辑) */ private async saveExperience(params: { experienceType: string; content: string; scenario: string; confidence: number; relatedCategory?: string; sourceConversationId: string; }): Promise { // 查找相似经验 const existingExperiences = await this.experienceRepo.find({ where: { experienceType: params.experienceType, relatedCategory: params.relatedCategory, }, }); // 简单的相似度检查(实际应该用向量相似度) const similar = existingExperiences.find( exp => this.simpleSimilarity(exp.content, params.content) > 0.8, ); if (similar) { // 合并到现有经验 if (!similar.sourceConversationIds.includes(params.sourceConversationId)) { similar.sourceConversationIds.push(params.sourceConversationId); similar.confidence = Math.min(100, similar.confidence + 5); await this.experienceRepo.save(similar); } } else { // 创建新经验 const newExperience = this.experienceRepo.create({ id: uuidv4(), experienceType: params.experienceType, content: params.content, scenario: params.scenario, confidence: params.confidence, relatedCategory: params.relatedCategory, sourceConversationIds: [params.sourceConversationId], verificationStatus: 'PENDING', isActive: false, }); await this.experienceRepo.save(newExperience); } } /** * 简单的文本相似度计算 */ private simpleSimilarity(a: string, b: string): number { const aWords = new Set(a.toLowerCase().split(/\s+/)); const bWords = new Set(b.toLowerCase().split(/\s+/)); const intersection = [...aWords].filter(x => bWords.has(x)).length; const union = new Set([...aWords, ...bWords]).size; return intersection / union; } /** * 获取进化统计信息 */ async getEvolutionStatistics(): Promise<{ totalExperiences: number; pendingExperiences: number; approvedExperiences: number; activeExperiences: number; recentConversationsAnalyzed: number; topExperienceTypes: Array<{ type: string; count: number }>; }> { const [total, pending, approved, active] = await Promise.all([ this.experienceRepo.count(), this.experienceRepo.count({ where: { verificationStatus: 'PENDING' } }), this.experienceRepo.count({ where: { verificationStatus: 'APPROVED' } }), this.experienceRepo.count({ where: { isActive: true } }), ]); // 获取最近分析的对话数(过去7天) const weekAgo = new Date(); weekAgo.setDate(weekAgo.getDate() - 7); const recentConversations = await this.conversationRepo.count({ where: { status: 'ENDED', updatedAt: MoreThan(weekAgo), }, }); // 获取经验类型分布 const typeDistribution = await this.experienceRepo .createQueryBuilder('exp') .select('exp.experienceType', 'type') .addSelect('COUNT(*)', 'count') .groupBy('exp.experienceType') .orderBy('count', 'DESC') .limit(5) .getRawMany(); return { totalExperiences: total, pendingExperiences: pending, approvedExperiences: approved, activeExperiences: active, recentConversationsAnalyzed: recentConversations, topExperienceTypes: typeDistribution.map(t => ({ type: t.type, count: parseInt(t.count), })), }; } /** * 获取系统健康报告 */ async getSystemHealthReport(): Promise<{ overall: 'healthy' | 'warning' | 'critical'; metrics: Array<{ name: string; value: number; threshold: number; status: 'good' | 'warning' | 'critical'; }>; recommendations: string[]; }> { const stats = await this.getEvolutionStatistics(); const metrics: Array<{ name: string; value: number; threshold: number; status: 'good' | 'warning' | 'critical'; }> = []; const recommendations: string[] = []; // 检查待验证经验堆积 const pendingRatio = stats.pendingExperiences / Math.max(1, stats.totalExperiences); metrics.push({ name: '待验证经验比例', value: Math.round(pendingRatio * 100), threshold: 50, status: pendingRatio > 0.5 ? 'warning' : 'good', }); if (pendingRatio > 0.5) { recommendations.push('待验证经验过多,建议及时审核'); } // 检查活跃经验数量 metrics.push({ name: '活跃经验数量', value: stats.activeExperiences, threshold: 10, status: stats.activeExperiences < 10 ? 'warning' : 'good', }); if (stats.activeExperiences < 10) { recommendations.push('活跃经验较少,系统学习能力有限'); } // 检查最近分析的对话 metrics.push({ name: '近7天分析对话数', value: stats.recentConversationsAnalyzed, threshold: 50, status: stats.recentConversationsAnalyzed < 50 ? 'warning' : 'good', }); // 计算总体健康状态 const criticalCount = metrics.filter(m => m.status === 'critical').length; const warningCount = metrics.filter(m => m.status === 'warning').length; let overall: 'healthy' | 'warning' | 'critical' = 'healthy'; if (criticalCount > 0) { overall = 'critical'; } else if (warningCount > 1) { overall = 'warning'; } return { overall, metrics, recommendations }; } }