293 lines
7.3 KiB
TypeScript
293 lines
7.3 KiB
TypeScript
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 [];
|
||
}
|
||
}
|
||
}
|