/** * RVW稿件审查模块 - 工具函数 * @module rvw/services/utils */ import { MethodologyReview, MethodologyStatus } from '../types/index.js'; import { jsonrepair } from 'jsonrepair'; /** * 从LLM响应中解析JSON * 支持多种格式:纯JSON、```json代码块、混合文本 */ export function parseJSONFromLLMResponse(content: string): T { try { // 1. 尝试直接解析 return JSON.parse(content) as T; } catch { // 1.1 先尝试 jsonrepair(处理尾逗号、引号缺失等常见脏 JSON) try { const repaired = jsonrepair(content); return JSON.parse(repaired) as T; } catch { // 继续后续提取策略 } // 2. 尝试提取```json代码块 const jsonMatch = content.match(/```json\s*\n?([\s\S]*?)\n?```/); if (jsonMatch) { try { return JSON.parse(jsonMatch[1].trim()) as T; } catch { // 尝试修复代码块 JSON try { const repaired = jsonrepair(jsonMatch[1].trim()); return JSON.parse(repaired) as T; } catch { // 继续尝试其他方法 } } } // 3. 尝试提取{}或[]包裹的内容 const objectMatch = content.match(/(\{[\s\S]*\})/); if (objectMatch) { try { return JSON.parse(objectMatch[1]) as T; } catch { try { const repaired = jsonrepair(objectMatch[1]); return JSON.parse(repaired) as T; } catch { // 继续尝试其他方法 } } } const arrayMatch = content.match(/(\[[\s\S]*\])/); if (arrayMatch) { try { return JSON.parse(arrayMatch[1]) as T; } catch { try { const repaired = jsonrepair(arrayMatch[1]); return JSON.parse(repaired) as T; } catch { // 失败 } } } // 4. 所有尝试都失败 throw new Error('无法从LLM响应中解析JSON'); } } /** * 根据方法学评估结果判断状态 * @param review 方法学评估结果 * @returns pass | warn | fail */ export function getMethodologyStatus(review: MethodologyReview | null | undefined): MethodologyStatus | undefined { if (!review) return undefined; const score = review.overall_score; if (score >= 80) return 'pass'; if (score >= 60) return 'warn'; return 'fail'; } /** * 根据选择的智能体计算综合分数 * @param editorialScore 稿约规范性分数 * @param methodologyScore 方法学分数 * @param agents 选择的智能体 * @returns 综合分数(保留1位小数) */ export function calculateOverallScore( editorialScore: number | null | undefined, methodologyScore: number | null | undefined, agents: string[] ): number | null { const hasEditorial = agents.includes('editorial') && editorialScore != null; const hasMethodology = agents.includes('methodology') && methodologyScore != null; let score: number | null = null; if (hasEditorial && hasMethodology) { // 两个都选:稿约40% + 方法学60% score = editorialScore! * 0.4 + methodologyScore! * 0.6; } else if (hasEditorial) { // 只选规范性 score = editorialScore!; } else if (hasMethodology) { // 只选方法学 score = methodologyScore!; } // 修复浮点数精度问题:保留1位小数 return score !== null ? Math.round(score * 10) / 10 : null; } /** * 验证智能体选择 * @param agents 选择的智能体列表 * @throws 如果选择无效 */ export function validateAgentSelection(agents: string[]): void { if (!agents || agents.length === 0) { throw new Error('请至少选择一个智能体'); } const validAgents = ['editorial', 'methodology', 'clinical']; for (const agent of agents) { if (!validAgents.includes(agent)) { throw new Error(`无效的智能体类型: ${agent}`); } } if (agents.length > 3) { throw new Error('最多只能选择3个智能体'); } }