Features: - Add V2.9 enhancements: Cron Skill, User Profiling, Feedback Loop, Multi-Intent Handling - Create modular development plan documents (database, engines, services, memory, tasks) - Add V2.5/V2.6/V2.8/V2.9 design documents for architecture evolution - Add system design white papers and implementation guides Architecture: - Dual-Brain Architecture (SOP + ReAct engines) - Three-layer memory system (Flow Log, Hot Memory, History Book) - ProfilerService for personalized responses - SchedulerService with Cron Skill support Also includes: - Frontend nginx config updates - Backend test scripts for WeChat signature - Database backup files Co-authored-by: Cursor <cursoragent@cursor.com>
20 KiB
20 KiB
IIT Manager Agent 核心引擎实现指南
版本: V2.9
更新日期: 2026-02-05
关联文档: IIT Manager Agent V2.6 综合开发计划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 完整实现
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<string, any>): 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<string, any>): boolean {
return this.run(rules, data).length === 0;
}
}
2.3 JSON Logic 常用表达式
// 范围检查
{ "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 完整实现
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<SoftRuleResult> {
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 类型定义
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 完整实现
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<SopResult> {
// 创建或恢复任务记录
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<SopResult> {
// 挂起任务,不再占用 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<void> {
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 安全约束
// 工具白名单: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 完整实现
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<ReActResult> {
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<void> {
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. 引擎集成示意
// 初始化
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