313 lines
8.5 KiB
TypeScript
313 lines
8.5 KiB
TypeScript
/**
|
||
* 回复质量门控 - 第三层
|
||
* 检查和优化 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();
|