# **IIT Manager Agent V2.2:工具泛化与灵活性提升指南** **核心观点:** 灵活性来自“工具的设计粒度”,而不是“连接协议(MCP)”。 **解决方案:** 通过设计 3-5 个“瑞士军刀型”通用工具,替代 100 个“特种螺丝刀型”专用工具。 ## **1\. 直击灵魂:MCP Server 真的更灵活吗?** 你担心 V2.1 方案(Service Class)不够灵活,让我们来做个对比: | 维度 | MCP Server 方案 | V2.1 Service Class 方案 | 真相 | | :---- | :---- | :---- | :---- | | **新增工具** | 在 Server 进程里写代码 \-\> 重启 Server \-\> Agent 发现新工具 | 在 Service 类里写代码 \-\> 重启 Node.js \-\> Agent 发现新工具 | **工作量完全一样**。都要写代码逻辑。 | | **工具调用** | Agent 发 JSON \-\> 网络传输 \-\> MCP 执行 | Agent 发 JSON \-\> 内存调用 \-\> Service 执行 | **V2.1 更快**。没有网络开销。 | | **Agent 自主性** | LLM 看到的是 tool definitions (JSON) | LLM 看到的也是 tool definitions (JSON) | **LLM 根本分不清**你是 MCP 还是 Service。 | **结论:** * **MCP 的灵活性在于“解耦”**:比如你可以把 REDCap 工具给别人用,或者在运行时动态加载新的 Server。 * **V2.1 的灵活性在于“敏捷”**:代码都在一个工程里,改起来最快。对于 2 人团队,**改代码 \> 调协议**。 ## **2\. 你的痛点:是不是要写很多工具?** 你担心:“如果有 100 个检查点,我是不是要写 check\_age, check\_gender, check\_bmi 一百个工具?” **绝对不需要!** 这就是我说的\*\*“工具泛化”\*\*。 ### **错误的设计(特种螺丝刀)** 写一堆具体的工具,导致 Service 类无限膨胀: * get\_patient\_age(id) * get\_patient\_history(id) * check\_informed\_consent(id) * ... ### **正确的设计(瑞士军刀)** 只写 **3 个** 通用工具,就能覆盖 99% 的场景。Agent 会通过**参数**来体现灵活性。 #### **工具 1:read\_clinical\_data (全能查询器)** * **功能**:读取 REDCap 里的任意数据。 * **参数**:record\_id (患者ID), fields (想查什么字段), event (哪个访视)。 * **灵活性**: * 查年龄?Agent 传 fields: \["age"\]。 * 查病史?Agent 传 fields: \["medical\_history"\]。 * **你只需要写这一个函数,Agent 就可以查任何东西。** #### **工具 2:eval\_logic (逻辑计算器)** * **功能**:处理复杂的数值计算或逻辑判断(利用 json-logic)。 * **场景**:Agent 算不清 BMI,可以调这个工具帮它算。 #### **工具 3:manage\_issue (通用反馈器)** * **功能**:包括提出质疑、发送提醒、记录日志。 * **参数**:type (QUERY | NOTIFY | LOG), message (内容), severity (严重程度)。 ## **3\. 代码实现:如何实现“瑞士军刀”?** 在你的 ToolsService.ts 中,只需要实现这几个通用方法。 // backend/src/modules/iit-manager/services/ToolsService.ts export const UNIVERSAL\_TOOL\_DEFS \= \[ { name: "read\_clinical\_data", description: "通用数据查询工具。当需要检查患者的某项指标时使用。", parameters: { type: "object", properties: { record\_id: { type: "string" }, // 关键:让 Agent 自己决定查什么字段 fields: { type: "array", items: { type: "string" }, description: "需要查询的REDCap变量名列表,如 \['age', 's\_cr', 'ae\_desc'\]" } }, required: \["record\_id", "fields"\] } }, { name: "manage\_issue", description: "通用问题处理工具。用于提出质疑或发送通知。", parameters: { type: "object", properties: { record\_id: { type: "string" }, action\_type: { type: "string", enum: \["RAISE\_QUERY", "SEND\_WECHAT"\] }, message: { type: "string" } }, required: \["record\_id", "action\_type", "message"\] } } \]; export class ToolsService { constructor(private redcapAdapter: RedcapAdapter) {} async execute(name: string, args: any) { switch (name) { case 'read\_clinical\_data': // 一个函数,通过参数变化应对无限需求 return await this.redcapAdapter.exportRecords({ recordId: args.record\_id, fields: args.fields // \<--- 动态字段 }); case 'manage\_issue': if (args.action\_type \=== 'RAISE\_QUERY') { return await this.saveShadowQuery(args); } else { return await this.sendWechat(args); } } } } ## **4\. 场景演示:Agent 的自主性如何体现?** 哪怕你只提供了 2 个工具,Agent 依然表现得像个专家,因为**Skill (Prompt)** 在指导它如何组合使用这些工具。 ### **场景 A:检查肝功能** * **Skill 配置**: "instruction": "检查 ALT 和 AST 是否超过正常值 3 倍。" * **Agent 思考**: "我要查肝功能。" * **Agent 行动**: 调用 read\_clinical\_data(fields=\['alt', 'ast'\])。 * **系统响应**: {"alt": 150, "ast": 40} * **Agent 判断**: "ALT 150 \> 40\*3。违规。" * **Agent 行动**: 调用 manage\_issue(action\_type='RAISE\_QUERY', message='ALT异常')。 ### **场景 B:检查入组年龄** * **Skill 配置**: "instruction": "确保患者年龄 \> 18。" * **Agent 思考**: "我要查年龄。" * **Agent 行动**: 调用 read\_clinical\_data(fields=\['age'\])。 * **系统响应**: {"age": 16} * **Agent 行动**: 调用 manage\_issue(action\_type='RAISE\_QUERY', message='年龄不合规')。 **看到没有?** 你**没有写** check\_liver 和 check\_age 两个函数。 你只写了一个 read\_clinical\_data。 是 **Agent 的大脑(Prompt)** \+ **通用工具(Tool)** 实现了无限的灵活性。 ## **5\. 总结** 1. **MCP Server 不是灵活性的来源**:它只是一个连接标准。如果你的工具设计得烂(粒度太细),用 MCP 也一样累。 2. **通用参数是王道**:不要写“查年龄”的工具,要写“查字段”的工具。让 Agent 填参数,这就是最大的灵活性。 3. **V2.1 极简版足够强**:只要你的 ToolsService 按照“瑞士军刀”的思路去设计,2 个开发人员维护 100 个项目的逻辑完全没有压力。 **行动建议:** 按照本文档的 **第 3 节**,把你的 ToolsService 重构一下,只保留 3-5 个通用方法。然后去测试一下 Agent 是否能聪明地自己填参数。