/** * 策略执行引擎 * * 负责: * 1. 管理对话的咨询阶段状态 * 2. 判断阶段转移 * 3. 生成阶段引导指令(注入到System Prompt) * 4. 从对话中提取用户信息 */ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Anthropic from '@anthropic-ai/sdk'; import { DEFAULT_CONSULTING_STRATEGY, ConsultingStage, ConsultingStrategy, StageId, DirectiveType, RequiredInfo, TransitionCondition, ConversionPath, getStageById, getNextStage, getUncollectedInfo, detectUserIntents, CORE_USER_INFO, } from './default-strategy'; /** * 对话的咨询状态 */ export interface ConsultingState { // 当前使用的策略ID strategyId: string; // 当前阶段 currentStageId: StageId; // 当前阶段已进行的轮次 stageTurnCount: number; // 已收集的用户信息 collectedInfo: Record; // 评估结果(如果已评估) assessmentResult?: { recommendedPrograms: string[]; // 推荐的移民类型 suitabilityScore: number; // 匹配度 0-100 highlights: string[]; // 优势 concerns: string[]; // 需要注意的点 }; // 用户选择的转化路径 conversionPath?: ConversionPath; // 阶段历史 stageHistory: Array<{ stageId: StageId; enteredAt: Date; exitedAt?: Date; turnsInStage: number; }>; } /** * 策略引擎服务 */ @Injectable() export class StrategyEngineService { private client: Anthropic; constructor(private configService: ConfigService) { const baseUrl = this.configService.get('ANTHROPIC_BASE_URL'); const isProxyUrl = baseUrl && baseUrl.match(/^\d+\.\d+\.\d+\.\d+/); if (isProxyUrl) { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; } this.client = new Anthropic({ apiKey: this.configService.get('ANTHROPIC_API_KEY'), baseURL: baseUrl || undefined, }); } /** * 初始化新对话的咨询状态 */ initializeState(): ConsultingState { return { strategyId: DEFAULT_CONSULTING_STRATEGY.id, currentStageId: StageId.GREETING, stageTurnCount: 0, collectedInfo: {}, stageHistory: [ { stageId: StageId.GREETING, enteredAt: new Date(), turnsInStage: 0, }, ], }; } /** * 获取当前策略 */ getStrategy(): ConsultingStrategy { // 目前只有默认策略,后续可从数据库加载 return DEFAULT_CONSULTING_STRATEGY; } /** * 获取当前阶段 */ getCurrentStage(state: ConsultingState): ConsultingStage { const stage = getStageById(state.currentStageId); if (!stage) { // 如果阶段无效,回到开场 return getStageById(StageId.GREETING)!; } return stage; } /** * 评估是否需要转移阶段 */ async evaluateTransition( state: ConsultingState, userMessage: string, ): Promise<{ shouldTransition: boolean; targetStageId?: StageId; reason?: string }> { const currentStage = this.getCurrentStage(state); const userIntents = detectUserIntents(userMessage); // 按优先级检查每个转移条件 const sortedTransitions = [...currentStage.transitions].sort((a, b) => b.priority - a.priority); for (const transition of sortedTransitions) { const conditionsMet = await this.checkConditions( transition.conditions, state, userMessage, userIntents, ); if (conditionsMet) { return { shouldTransition: true, targetStageId: transition.targetStageId, reason: transition.description, }; } } return { shouldTransition: false }; } /** * 检查转移条件是否满足 */ private async checkConditions( conditions: TransitionCondition[], state: ConsultingState, userMessage: string, userIntents: string[], ): Promise { // 所有条件都要满足(AND 逻辑) for (const condition of conditions) { const met = await this.checkSingleCondition(condition, state, userMessage, userIntents); if (!met) return false; } return true; } /** * 检查单个条件 */ private async checkSingleCondition( condition: TransitionCondition, state: ConsultingState, userMessage: string, userIntents: string[], ): Promise { switch (condition.type) { case 'INFO_COLLECTED': if (condition.infoKeys) { const collectedCount = condition.infoKeys.filter( key => state.collectedInfo[key] !== undefined ).length; if (condition.minCount) { return collectedCount >= condition.minCount; } return collectedCount === condition.infoKeys.length; } return false; case 'USER_INTENT': if (condition.intents) { return condition.intents.some(intent => userIntents.includes(intent)); } return false; case 'TURNS_EXCEEDED': if (condition.maxTurns !== undefined) { return state.stageTurnCount >= condition.maxTurns; } return false; case 'KEYWORD': if (condition.keywords) { const lowerMessage = userMessage.toLowerCase(); return condition.keywords.some(kw => lowerMessage.includes(kw.toLowerCase())); } return false; case 'ASSESSMENT_DONE': return state.assessmentResult !== undefined; case 'CUSTOM': // 自定义条件用LLM判断 if (condition.customCondition) { return this.evaluateCustomCondition(condition.customCondition, userMessage, state); } return false; default: return false; } } /** * 用LLM评估自定义条件 */ private async evaluateCustomCondition( condition: string, userMessage: string, state: ConsultingState, ): Promise { try { const response = await this.client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 10, messages: [ { role: 'user', content: `判断以下条件是否满足,只回答 true 或 false: 条件:${condition} 用户最新消息:${userMessage} 已收集的用户信息:${JSON.stringify(state.collectedInfo)} 回答(true/false):`, }, ], }); const text = response.content[0].type === 'text' ? response.content[0].text : ''; return text.toLowerCase().includes('true'); } catch { return false; } } /** * 执行阶段转移 */ transitionToStage(state: ConsultingState, targetStageId: StageId): ConsultingState { // 更新历史记录 const currentHistory = state.stageHistory[state.stageHistory.length - 1]; if (currentHistory) { currentHistory.exitedAt = new Date(); currentHistory.turnsInStage = state.stageTurnCount; } // 创建新状态 return { ...state, currentStageId: targetStageId, stageTurnCount: 0, stageHistory: [ ...state.stageHistory, { stageId: targetStageId, enteredAt: new Date(), turnsInStage: 0, }, ], }; } /** * 增加阶段轮次 */ incrementTurnCount(state: ConsultingState): ConsultingState { return { ...state, stageTurnCount: state.stageTurnCount + 1, }; } /** * 从对话中提取用户信息 */ async extractUserInfo( userMessage: string, assistantMessage: string, existingInfo: Record, ): Promise> { const infoToExtract = CORE_USER_INFO.filter(info => !existingInfo[info.key]); if (infoToExtract.length === 0) { return {}; } try { const response = await this.client.messages.create({ model: 'claude-sonnet-4-20250514', max_tokens: 500, messages: [ { role: 'user', content: `从以下对话中提取用户信息。只提取能明确确定的信息,不要猜测。 用户消息:${userMessage} 助手回复:${assistantMessage} 需要提取的字段: ${infoToExtract.map(i => `- ${i.key}: ${i.description}`).join('\n')} 返回JSON格式,只包含能确定的字段。如果没有任何信息可提取,返回 {} 示例:{"age": 35, "education": "硕士"} JSON:`, }, ], }); const text = response.content[0].type === 'text' ? response.content[0].text : '{}'; // 提取JSON部分 const jsonMatch = text.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } return {}; } catch (error) { console.error('[StrategyEngine] Failed to extract user info:', error); return {}; } } /** * 执行资格评估 */ async performAssessment( collectedInfo: Record, ): Promise { const age = collectedInfo.age as number | undefined; const education = collectedInfo.education as string | undefined; const university = collectedInfo.university as string | undefined; const workYears = collectedInfo.workYears as number | undefined; const annualIncome = collectedInfo.annualIncome as number | undefined; const recommendedPrograms: string[] = []; const highlights: string[] = []; const concerns: string[] = []; let suitabilityScore = 50; // 高才通A类:年薪250万港币以上(约220万人民币) if (annualIncome && annualIncome >= 2200000) { recommendedPrograms.push('高才通A类'); highlights.push('年薪达到高才通A类标准(250万港币)'); suitabilityScore += 30; } // 高才通B类:全球百强大学+3年以上工作经验 const top100Universities = ['清华', '北大', '复旦', '上海交通', '浙大', '中科大', '南京大学', '香港大学', '港中文', '港科大']; const isTop100 = university && top100Universities.some(u => university.includes(u)); if (isTop100 && workYears && workYears >= 3) { recommendedPrograms.push('高才通B类'); highlights.push('名校毕业+工作经验充足'); suitabilityScore += 25; } // 高才通C类:全球百强大学毕业,工作经验不足3年 if (isTop100 && (!workYears || workYears < 3)) { recommendedPrograms.push('高才通C类'); highlights.push('名校毕业'); concerns.push('工作经验较少,C类有年度名额限制'); suitabilityScore += 15; } // 优才计划 if (education && ['硕士', '博士', 'MBA'].some(e => education.includes(e))) { recommendedPrograms.push('优才计划'); highlights.push('高学历加分'); suitabilityScore += 15; } if (workYears && workYears >= 5) { if (!recommendedPrograms.includes('优才计划')) { recommendedPrograms.push('优才计划'); } highlights.push('工作经验丰富'); suitabilityScore += 10; } // 专才计划:需要香港雇主 if (collectedInfo.hasHKConnection) { recommendedPrograms.push('专才计划'); highlights.push('有香港雇主/工作机会'); suitabilityScore += 20; } // 年龄因素 if (age) { if (age >= 18 && age <= 39) { highlights.push('年龄优势明显'); suitabilityScore += 10; } else if (age >= 40 && age <= 50) { concerns.push('年龄在优才计划中扣分'); } else if (age > 50) { concerns.push('年龄偏大,建议尽快申请'); } } // 如果没有明确推荐,默认推荐优才 if (recommendedPrograms.length === 0) { recommendedPrograms.push('优才计划'); concerns.push('需要更多信息做精准评估'); } return { recommendedPrograms: [...new Set(recommendedPrograms)], // 去重 suitabilityScore: Math.min(100, suitabilityScore), highlights, concerns, }; } /** * 构建阶段引导(注入到System Prompt) */ buildStageGuidance(state: ConsultingState): string { const strategy = this.getStrategy(); const stage = this.getCurrentStage(state); const uncollectedInfo = getUncollectedInfo(stage.id, state.collectedInfo); const parts: string[] = []; // ========== 当前阶段信息 ========== parts.push('## 【咨询流程控制】'); parts.push(''); parts.push(`### 当前阶段:${stage.name}(第${stage.order}阶段,共8阶段)`); parts.push(`**阶段目标**:${stage.goal}`); parts.push(`**当前轮次**:${state.stageTurnCount + 1}/${stage.maxTurns}`); parts.push(''); // ========== 行为指令 ========== parts.push('### 行为指令(必须遵守)'); const allDirectives = [...strategy.globalDirectives, ...stage.directives] .sort((a, b) => b.priority - a.priority); for (const directive of allDirectives) { const prefix = { [DirectiveType.MUST_DO]: '✓ 必须', [DirectiveType.SHOULD_DO]: '○ 建议', [DirectiveType.MUST_NOT]: '✗ 禁止', [DirectiveType.PREFER]: '☆ 优先', }[directive.type]; parts.push(`${prefix}:${directive.content}`); } parts.push(''); // ========== 信息收集进度 ========== if (Object.keys(state.collectedInfo).length > 0 || uncollectedInfo.length > 0) { parts.push('### 用户信息收集进度'); // 已收集的 for (const [key, value] of Object.entries(state.collectedInfo)) { const info = CORE_USER_INFO.find(i => i.key === key); parts.push(`✓ ${info?.label || key}:${value}`); } // 未收集的(当前阶段需要的) for (const info of uncollectedInfo.slice(0, 3)) { // 最多显示3个 parts.push(`○ ${info.label}:待收集(建议问法:"${info.askingStrategy}")`); } parts.push(''); } // ========== 评估结果 ========== if (state.assessmentResult) { parts.push('### 评估结果(基于已收集信息)'); parts.push(`**推荐方案**:${state.assessmentResult.recommendedPrograms.join('、')}`); parts.push(`**匹配度**:${state.assessmentResult.suitabilityScore}分`); if (state.assessmentResult.highlights.length > 0) { parts.push(`**优势**:${state.assessmentResult.highlights.join(';')}`); } if (state.assessmentResult.concerns.length > 0) { parts.push(`**注意事项**:${state.assessmentResult.concerns.join(';')}`); } parts.push(''); } // ========== 阶段转移提示 ========== parts.push('### 阶段转移判断'); parts.push('根据用户回复,判断是否满足以下转移条件:'); for (const transition of stage.transitions.slice(0, 3)) { parts.push(`- 如果【${transition.description}】→ 进入【${getStageById(transition.targetStageId)?.name}】阶段`); } parts.push(''); // ========== 专家联系方式(如果在转化/对接阶段)========== if ([StageId.CONVERSION, StageId.HANDOFF].includes(state.currentStageId)) { parts.push('### 可用资源'); parts.push(`- 付费评估服务:¥${strategy.paidServices.assessmentPrice},${strategy.paidServices.description}`); parts.push(`- 人类专家微信:${strategy.expertContact.wechat}`); parts.push(`- 专家电话:${strategy.expertContact.phone}`); parts.push(`- 工作时间:${strategy.expertContact.workingHours}`); parts.push(''); } // ========== 回复要求 ========== parts.push('### 回复要求'); parts.push(`- 建议长度:${stage.suggestedResponseLength}字左右`); parts.push('- 结尾必须有推进对话的问题或明确的下一步引导'); parts.push('- 不要一次给太多信息,要循序渐进'); return parts.join('\n'); } /** * 更新收集的信息 */ updateCollectedInfo( state: ConsultingState, newInfo: Record, ): ConsultingState { return { ...state, collectedInfo: { ...state.collectedInfo, ...newInfo, }, }; } /** * 设置评估结果 */ setAssessmentResult( state: ConsultingState, result: ConsultingState['assessmentResult'], ): ConsultingState { return { ...state, assessmentResult: result, }; } /** * 设置转化路径 */ setConversionPath( state: ConsultingState, path: ConversionPath, ): ConsultingState { return { ...state, conversionPath: path, }; } }