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>
162 lines
6.2 KiB
Markdown
162 lines
6.2 KiB
Markdown
# **IIT Manager Agent V2.3:健壮性设计与最佳实践**
|
||
|
||
**核心目标:** 在极简架构下,解决 LLM 调用工具时的幻觉、参数错误和无结果问题。
|
||
|
||
**适用场景:** 2人团队,Node.js Service Class 架构。
|
||
|
||
## **1\. 核心风险分析:LLM 会犯什么错?**
|
||
|
||
在让 Agent 调用 read\_clinical\_data 等工具时,通常会发生以下三类错误:
|
||
|
||
| 错误类型 | 场景举例 | 后果 |
|
||
| :---- | :---- | :---- |
|
||
| **参数幻觉** | LLM 调用 read\_data(id="P001", fields=\["gender"\]),但 REDCap 里字段名叫 sex。 | API 返回空或报错,用户觉得 AI 很蠢。 |
|
||
| **意图漂移** | 用户问:“今天天气怎么样?” LLM 试图去 REDCap 查天气。 | 工具调用失败,浪费 Token。 |
|
||
| **格式错误** | LLM 返回的 JSON 少了一个括号,或者参数类型不对(字符串给了数字)。 | 程序 Crash,前端报错。 |
|
||
|
||
## **2\. 解决方案:三层防御体系 (The Defense Layer)**
|
||
|
||
不需要 MCP Server,我们在 Service Class 里加三层防御即可。
|
||
|
||
### **第一层:模糊映射 (The Translator)**
|
||
|
||
**解决:** 字段名对不上的问题。
|
||
|
||
**原理:** 别指望 LLM 能记住 REDCap 几千个变量名。我们在代码里做一个“模糊翻译器”。
|
||
|
||
// backend/src/modules/iit-manager/services/ToolsService.ts
|
||
|
||
class ToolsService {
|
||
// 1\. 定义常用字段映射字典
|
||
private fieldMapping \= {
|
||
"gender": "sex",
|
||
"sex": "sex",
|
||
"性别": "sex",
|
||
"age": "age\_calculated",
|
||
"年龄": "age\_calculated",
|
||
"history": "medical\_history\_desc",
|
||
"病史": "medical\_history\_desc"
|
||
};
|
||
|
||
async executeTool(name: string, args: any) {
|
||
if (name \=== 'read\_clinical\_data') {
|
||
// 🛡️ 自动纠错逻辑
|
||
const correctedFields \= args.fields.map(f \=\> {
|
||
// 如果字典里有,就替换;没有就保留原样
|
||
return this.fieldMapping\[f.toLowerCase()\] || f;
|
||
});
|
||
|
||
console.log(\`\[Auto-Fix\] ${args.fields} \-\> ${correctedFields}\`);
|
||
|
||
return this.redcapAdapter.exportRecords({
|
||
...args,
|
||
fields: correctedFields
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
### **第二层:容错重试 (The Self-Correction Loop)**
|
||
|
||
**解决:** LLM 调用失败直接报错给用户的问题。
|
||
|
||
**原理:** 当工具报错时,**不要直接抛给用户**,而是把错误信息喂回给 LLM,让它重试。这是 Agent 能够“甚至比人更聪明”的关键。
|
||
|
||
// backend/src/modules/iit-manager/agents/QcAgent.ts
|
||
|
||
async function runAgentLoop(prompt: string, maxRetries \= 3\) {
|
||
let history \= \[{ role: 'user', content: prompt }\];
|
||
|
||
for (let i \= 0; i \< maxRetries; i++) {
|
||
// 1\. LLM 思考
|
||
const response \= await llm.chat(history);
|
||
|
||
// 2\. 如果没有调用工具,直接返回结果
|
||
if (\!response.hasToolCall) return response.content;
|
||
|
||
// 3\. 尝试执行工具
|
||
try {
|
||
const result \= await toolsService.execute(response.toolName, response.args);
|
||
|
||
// 4\. ✅ 成功:把结果喂给 LLM,继续思考
|
||
history.push({ role: 'tool', content: JSON.stringify(result) });
|
||
|
||
} catch (error) {
|
||
// 5\. ❌ 失败:触发自我修正机制
|
||
console.warn(\`\[Agent Error\] 工具调用失败: ${error.message}\`);
|
||
|
||
// 关键步骤:告诉 LLM 它错了,让它重试
|
||
history.push({
|
||
role: 'user',
|
||
content: \`系统报错:${error.message}。请检查你的参数(例如字段名是否存在),然后重试。\`
|
||
});
|
||
// 循环继续,LLM 会在下一次迭代中修正参数
|
||
}
|
||
}
|
||
|
||
return "抱歉,系统尝试多次后仍然无法获取数据,请联系管理员。";
|
||
}
|
||
|
||
### **第三层:空结果兜底 (The Fallback)**
|
||
|
||
**解决:** 查不到数据时体验不好的问题。
|
||
|
||
**原理:** 如果 REDCap 返回空,工具层要返回一段“人话”,而不是 null。
|
||
|
||
// ToolsService.ts
|
||
|
||
async execute(name: string, args: any) {
|
||
// ...
|
||
const data \= await this.redcapAdapter.exportRecords(...);
|
||
|
||
if (\!data || data.length \=== 0\) {
|
||
// 🛡️ 友好的空结果返回
|
||
return {
|
||
status: "empty",
|
||
message: \`未找到 ID 为 ${args.record\_id} 的患者数据。请确认 ID 是否正确。\`
|
||
};
|
||
}
|
||
return data;
|
||
}
|
||
|
||
## **3\. 最佳实践:如何应对未知问题?**
|
||
|
||
### **3.1 限制工具的“射程”**
|
||
|
||
不要给 LLM 一个 sql\_query 工具让它随便查库。
|
||
|
||
**最佳实践**:只提供 read\_clinical\_data(record\_id, fields)。
|
||
|
||
* 如果用户问“天气怎么样”,LLM 发现没有 get\_weather 工具,它自己就会回答:“抱歉,我无法查询天气,我只能查询临床数据。”(这是 LLM 的自带能力)。
|
||
|
||
### **3.2 System Prompt 的“紧箍咒”**
|
||
|
||
在 qc\_config.json 的 soft\_instructions 或者全局 System Prompt 中,明确边界。
|
||
|
||
你是一个临床研究助手。
|
||
1\. 你只能回答与【肺癌研究】相关的问题。
|
||
2\. 如果用户问无关问题(如天气、股票),请礼貌拒绝。
|
||
3\. 在调用 \`read\_clinical\_data\` 时,请务必使用准确的英文变量名。如果不确定,请先调用 \`Youtube\` 查看字典。
|
||
|
||
### **3.3 调试与监控 (Admin Portal)**
|
||
|
||
你们已经有了 **运营管理端**。利用起来!
|
||
|
||
* 记录每一次 Agent 的思考过程(Thinking Trace)。
|
||
* 如果你发现 Agent 总是把 age 拼成 agg,就在 **第一层防御(映射字典)** 里加一行配置。
|
||
* 这就是\*\*“运营驱动开发”\*\*,比改代码快得多。
|
||
|
||
## **4\. 结论**
|
||
|
||
**极简方案(Service Class)的灵活性和健壮性完全够用。**
|
||
|
||
* **MCP Server** 只是把错误抛出来,它不会自动修错。
|
||
* **Service Class** 允许你在本地代码里直接写 try-catch 和 Retry Loop,这对 2 人团队来说调试极其友好。
|
||
|
||
**行动指南:**
|
||
|
||
1. **不用担心**用户乱问,Prompt 会拒绝。
|
||
2. **不用担心**参数传错,写一个简单的 Mapping 字典去拦截。
|
||
3. **不用担心**报错,实现 Self-Correction Loop(出错把错误扔回给 AI),让 AI 自己修。
|
||
|
||
**这就是目前 Agent 落地最务实、最抗造的模式。** |