Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | /** * AI 响应清理工具(LLM 服务层统一入口) * * 所有 LLM 响应都应经过此模块处理后再返回给调用方。 * 职责: * 1. 过滤思考标签(<think/>、<thinking/> 等) * 2. 清理格式标记(Markdown 符号、代码块等) * 3. 提取纯文本 / JSON 内容 */ /** * 清理 AI 响应中的思考标签和格式 * 统一入口,所有 LLM 响应都应经过此函数处理 * * 处理策略: * 1. 先移除成对思考标签及内容(如 <think ... </think >) * 2. 再移除未闭合思考标签(如 <think ... 到文本末尾) * 3. 移除孤立的闭标签 */ export function cleanLlmResponse(raw: string): string { if (!raw || typeof raw !== 'string') return raw; let cleaned = raw; // 1. 移除成对思考标签及其内容(从开标签到闭标签,含中间内容) const thinkBlockPatterns = [ /<think\b[^>]*>[\s\S]*?<\/think\s*>/gi, /<thinking\b[^>]*>[\s\S]*?<\/thinking\s*>/gi, /<thought\b[^>]*>[\s\S]*?<\/thought\s*>/gi, /<reflection\b[^>]*>[\s\S]*?<\/reflection\s*>/gi, /<reasoning\b[^>]*>[\s\S]*?<\/reasoning\s*>/gi, /<scratchpad\b[^>]*>[\s\S]*?<\/scratchpad\s*>/gi, /<internal\b[^>]*>[\s\S]*?<\/internal\s*>/gi, /<antthinking\b[^>]*>[\s\S]*?<\/antthinking\s*>/gi, /\[think\][\s\S]*?\[\/think\]/gi, /\[thinking\][\s\S]*?\[\/thinking\]/gi, ]; for (const pattern of thinkBlockPatterns) { cleaned = cleaned.replace(pattern, ''); } // 2. 移除未闭合思考标签(从开标签到文本末尾) const unclosedPatterns = [ /<think\b[^>]*>[\s\S]*$/gi, /<thinking\b[^>]*>[\s\S]*$/gi, /<thought\b[^>]*>[\s\S]*$/gi, /<reflection\b[^>]*>[\s\S]*$/gi, /<reasoning\b[^>]*>[\s\S]*$/gi, ]; for (const pattern of unclosedPatterns) { cleaned = cleaned.replace(pattern, ''); } // 3. 移除自闭合标签 cleaned = cleaned.replace(/<think\s*\/>/gi, ''); cleaned = cleaned.replace(/<thinking\s*\/>/gi, ''); // 4. 移除孤立的闭标签 cleaned = cleaned.replace(/<\/think\s*>/gi, ''); cleaned = cleaned.replace(/<\/thinking\s*>/gi, ''); cleaned = cleaned.replace(/<\/thought\s*>/gi, ''); cleaned = cleaned.replace(/<\/reflection\s*>/gi, ''); cleaned = cleaned.replace(/<\/reasoning\s*>/gi, ''); // 5. 压缩多余空行 cleaned = cleaned.replace(/\n{3,}/g, '\n\n'); return cleaned.trim(); } /** * 清理 AI 响应并提取简短文本(适合标题、标签等场景) * 在 cleanLlmResponse 基础上额外处理: * - 去除 Markdown 格式标记 * - 去除引号包裹 * - 中文截断 / 英文截断 */ export function cleanLlmShortText( raw: string, options: { maxLength?: number; // 最大字符数(默认 50) maxChineseChars?: number; // 中文最大字数(默认 20) maxEnglishWords?: number; // 英文最大词数(默认 20) } = {} ): string { const { maxLength = 50, maxChineseChars = 20, maxEnglishWords = 20 } = options; let text = cleanLlmResponse(raw); // 去除 Markdown 格式 text = text .replace(/^#+\s*/gm, '') // 标题标记 .replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1') // 加粗/斜体 .replace(/`{1,3}[^`]+`{1,3}/g, '') // 代码块 .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // 链接 .replace(/["""《》''「」『』]/g, '') // 各种引号 .replace(/[\n\r]+/g, ' ') // 换行转空格 .replace(/^书名[::]\s*/i, '') // 去掉可能的"书名:"前缀 .trim(); // 中文截断:如果中文字符超过限制 const chineseChars = text.match(/[\u4e00-\u9fff]/g); if (chineseChars && chineseChars.length > maxChineseChars) { let count = 0; let cutIndex = 0; for (let i = 0; i < text.length; i++) { if (/[\u4e00-\u9fff]/.test(text[i])) { count++; if (count > maxChineseChars) { cutIndex = i; break; } } } if (cutIndex > 0) text = text.substring(0, cutIndex); } // 英文截断:如果英文单词超过限制 const englishWords = text.match(/[a-zA-Z]+/g); if (englishWords && englishWords.length > maxEnglishWords) { const words = text.split(/\s+/); text = words.slice(0, maxEnglishWords).join(' '); } // 总长度截断保护 if (text.length > maxLength) { text = text.substring(0, maxLength); } return text.trim(); } /** * 从 AI 响应中提取 JSON * 在 cleanLlmResponse 基础上尝试提取 JSON 对象 */ export function extractJsonFromResponse<T = any>(raw: string): T | null { const cleaned = cleanLlmResponse(raw); // 尝试直接解析 try { return JSON.parse(cleaned); } catch {} // 提取 ```json ... ``` 代码块 const codeBlockMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/); if (codeBlockMatch) { try { return JSON.parse(codeBlockMatch[1].trim()); } catch {} } // 提取第一个 {...} 或 [...] const jsonMatch = cleaned.match(/[\[{][\s\S]*[}\]]/); if (jsonMatch) { try { return JSON.parse(jsonMatch[0]); } catch {} } return null; } |