/** * RVW稿件审查模块 - 临床专业评估服务 * @module rvw/services/clinicalService * * 使用 PromptService 获取 RVW_CLINICAL prompt * 返回纯 Markdown 报告(无分数) */ import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js'; import { ModelType } from '../../../common/llm/adapters/types.js'; import { logger } from '../../../common/logging/index.js'; import { prisma } from '../../../config/database.js'; import { getPromptService } from '../../../common/prompt/index.js'; import { composeRvwSystemPrompt, sanitizeRvwBusinessPrompt } from './promptProtocols.js'; export interface ClinicalReviewResult { report: string; summary: string; } /** * 临床专业评估 * @param text 稿件文本 * @param modelType 模型类型 * @param userId 用户ID(用于灰度预览判断) * @returns 评估结果(Markdown 报告 + 摘要) */ export async function reviewClinical( text: string, modelType: ModelType = 'deepseek-v3', userId?: string, tenantExpertPrompt?: string | null ): Promise { try { let businessPrompt = tenantExpertPrompt?.trim() || ''; let isDraft = false; if (!businessPrompt) { const promptService = getPromptService(prisma); const result = await promptService.get('RVW_CLINICAL', {}, { userId }); businessPrompt = result.content; isDraft = result.isDraft; } businessPrompt = sanitizeRvwBusinessPrompt('clinical', businessPrompt); if (isDraft) { logger.info('[RVW:Clinical] 使用 DRAFT 版本 Prompt(调试模式)', { userId }); } const messages = [ { role: 'system' as const, content: composeRvwSystemPrompt('clinical', businessPrompt) }, { role: 'user' as const, content: `请对以下医学稿件进行临床专业评估。\n\n稿件内容如下:\n${text}`, }, ]; logger.info('[RVW:Clinical] 开始临床专业评估', { modelType }); const llmAdapter = LLMFactory.getAdapter(modelType); const response = await llmAdapter.chat(messages, { temperature: 0.3, maxTokens: 8000, }); const content = response.content ?? ''; logger.info('[RVW:Clinical] 评估完成', { modelType, responseLength: content.length, }); // 优先提取“总体评价”段落作为摘要,提取失败再兜底首段文本 let summary = '临床专业评估已完成'; const overallMatch = content.match(/(?:^|\n)\s*(?:#*\s*)?1[\.\、]\s*总体评价[\s\S]*?(?:\n(?:#*\s*)?2[\.\、]\s*详细问题清单与建议|$)/); if (overallMatch) { const extracted = overallMatch[0] .replace(/(?:^|\n)\s*(?:#*\s*)?1[\.\、]\s*总体评价\s*/m, '') .trim(); if (extracted) { summary = extracted.substring(0, 200); } } else { const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#')); if (lines.length > 0) { summary = lines[0].trim().substring(0, 200); } } return { report: content, summary }; } catch (error) { logger.error('[RVW:Clinical] 临床专业评估失败', { error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined, }); throw new Error(`临床专业评估失败: ${error instanceof Error ? error.message : 'Unknown error'}`); } }