Files
AIclinicalresearch/docs/03-业务模块/IIT Manager Agent/04-开发计划/02-核心引擎实现指南.md
HaHafeng 0c590854b5 docs(iit): Add IIT Manager Agent V2.9 development plan with multi-agent architecture
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>
2026-02-05 22:33:26 +08:00

732 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<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 常用表达式
```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<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 类型定义
```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<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 安全约束
```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<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. 引擎集成示意
```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