import { Injectable, OnModuleInit } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Anthropic from '@anthropic-ai/sdk'; /** * 提取的经验结构 */ export interface ExtractedExperience { type: string; content: string; scenario: string; confidence: number; relatedCategory?: string; } /** * 对话分析结果 */ export interface ConversationAnalysis { experiences: ExtractedExperience[]; userInsights: UserInsight[]; knowledgeGaps: string[]; conversionSignals: ConversionSignal[]; } export interface UserInsight { type: string; content: string; importance: number; } export interface ConversionSignal { type: 'positive' | 'negative'; signal: string; context: string; } /** * 经验提取服务 * 使用Claude分析对话,提取有价值的系统经验 */ @Injectable() export class ExperienceExtractorService implements OnModuleInit { private client: Anthropic; constructor(private configService: ConfigService) {} onModuleInit() { const apiKey = this.configService.get('ANTHROPIC_API_KEY'); if (!apiKey) { console.warn('[ExperienceExtractor] ANTHROPIC_API_KEY not set'); return; } this.client = new Anthropic({ apiKey }); console.log('[ExperienceExtractor] Initialized'); } /** * 分析对话并提取经验 */ async analyzeConversation(params: { conversationId: string; messages: Array<{ role: 'user' | 'assistant'; content: string }>; category?: string; hasConverted: boolean; rating?: number; }): Promise { if (!this.client) { return this.getMockAnalysis(); } const prompt = this.buildAnalysisPrompt(params); try { const response = await this.client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 2000, messages: [{ role: 'user', content: prompt }], }); const content = response.content[0]; if (content.type !== 'text') { return this.getMockAnalysis(); } return this.parseAnalysisResult(content.text); } catch (error) { console.error('[ExperienceExtractor] Analysis failed:', error); return this.getMockAnalysis(); } } /** * 构建分析提示词 */ private buildAnalysisPrompt(params: { messages: Array<{ role: 'user' | 'assistant'; content: string }>; category?: string; hasConverted: boolean; rating?: number; }): string { const conversationText = params.messages .map(m => `${m.role === 'user' ? '用户' : '助手'}: ${m.content}`) .join('\n\n'); return `你是一个专门分析香港移民咨询对话的AI专家。请分析以下对话,提取有价值的系统经验。 ## 对话背景 - 移民类别: ${params.category || '未知'} - 是否转化付费: ${params.hasConverted ? '是' : '否'} - 用户评分: ${params.rating || '未评分'} ## 对话内容 ${conversationText} ## 分析任务 请提取以下信息,以JSON格式返回: 1. **experiences** (系统经验数组): - type: 经验类型 (COMMON_QUESTION/ANSWER_TEMPLATE/CLARIFICATION/USER_PATTERN/CONVERSION_TRIGGER/KNOWLEDGE_GAP/CONVERSATION_SKILL/OBJECTION_HANDLING) - content: 经验内容 - scenario: 适用场景 - confidence: 置信度 (0-100) - relatedCategory: 相关移民类别 2. **userInsights** (用户洞察数组): - type: 洞察类型 (PERSONAL_INFO/WORK_EXPERIENCE/EDUCATION/LANGUAGE/IMMIGRATION_INTENT/CONCERN) - content: 洞察内容 - importance: 重要性 (0-100) 3. **knowledgeGaps** (知识缺口数组): - 对话中暴露出的知识库缺失内容 4. **conversionSignals** (转化信号数组): - type: positive/negative - signal: 信号描述 - context: 上下文 请只返回JSON,不要其他内容。示例格式: { "experiences": [...], "userInsights": [...], "knowledgeGaps": [...], "conversionSignals": [...] }`; } /** * 解析分析结果 */ private parseAnalysisResult(text: string): ConversationAnalysis { try { // 尝试从文本中提取JSON const jsonMatch = text.match(/\{[\s\S]*\}/); if (!jsonMatch) { throw new Error('No JSON found in response'); } const result = JSON.parse(jsonMatch[0]); return { experiences: result.experiences || [], userInsights: result.userInsights || [], knowledgeGaps: result.knowledgeGaps || [], conversionSignals: result.conversionSignals || [], }; } catch (error) { console.error('[ExperienceExtractor] Failed to parse result:', error); return this.getMockAnalysis(); } } /** * Mock分析结果(开发用) */ private getMockAnalysis(): ConversationAnalysis { return { experiences: [], userInsights: [], knowledgeGaps: [], conversionSignals: [], }; } /** * 从多个对话中总结经验 */ async summarizeExperiences( experiences: ExtractedExperience[], ): Promise { if (!this.client || experiences.length < 3) { return experiences; } const prompt = `你是一个经验总结专家。请分析以下从多个对话中提取的经验,找出共同模式并合并相似经验。 ## 原始经验 ${JSON.stringify(experiences, null, 2)} ## 任务 1. 合并相似的经验 2. 提高置信度(多次出现的经验置信度更高) 3. 移除重复或低质量的经验 请返回优化后的经验数组(JSON格式):`; try { const response = await this.client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 2000, messages: [{ role: 'user', content: prompt }], }); const content = response.content[0]; if (content.type !== 'text') { return experiences; } const jsonMatch = content.text.match(/\[[\s\S]*\]/); if (!jsonMatch) { return experiences; } return JSON.parse(jsonMatch[0]); } catch (error) { console.error('[ExperienceExtractor] Summary failed:', error); return experiences; } } /** * 分析知识库缺口 */ async analyzeKnowledgeGaps( gaps: string[], existingArticles: string[], ): Promise> { if (!this.client || gaps.length === 0) { return []; } const prompt = `你是香港移民知识库的内容规划专家。 ## 现有知识库文章标题 ${existingArticles.join('\n')} ## 对话中发现的知识缺口 ${gaps.join('\n')} ## 任务 分析哪些知识缺口是真正需要补充的,并按优先级排序。 对于每个需要补充的主题,提供建议的内容大纲。 请返回JSON数组: [ { "topic": "主题", "priority": 1-100, "suggestedContent": "建议的内容大纲" } ]`; try { const response = await this.client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 2000, messages: [{ role: 'user', content: prompt }], }); const content = response.content[0]; if (content.type !== 'text') { return []; } const jsonMatch = content.text.match(/\[[\s\S]*\]/); if (!jsonMatch) { return []; } return JSON.parse(jsonMatch[0]); } catch (error) { console.error('[ExperienceExtractor] Gap analysis failed:', error); return []; } } }