iconsulting/packages/services/conversation-service/src/infrastructure/claude/response-gate.ts

313 lines
8.5 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.

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