/** * 回复质量门控 - 第三层 * 检查和优化 AI 回复质量 * * @deprecated 已被多 Agent 协作架构取代。 * 新架构中,回复质量通过 Coordinator 的系统提示词和专家 Agent 的协作保证,无需单独的门控层。 * 参见 infrastructure/agents/prompts/coordinator-system-prompt.ts */ import { IntentType, IntentResult } from './intent-classifier'; export interface GateResult { /** 是否通过门控 */ passed: boolean; /** 原始回复 */ originalResponse: string; /** 优化后的回复(如果需要) */ optimizedResponse?: string; /** 门控检查详情 */ checks: GateCheck[]; /** 建议(如果未通过) */ suggestions?: string[]; } export interface GateCheck { name: string; passed: boolean; message?: string; } /** * 回复质量门控 */ export class ResponseGate { // 冗余短语 private redundantPhrases = [ '根据我的了解', '根据我所知', '我来为您解答', '让我来帮您', '非常感谢您的咨询', '感谢您的提问', '希望以上信息对您有所帮助', '如果您还有其他问题', '祝您一切顺利', '希望能够帮到您', '以下是详细信息', '以下是具体内容', ]; // 重复结构 private repetitivePatterns = [ /首先[,,].*其次[,,].*然后[,,].*最后/, /第一[,,].*第二[,,].*第三[,,].*第四/, /(需要注意的是[,,]?){2,}/, ]; /** * 检查回复质量 */ check( response: string, intent: IntentResult, userMessage: string, ): GateResult { const checks: GateCheck[] = []; let optimizedResponse = response; const suggestions: string[] = []; // 1. 长度检查 const lengthCheck = this.checkLength(response, intent); checks.push(lengthCheck); if (!lengthCheck.passed) { optimizedResponse = this.trimResponse(optimizedResponse, intent.maxResponseLength); suggestions.push(`回复过长,已从 ${response.length} 字裁剪至 ${optimizedResponse.length} 字`); } // 2. 相关性检查 const relevanceCheck = this.checkRelevance(response, userMessage, intent); checks.push(relevanceCheck); if (!relevanceCheck.passed) { suggestions.push('回复可能与用户问题不够相关'); } // 3. 冗余检查 const redundancyCheck = this.checkRedundancy(optimizedResponse); checks.push(redundancyCheck); if (!redundancyCheck.passed) { optimizedResponse = this.removeRedundancy(optimizedResponse); suggestions.push('已移除冗余表达'); } // 4. 完整性检查 const completenessCheck = this.checkCompleteness(response, intent); checks.push(completenessCheck); if (!completenessCheck.passed) { suggestions.push('回复可能不够完整,建议补充关键信息'); } // 5. 语气检查(针对闲聊) if (intent.type === IntentType.CHAT) { const toneCheck = this.checkTone(optimizedResponse); checks.push(toneCheck); } const allPassed = checks.every(c => c.passed); return { passed: allPassed, originalResponse: response, optimizedResponse: allPassed ? undefined : optimizedResponse, checks, suggestions: suggestions.length > 0 ? suggestions : undefined, }; } /** * 长度检查 */ private checkLength(response: string, intent: IntentResult): GateCheck { const length = response.length; const maxLength = intent.maxResponseLength; // 允许 20% 的弹性 const passed = length <= maxLength * 1.2; return { name: 'length', passed, message: passed ? `长度合适 (${length}/${maxLength})` : `回复过长 (${length}/${maxLength})`, }; } /** * 相关性检查 */ private checkRelevance( response: string, userMessage: string, intent: IntentResult, ): GateCheck { // 提取用户消息中的关键词 const userKeywords = this.extractKeywords(userMessage); // 检查回复中是否包含关键词 const matchedKeywords = userKeywords.filter(kw => response.includes(kw)); const relevanceScore = userKeywords.length > 0 ? matchedKeywords.length / userKeywords.length : 1; // 如果是后续问题,降低阈值 const threshold = intent.isFollowUp ? 0.3 : 0.5; const passed = relevanceScore >= threshold; return { name: 'relevance', passed, message: `相关性: ${Math.round(relevanceScore * 100)}%`, }; } /** * 冗余检查 */ private checkRedundancy(response: string): GateCheck { const foundRedundant = this.redundantPhrases.filter(phrase => response.includes(phrase) ); const hasRepetitive = this.repetitivePatterns.some(pattern => pattern.test(response) ); const passed = foundRedundant.length === 0 && !hasRepetitive; return { name: 'redundancy', passed, message: passed ? '无冗余表达' : `发现 ${foundRedundant.length} 处冗余`, }; } /** * 完整性检查 */ private checkCompleteness(response: string, intent: IntentResult): GateCheck { // 根据意图类型检查必要元素 let passed = true; let message = '回复完整'; switch (intent.type) { case IntentType.DEEP_CONSULTATION: // 深度咨询应包含具体信息 if (response.length < 100) { passed = false; message = '深度咨询回复过于简短'; } break; case IntentType.ACTION_NEEDED: // 需要行动的回复应包含结果或状态 const hasResult = response.includes('已') || response.includes('完成') || response.includes('结果') || response.includes('找到') || response.includes('查询到'); if (!hasResult && intent.needsTools) { passed = false; message = '缺少操作结果'; } break; case IntentType.CLARIFICATION: // 澄清应包含问句 if (!response.includes('?') && !response.includes('?')) { passed = false; message = '澄清应包含追问'; } break; } return { name: 'completeness', passed, message }; } /** * 语气检查 */ private checkTone(response: string): GateCheck { // 闲聊应该简短友好 const isFriendly = response.includes('您好') || response.includes('!') || response.length < 100; return { name: 'tone', passed: isFriendly, message: isFriendly ? '语气友好' : '可以更友好些', }; } /** * 提取关键词 */ private extractKeywords(text: string): string[] { // 移除常见停用词 const stopWords = ['的', '是', '在', '我', '有', '和', '与', '了', '着', '也', '就', '都', '而', '及', '到', '把', '被', '让', '给', '对', '从', '以', '为', '这', '那', '它', '他', '她', '吗', '呢', '吧', '啊', '哦', '嗯']; // 简单分词(按标点和空格) const words = text.split(/[,。!?、;:""''【】()\s]+/).filter(w => w.length >= 2 && !stopWords.includes(w) ); return [...new Set(words)]; } /** * 裁剪回复 */ private trimResponse(response: string, maxLength: number): string { if (response.length <= maxLength) return response; // 尝试在句子边界裁剪 const sentences = response.split(/(?<=[。!?])/); let trimmed = ''; for (const sentence of sentences) { if ((trimmed + sentence).length <= maxLength) { trimmed += sentence; } else { break; } } // 如果没有找到合适的句子边界,强制裁剪 if (trimmed.length < maxLength * 0.5) { trimmed = response.substring(0, maxLength - 3) + '...'; } return trimmed; } /** * 移除冗余表达 */ private removeRedundancy(response: string): string { let cleaned = response; for (const phrase of this.redundantPhrases) { cleaned = cleaned.replace(new RegExp(phrase + '[,,。]?', 'g'), ''); } // 清理多余空格和开头标点 cleaned = cleaned.replace(/^\s*[,,。]+/, '').trim(); return cleaned; } /** * 快速优化回复(不做完整检查) */ optimize(response: string, maxLength: number): string { let optimized = this.removeRedundancy(response); optimized = this.trimResponse(optimized, maxLength); return optimized; } } // 单例导出 export const responseGate = new ResponseGate();