# IIT Manager Agent 核心引擎实现指南 > **版本:** V2.9 > **更新日期:** 2026-02-05 > **关联文档:** [IIT Manager Agent V2.6 综合开发计划](./IIT%20Manager%20Agent%20V2.6%20综合开发计划.md) > > **V2.9 更新**:ReActEngine Prompt 优化,支持多意图处理(Chain of Thought) --- ## 1. 引擎总览 | 引擎 | 职责 | 执行方式 | Phase | |------|------|----------|-------| | `HardRuleEngine` | 执行硬规则(JSON Logic) | CPU 执行,无 LLM | 1 | | `SoftRuleEngine` | 执行软指令(LLM 推理) | LLM + 工具调用 | 2 | | `SopEngine` | SOP 状态机调度 | 编排 Hard/Soft 引擎 | 2 | | `ReActEngine` | 多步推理(思考-行动-观察) | LLM + 循环推理 | 5 | ``` ┌─────────────────────────────────────────────────────────────────────┐ │ SopEngine (状态机) │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │ 节点A: HardRuleEngine → 节点B: SoftRuleEngine → 节点C: ... │ │ │ └────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ToolsService (工具层) │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 2. HardRuleEngine - 硬规则引擎 > **文件路径**: `backend/src/modules/iit-manager/engines/HardRuleEngine.ts` ### 2.1 核心职责 - 执行 JSON Logic 规则,无需 LLM - 返回结构化的违规列表 - 执行时间 < 100ms ### 2.2 完整实现 ```typescript import jsonLogic from 'json-logic-js'; export interface HardRule { field: string; logic: object; // JSON Logic 表达式 message: string; severity?: 'error' | 'warning' | 'info'; } export interface RuleResult { field: string; message: string; severity: string; value?: any; } export class HardRuleEngine { /** * 执行硬规则检查 * @param rules 规则数组 * @param data 待检查的数据 * @returns 违规列表(空数组表示全部通过) */ run(rules: HardRule[], data: Record): RuleResult[] { const errors: RuleResult[] = []; for (const rule of rules) { try { const passed = jsonLogic.apply(rule.logic, data); if (!passed) { errors.push({ field: rule.field, message: rule.message, severity: rule.severity || 'error', value: data[rule.field] }); } } catch (error) { // 规则执行异常,记录但不中断 errors.push({ field: rule.field, message: `规则执行异常: ${error.message}`, severity: 'error' }); } } return errors; } /** * 检查是否全部通过 */ isAllPassed(rules: HardRule[], data: Record): boolean { return this.run(rules, data).length === 0; } } ``` ### 2.3 JSON Logic 常用表达式 ```javascript // 范围检查 { "and": [ { ">=": [{ "var": "age" }, 18] }, { "<=": [{ "var": "age" }, 75] } ]} // 非空检查 { "!!": { "var": "informed_consent_date" } } // 日期比较(需自定义操作符) { "<=": [{ "var": "icf_date" }, { "var": "enrollment_date" }] } // 枚举值检查 { "in": [{ "var": "ecog" }, [0, 1, 2]] } // 字符串包含 { "in": ["肺炎", { "var": "medical_history" }] } ``` --- ## 3. SoftRuleEngine - 软规则引擎 > **文件路径**: `backend/src/modules/iit-manager/engines/SoftRuleEngine.ts` ### 3.1 核心职责 - 执行需要 LLM 推理的软指令 - 支持工具调用 - 实现自我修正回路(最多 3 次重试) ### 3.2 完整实现 ```typescript import { LLMFactory } from '../../common/llm/adapters/LLMFactory'; import { ToolsService } from '../services/ToolsService'; export interface SoftRuleResult { passed: boolean; reason: string; confidence?: number; evidence?: any; } export class SoftRuleEngine { private llm = LLMFactory.create('qwen'); private tools: ToolsService; constructor(tools: ToolsService) { this.tools = tools; } /** * 执行软指令,带自我修正回路 */ async runWithRetry( instruction: string, context: any, maxRetries: number = 3 ): Promise { const systemPrompt = `你是一个临床研究质控专家。请根据以下指令和数据进行判断。 指令: ${instruction} 请返回 JSON 格式: { "passed": true/false, "reason": "判断理由", "confidence": 0.0-1.0, "evidence": "支持判断的证据" }`; let history: Message[] = [ { role: 'system', content: systemPrompt }, { role: 'user', content: `数据: ${JSON.stringify(context)}` } ]; for (let i = 0; i < maxRetries; i++) { const response = await this.llm.chat(history, { tools: this.tools.getToolDefinitions() }); // AI 决定调用工具 if (response.toolCalls && response.toolCalls.length > 0) { for (const toolCall of response.toolCalls) { try { const result = await this.tools.executeTool( toolCall.name, toolCall.args ); history.push({ role: 'tool', toolCallId: toolCall.id, content: JSON.stringify(result) }); } catch (error) { // ⚠️ 自我修正:告诉 LLM 它错了 history.push({ role: 'user', content: `工具调用失败: ${error.message}。请检查参数并重试,或直接根据已有信息判断。` }); continue; } } } else { // AI 返回最终答案 return this.parseResult(response.content); } } return { passed: false, reason: '多次重试后仍失败,需人工复核', confidence: 0 }; } private parseResult(content: string): SoftRuleResult { try { // 提取 JSON(可能被 Markdown 包裹) const jsonMatch = content.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } } catch (e) { // 解析失败 } // 回退:尝试从文本推断 const passed = content.includes('通过') || content.includes('符合'); return { passed, reason: content.slice(0, 200), confidence: 0.5 }; } } ``` --- ## 4. SopEngine - SOP 状态机引擎 > **文件路径**: `backend/src/modules/iit-manager/engines/SopEngine.ts` ### 4.1 核心职责 - 解析 Skill 配置中的 SOP 流程图 - 按顺序执行节点(Hard → Soft → Human Review) - 实现状态持久化(防止服务重启丢失进度) - 实现 SUSPENDED 挂起机制(解决"死锁"风险) ### 4.2 类型定义 ```typescript interface SopConfig { name: string; start_node: string; nodes: { [nodeId: string]: SopNode; }; } interface SopNode { type: 'hard_rule' | 'soft_instruction' | 'human_review'; // hard_rule 类型 rules?: HardRule[]; // soft_instruction 类型 instruction?: string; tools?: string[]; // human_review 类型 description?: string; // 流转 on_pass: string; on_fail: string; on_approve?: string; // human_review 专用 on_reject?: string; // human_review 专用 on_error?: string; } interface SopResult { status: 'COMPLETED' | 'SUSPENDED' | 'FAILED'; finalState: string; trace: TraceItem[]; taskRunId?: string; message?: string; } interface TraceItem { node: string; timestamp: Date; result?: any; } ``` ### 4.3 完整实现 ```typescript import PgBoss from 'pg-boss'; import { prisma } from '../../common/prisma'; import { HardRuleEngine } from './HardRuleEngine'; import { SoftRuleEngine } from './SoftRuleEngine'; export class SopEngine { private hardEngine: HardRuleEngine; private softEngine: SoftRuleEngine; private pgBoss: PgBoss; constructor( hardEngine: HardRuleEngine, softEngine: SoftRuleEngine, pgBoss: PgBoss ) { this.hardEngine = hardEngine; this.softEngine = softEngine; this.pgBoss = pgBoss; } /** * 执行 SOP 流程 */ async run( skillConfig: SopConfig, data: any, taskRunId?: string ): Promise { // 创建或恢复任务记录 const task = taskRunId ? await this.resumeTask(taskRunId) : await this.createTask(skillConfig, data); let currentNodeId = task.currentNode || skillConfig.start_node; let context = { ...data }; const trace: TraceItem[] = task.trace ? JSON.parse(task.trace) : []; while (currentNodeId && !currentNodeId.startsWith('end')) { const node = skillConfig.nodes[currentNodeId]; trace.push({ node: currentNodeId, timestamp: new Date() }); // ⚠️ 每步持久化,防止服务重启丢失进度 await prisma.iitTaskRun.update({ where: { id: task.id }, data: { currentNode: currentNodeId, trace: JSON.stringify(trace), updatedAt: new Date() } }); // 检查是否需要人工介入 if (node.type === 'human_review') { return await this.handleHumanReview(task.id, node, data, trace); } // 执行节点 let result: NodeResult; try { if (node.type === 'hard_rule') { const errors = this.hardEngine.run(node.rules || [], context); result = { passed: errors.length === 0, errors }; } else { result = await this.softEngine.runWithRetry( node.instruction || '', context ); } } catch (error) { // 执行异常,走 on_error 分支 currentNodeId = node.on_error || 'end_error'; continue; } // 记录违规 if (!result.passed) { await this.savePendingAction(task.id, data.recordId, result); } // 状态流转 currentNodeId = result.passed ? node.on_pass : node.on_fail; } // 完成任务 await prisma.iitTaskRun.update({ where: { id: task.id }, data: { status: 'COMPLETED', currentNode: currentNodeId, completedAt: new Date() } }); return { status: 'COMPLETED', finalState: currentNodeId, trace }; } /** * ⚠️ 处理人工复核节点 - SUSPENDED 挂起机制 */ private async handleHumanReview( taskRunId: string, node: SopNode, data: any, trace: TraceItem[] ): Promise { // 挂起任务,不再占用 Worker await prisma.iitTaskRun.update({ where: { id: taskRunId }, data: { status: 'SUSPENDED', suspendedAt: new Date(), resumeCallback: node.on_approve, rejectCallback: node.on_reject || 'end_rejected' } }); // 通知相关人员 await this.notifyReviewRequired(data, node); return { status: 'SUSPENDED', finalState: 'human_review', trace, taskRunId, message: '任务已挂起,等待人工复核' }; } /** * ⚠️ 唤醒机制 - CRC 确认后继续执行 */ async resumeFromSuspended( taskRunId: string, decision: 'approve' | 'reject' ): Promise { const task = await prisma.iitTaskRun.findUnique({ where: { id: taskRunId } }); if (!task || task.status !== 'SUSPENDED') { throw new Error('任务不在挂起状态'); } const nextNode = decision === 'approve' ? task.resumeCallback : task.rejectCallback || 'end_rejected'; // 更新状态 await prisma.iitTaskRun.update({ where: { id: taskRunId }, data: { status: 'RUNNING', currentNode: nextNode, resumedAt: new Date() } }); // 创建新的 Job 继续执行 await this.pgBoss.send('sop-continue', { taskRunId }); } // ... 辅助方法省略 } ``` --- ## 5. ReActEngine - 多步推理引擎 > **文件路径**: `backend/src/modules/iit-manager/engines/ReActEngine.ts` ### 5.1 核心职责 - 处理模糊查询,支持多步推理 - 循环执行"思考 → 行动 → 观察" - **只允许调用只读工具**(安全约束) - **只返回 Final Answer**(解决"多嘴"问题) - **V2.9 新增**:支持多意图处理(用户一句话包含多个任务) ### 5.2 安全约束 ```typescript // 工具白名单:ReAct 只能调用只读工具 private readonly READONLY_TOOLS = [ 'read_clinical_data', 'search_protocol', 'get_project_stats', 'check_visit_window' ]; // 资源限制 private maxIterations = 5; // 最大迭代次数 private maxTokens = 4000; // Token 预算 ``` ### 5.3 完整实现 ```typescript import { LLMFactory } from '../../common/llm/adapters/LLMFactory'; import { ToolsService } from '../services/ToolsService'; import { prisma } from '../../common/prisma'; export interface ReActResult { success: boolean; content: string; trace: TraceItem[]; } interface TraceItem { iteration: number; thought?: string; action?: string; observation?: string; } export class ReActEngine { private llm = LLMFactory.create('qwen'); private tools: ToolsService; private readonly READONLY_TOOLS = [ 'read_clinical_data', 'search_protocol', 'get_project_stats', 'check_visit_window' ]; private maxIterations = 5; private maxTokens = 4000; private tokenCounter = 0; constructor(tools: ToolsService) { this.tools = tools; } async run(query: string, context: AgentContext): Promise { this.tokenCounter = 0; // V2.9 优化:支持多意图处理的 Prompt const systemPrompt = `你是一个临床研究智能助手。你可以使用以下工具回答问题: ${this.formatToolDescriptions(this.READONLY_TOOLS)} 请按照 ReAct 模式思考: 1. 思考:分析问题,决定下一步 2. 行动:调用工具获取信息 3. 观察:查看工具返回结果 4. 重复以上步骤,直到能回答用户问题 ## 多任务处理原则 (V2.9) 当用户的消息包含多个任务时,请遵循以下步骤: **Step 1: 意图拆解** - 识别用户消息中的所有独立意图 - 按依赖关系排序:先处理前置任务 - 示例:"帮我查一下 P003 的 ECOG,然后录入今天的访视数据" → 意图1: 查询 P003 ECOG(只读) → 意图2: 录入访视数据(需要引导) **Step 2: 顺序执行** - 每次只聚焦处理一个意图 - 完成后明确告知用户,并继续下一个 - 遇到写操作,暂停并引导用户确认 **Step 3: 统一回复** - 在最终回复中按顺序总结所有任务结果 - 格式: 1. [任务1] 已完成 / 结果:... 2. [任务2] 需要确认 / 引导操作 注意:你只能查询数据,不能修改数据。如果用户需要修改操作,请引导他们使用正式的质控流程。`; let messages: Message[] = [ { role: 'system', content: systemPrompt }, { role: 'user', content: query } ]; const trace: TraceItem[] = []; for (let i = 0; i < this.maxIterations; i++) { // Token 预算检查 if (this.tokenCounter > this.maxTokens) { return { success: false, content: '抱歉,这个问题比较复杂,请尝试更具体的描述。', trace }; } const response = await this.llm.chat(messages, { tools: this.getReadonlyToolDefinitions() }); this.tokenCounter += response.usage?.total_tokens || 0; trace.push({ iteration: i, thought: response.content }); // AI 决定结束,返回 Final Answer if (!response.toolCalls || response.toolCalls.length === 0) { return { success: true, content: response.content, trace }; } // 执行工具调用 let errorCount = 0; for (const toolCall of response.toolCalls) { // ⚠️ 安全检查:只允许只读工具 if (!this.READONLY_TOOLS.includes(toolCall.name)) { messages.push({ role: 'tool', toolCallId: toolCall.id, content: `错误:工具 ${toolCall.name} 不在允许列表中。你只能使用只读工具查询数据。如果需要修改数据,请引导用户使用"质控"命令。` }); errorCount++; continue; } try { const result = await this.tools.executeTool(toolCall.name, toolCall.args); messages.push({ role: 'tool', toolCallId: toolCall.id, content: JSON.stringify(result) }); trace[i].observation = JSON.stringify(result).slice(0, 200); } catch (error) { errorCount++; messages.push({ role: 'tool', toolCallId: toolCall.id, content: `工具调用失败: ${error.message}` }); } } // ⚠️ 幻觉熔断:连续 2 次工具调用失败 if (errorCount >= 2) { return { success: false, content: '抱歉,我遇到了一些技术问题,无法获取相关信息。请稍后重试或联系管理员。', trace }; } } return { success: false, content: '抱歉,我无法在有限步骤内完成这个查询。请尝试更具体的问题。', trace }; } /** * ⚠️ 保存 Trace 到数据库(仅供 Admin 调试) */ async saveTrace( projectId: string, userId: string, query: string, result: ReActResult ): Promise { await prisma.iitAgentTrace.create({ data: { projectId, userId, query, trace: result.trace, tokenUsage: this.tokenCounter, success: result.success, createdAt: new Date() } }); } private getReadonlyToolDefinitions() { return this.tools.getToolDefinitions() .filter(t => this.READONLY_TOOLS.includes(t.name)); } private formatToolDescriptions(toolNames: string[]): string { return toolNames.map(name => { const def = this.tools.getToolDefinition(name); return `- ${name}: ${def?.description || ''}`; }).join('\n'); } } ``` --- ## 6. 引擎集成示意 ```typescript // 初始化 const hardEngine = new HardRuleEngine(); const toolsService = new ToolsService(redcapAdapter, difyClient); const softEngine = new SoftRuleEngine(toolsService); const sopEngine = new SopEngine(hardEngine, softEngine, pgBoss); const reactEngine = new ReActEngine(toolsService); // 在 ChatService 中使用 async handleMessage(userId: string, message: string) { const intent = await this.intentService.detect(message); if (intent.type === 'QC_TASK') { // 走 SOP 引擎 return await this.sopEngine.run(skillConfig, data); } else if (intent.type === 'QA_QUERY') { // 走 ReAct 引擎 const result = await this.reactEngine.run(message, context); // ⚠️ 只返回 Final Answer,不返回中间思考 await this.reactEngine.saveTrace(projectId, userId, message, result); return result.content; } } ``` --- **文档维护人**:AI Agent **最后更新**:2026-02-05