iconsulting/packages/services/evolution-service/src/infrastructure/claude/experience-extractor.servic...

293 lines
7.3 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, 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<string>('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<ConversationAnalysis> {
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<ExtractedExperience[]> {
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<Array<{
topic: string;
priority: number;
suggestedContent: string;
}>> {
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 [];
}
}
}