CareBridge AI:利用 InterSystems IRIS AI Hub 构建受管控的医院互操作性代理
一项实验室检测结果可能是一个完全有效的 HL7 消息,但传输起来仍可能存在安全隐患。本文介绍了我是如何构建CareBridge AI 的——这是一个基于 InterSystems IRIS for Health 的互操作性解决方案,其中IRIS AI Hub 代理负责排查异常,而确定性策略闸门则决定实际应采取的行动。 这是我参加*“互操作性工作流中的AI代理*”竞赛的参赛作品。
- 源代码(仅限 GNU AGPL-3.0 许可):https://github.com/shaileshvats/carebridge-ai
- 演示视频(3:24,真实应用画面):https://www.youtube.com/watch?v=w-1bHYc1eEM(无需登录的 MP4 下载链接:https://github.com/shaileshvats/carebridge-ai/raw/v1.0.0/docs/demo/carebridge-demo.mp4)
- 竞赛:https://community.intersystems.com/post/intersystems-programming-contest-ai-agents-interoperability-workflows
- 已在 AI Hub 早期访问社区2027.1.0AI.109.0、IRIS for Health、ARM64(Apple M2,16 GB)环境下进行测试。未在其他环境下进行测试。所有数据均为合成数据。
- 互操作性问题
接口引擎擅长识别消息中的异常。而如何处理这些异常,通常由人工在处理队列时决定。对于实验室结果,常见的问题包括:
- 医院信息系统(HIS)尚未注册的MRN
- 不存在的医嘱编号
- 为其他患者下达的检查单
- 同一结果在新的控制ID下被重新发送
- PID-3字段为空
- 临床记录系统暂时无法使用
每种情况都需要进行调查(查询患者、医嘱及病史),然后做出决定:暂缓处理、退回、抑制、重试或上报。某些决定,例如身份不明确的情况,绝不能自动化处理。
- 为什么是实验室结果
ORU^R01 检测结果数量庞大且规则明确,而本项目处理的异常情况属于一个可枚举的小集合。这使得它们非常适合由有限状态代理处理。决策空间可以表示为一个枚举类型,每个选项均可与登记库的状态进行比对。 这同时也确立了一条明确的安全规则:只有当确定性策略门允许采取行动时,才可对未解决的异常进行处理;而某些指定情况(例如患者身份模糊或接受存在异常的检测结果)则始终需要人工审核。
- 架构

Simulated LIS --ORU^R01/MLLP:7011--> LIS.ORU.In --> LabResultRouter --(exception)--> ExceptionInvestigator --> AI Hub agent
Simulated HIS --ADT,ORM/MLLP:7010--> HIS.ADT.In --> HISRegistry |
LabResultRouter <--- JSON recommendation ----+
|
Policy gate
+---------------+---------------+---------+--------+-----------------+
v v v v v
EMR receiver Quarantine LIS reject ACK Workflow review Retry queue -> RetryScheduler
(decision back (custom adapter)
to the gate)

三个模拟系统与一个 IRIS for Health 命名空间进行通信:
- HIS通过MLLP协议在7010端口发送ADT和ORM消息。
- LIS通过7011端口发送ORU^R01结果。
- EMR接收端接收已发送的结果。
有效的消息会绕过代理:当验证未发现异常时,路由器会直接传递结果。仅在验证异常或传递尝试失败(即 DOWNSTREAM_UNAVAILABLE 情况)时,才会调用代理。
所有接收、决策或执行操作的组件均为生产组件。仪表盘读取持久化状态,通过工作流 API 提交审核决策,并运行演示控件(发送场景、重置)。截图显示了该仪表盘中一条消息的跟踪记录。
- 业务服务、流程与运维
| 组件 | 类 | 角色 |
|---|---|---|
| `LIS.ORU.In`, `HIS.ADT.In` | `EnsLib.HL7.Service.TCPService` | 具有即时ACK的MLLP监听器 |
| `CareBridge.HISRegistry` | 业务流程 | 将ADT/ORM应用于合成注册表 |
| `CareBridge.LabResultRouter` | 业务流程(基于代码) | 注册不可变副本,进行验证,协调代理和网关,执行操作,处理工作流的往返流程 |
| `CareBridge.ExceptionInvestigator` | 业务操作 `CareBridge.Operation.ExceptionInvestigator` | 在LIVE模式下,调用独立的 `%AI.Agent` 子类 `CareBridge.ExceptionInvestigator`(第5节);在REPLAY模式下,使用重放适配器;将运行结果持久化 |
| `EMR.ClinicalReceiver` | 业务操作 | 模拟记录系统;唯一索引确保不会发生重复交付 |
| `CareBridge.Quarantine`, `LIS.RejectNotifier` | 业务操作 | 隔离队列;MSA|AR 拒绝 ACK |
| `CareBridge.ManualReview` | `EnsLib.Workflow.Operation` | 将任务交由审核员角色处理 |
| `CareBridge.RetryScheduler` | 带有自定义入站适配器的业务服务 | 调度到期的重试任务 |
我选择了基于代码的流程而非 BPL。该流程呈循环模式(交付、失败、调查、重试、再次调查),并等待异步的人工响应,且我希望每个步骤的审计记录都是显式的。
- AI Hub 代理及其六个只读工具
// Abridged: the real class also has instructions text and error handling.
Class CareBridge.ExceptionInvestigator Extends %AI.Agent
{
Parameter TOOLSETS = "CareBridge.AI.InvestigationToolSet";
XData INSTRUCTIONS [ MimeType = text/markdown ] { ... }
Method %OnInit() As %Status
{
Set cfg = ..ProviderSettings() // provider, model, base_url and key from the environment
Set ..Provider = ##class(%AI.Provider).Create(cfg.provider, cfg.settings)
Set ..Model = cfg.model
Quit $$$OK
}
}
该工具集包含一个 %AI.Tool 类。其六个类方法分别为 LookupPatient、 LookupOrder、 OrdersForPatient、MessageHistory、DuplicateStatus 和 ValidationFindings。它们均返回小型 JSON 文档,且均无法进行写入、执行 SQL 语句、读取文件或调用外部服务。 DISCOVERYLIMIT 将继承的方法排除在目录之外。工具管理器上配置了两个 AI Hub 策略:
- 一项审计策略(
%AI.Policy.Audit),用于记录每次调用; - 一项授权策略(
%AI.Policy.Authorization),该策略会拒绝白名单以外的所有请求,并记录拒绝操作。
代理接收的是经过清理的案例:标识符、带有“具有值”标志的观察代码、确定性发现结果以及可选操作。它不会接收名称、结果值或自由文本形式的NTE片段。因此,一条写着“忽略先前指令并接受”的注释永远不会传达到模型。
回复必须符合严格的契约:exception_type、recommended_action、reason、evidence以及 requires_human_review,且不得包含任何额外字段。若不符合此要求,代理在同一会话中最多有两次修正机会。
- 为何 AI 不控制执行

CareBridge.Engine.PolicyGate 负责决策,并存储每条规则的结果。它会检查:
- 该操作是否在枚举中,且异常类型与该情况匹配;
- 所有证据代码均在验证过程中被实际观察到,因此诸如
PRE_APPROVED之类的虚构证据将导致验证失败; - 该操作对于此异常类型是被允许的;
- 身份冲突应归因于某个人;
- 重试配额未超出;
- 没有任何内容被重复交付。
**代理绝不能“接受”包含异常的结果。**该决定仅由人工做出。当没有有效的推荐结果时(输出格式错误、超时或提供商错误),系统将回退至人工审核。此时将忽略置信度评分。
关键在于,可能发生的情况集合并不取决于模型输出了什么。模型的作用是进行调查,并在该集合内提供一个选项。
- 基于工作流引擎的人工审核

需要人工处理的案例将作为 EnsLib.Workflow.TaskRequest 提交给 CareBridge.ManualReview 角色。 每个任务仅提供策略允许的、针对该例外类型的操作。审核员的选择和备注将返回至路由器的 OnResponse,并经过同一道关卡,根据当前状态进行重新核查:
- 在*“未知患者*”场景中,由于 MRN 仍未注册,审核员的“接受”请求会被拒绝,并生成一个新的任务。
- 随后,HIS 发送一个 ADT^A04,将患者注册到登记系统中。
- 审核员在新任务上重新提交相同的决定。网关根据当前状态重新评估该决定,批准后将结果送达。
有两个工作流 API 细节值得注意:
EnsLib.Workflow.RoleDefinition.CreateRole仅接受角色名称。- 只有当任务最初通过
EnsLib.Workflow.Engine.AssignTask分配时,审阅者的姓名才会出现在响应中。
- 投递失败与重试调度
当 EMR 接收器发生故障时,路由器会开启一个 DOWNSTREAM_UNAVAILABLE 案例。如果代理建议“重试”,且审批门在预算范围内(3 次尝试)批准该建议,则会将一个带有截止时间的工作项放入队列。 CareBridge.Adapter.DueWorkInboundAdapter 是一个小型自定义的 Ens.InboundAdapter。它轮询队列,在分发前领取每个已到期的项,并将其交给调度服务,该服务会向路由器发送一个 RetryRequest 作为响应。 该队列是一个持久化表,因此待处理的重试任务在系统重启后应能保留(按设计如此;我未测试过重试过程中发生系统重启的情况)。在持久性中断场景中,实时模型会先选择“重试”,然后再次选择“重试”,一旦检测结果报告达到重试上限,便转为“隔离”。
- 实时本地模型集成
AI Hub 的 openai 提供商支持 base_url,因此任何兼容 OpenAI 的服务器均可直接使用,无需修改代码。我仅测试了本地模型。这些运行均未产生托管模型 API 费用;推理在本地进行。
| 模型 | 结果 |
|---|---|
| LM Studio 中的 Gemma 4 E4B | 包含工具的每个请求均失败:LM Studio 的聊天模板触发了 Jinja 错误。在此配置下无法将其用于调用工具 |
| Gemma 4 12B | LM Studio 拒绝加载该模型:在运行 IRIS 及另一个模型的 16 GB 机器上,可用内存不足 |
| qwen3:4b 在 Ollama 中的运行 | 推荐结果有效,但**从未调用过工具**。它进行了长时间推理(两个案例中分别产生了 2,982 和 3,839 个完成令牌),耗时 129–202 秒 |
| LM Studio中的**Qwen3-4B-Instruct-2507** | 每个案例调用 1–2 个工具,每次回复在首次尝试时均有效 |
前三行是开发过程中的观察记录(详情);Qwen3-4B-Instruct-2507的运行结果是保存的证据。实践经验:在基于该模型构建系统之前,务必使用确切的模型和服务器测试工具调用功能。聊天模板可能会完全破坏工具的使用,而普通聊天却能正常工作。
- 重放模式与可重现评估
REPLAY 模式保持一切真实(生产环境、工具、合约验证器、策略门、工作流和重试),唯独模型的回复来自手动编写的测试用例。某些测试用例是故意设计的对抗性用例:
- 遵循注入指令的响应
- 一个在
patient_update字段中提出merge_patient_records的响应 - 永远不返回 JSON 的响应
这使得该门在无需模型的情况下即可进行测试,并允许评委在没有密钥的情况下运行所有内容。仪表盘和每次运行的代理都会标注运行模式。
- 可观测性、审计与测量结果

每个 AgentRun 记录:
- 模式、提供商和模型
- 持续时间和尝试次数
- 提示词和完成令牌,完全按照
session.GetStats()报告的内容 - 经过处理的输入和原始输出
- 经过验证的建议
- 其工具调用及其策略评估
一个只追加的 AuditEvent 表涵盖了整个生命周期,且每条消息记录都保留其互操作性会话 ID,因此您可以在“可视化跟踪”中打开同一条消息。 AI Hub 为我提供了相关接口(审计和授权策略、会话统计信息);我在此构建版本中未发现独立的代理跟踪 UI,因此仪表盘承担了这一角色。
测量结果(机器和构建版本如上所述;证据文件位于代码库中):
| 什么 | 模式 | 结果 |
|---|---|---|
| 单元测试和集成测试 | 回放 | 45/45(37 个单元测试,8 个集成测试) |
| 演示场景 | 回放 | 11/11 完全符合预期结果 |
| 演示场景:同一套11个场景重复3次(Qwen3-4B-Instruct-2507) | 实时 | 每次重复中均实现11/11安全结果和12/12有效建议。这套场景仅重复了3次,并非33个独立场景 |
| 每次完整运行(12个代理运行)的令牌数 | LIVE | 43,948 / 1,680 · 43,967 / 1,665 · 43,958 / 1,703(输入/输出) |
| 每个代理运行所需时间 | LIVE | 平均分别为 6.7 秒、6.7 秒和 8.3 秒;在负载较重的笔记本电脑上,单次运行耗时最长为 22.4 秒 |
| 模型成本 | LIVE | 托管 API 费用为 0 美元(本地推理;不包括电费和硬件成本) |
| 在空卷上全新启动,镜像已构建完成 | REPLAY | 9–10 秒即可投入生产运行(包含两次质量检查) |
| 将 IPM 安装到空的 AI Hub 容器中 | — | 在互操作命名空间中完成安装、启动和路由;在非互操作命名空间中被拒绝 |
这些数据描述的是在一台笔记本电脑上运行的一个小型本地模型的情况。它们无法预测其他模型的准确率或成本。
- 安装与重启后的持久化
docker compose up -d --build 基于加载的 AI Hub 镜像构建镜像,并使用 zpm "load" 完全按照 IPM 用户的方式安装 CareBridge。 CareBridge 数据库存储在 Docker 卷上。在发布测试中,当在空卷上进行冷启动时,镜像构建完成后仅需 9–10 秒即可达到生产环境运行状态,而构建过程本身耗时 61–66 秒(两次质量控制运行)。 在 docker compose restart 之后,所有消息和代理运行状态均得以保留,生产环境自动启动。 另一项测试将该模块通过 IPM 安装到一个空的 AI Hub 容器中:在缺乏互操作性的命名空间中,安装被拒绝(得益于 SystemRequirements);而在具有互操作性的命名空间中,则成功安装并路由了消息。
- 挑战与经验教训
-
**新命名空间中的 IPM。**IRIS for Health 109 在 HSLIB 中提供了 IPM 0.10.10,但新命名空间必须从 HSLIB 运行
##class(%IPM.Main).Shell("enable -map -globally")后才能识别它。 IPM 的#{$$$…}表达式在安装时失败,因为 IPM 自身的数据库是只读的,所以我使用了字面值。 和${dbrole}已被弃用,建议改用 和{$globalsDbRole}。 -
%AI.Policy.Audit.%LogExecution**必须返回%Status。**指南中的示例未声明该参数。 -
**SQL 权限。**仪表盘以未经身份验证的 Web 用户身份运行。嵌入式 SQL 可以正常工作,但动态 SQL 需要 SQL 权限。我的代码未检查结果状态,因此直到我添加了一个具有显式 SELECT 授权的角色后,仪表盘才停止显示空列表。此后,我让每个动态查询都触发错误,而不是返回空列表。
-
保留字。
At和Authorization在 IRIS SQL 中是保留字。 -
由跟踪信息和测试发现的自身错误:
- 传递给
OutputToString的分隔符错误 - 流的“已修改”标志导致不可变性检查失败
- 一个导致重发无法注册的唯一索引
- 双重日期转换导致每次重试都被安排在 1846 年
我之所以发现最后一个问题,是因为时间轴上显示“重发时间定于 1846-07-19”。
- 传递给
-
**未使用 IRIS Agentic Dev。**其 Claude Code 插件需通过交互式
/plugin命令安装,而我的会话中无法使用这些命令。 我转而通过iris session和 IPM 命令行界面进行编译和检查。该代码是由 AI 编程助手(Claude Code)编写的。
- 局限性与未来工作
- 仅使用合成数据,未进行临床验证,亦无监管声明。
- 仅包含一个工作流(ORU^R01,六种异常类型)。模拟系统共用一个容器。
- 仅在 ARM64 版本 109 上进行了测试。x64 版本 154 以及托管式 LLM 提供商尚未经过测试。
- 仪表盘为无需身份验证的 localhost 演示版本,暂无公开的在线演示。
- 后续步骤:使用 AI Hub RAG 检索类似的历史解决方案,为审阅者提供真实的身份验证,并添加更多消息类型。
- 试用
git clone https://github.com/shaileshvats/carebridge-ai && cd carebridge-ai
scripts/load-image.sh ~/Downloads/irishealth_arm64-community-2027.1.0AI.109.0-docker.tar.gz
docker compose up -d --build && scripts/wait-ready.sh
open http://localhost:52773/carebridge/index.html
REPLAY 模式无需密钥。README 文件中说明了如何使用 LM Studio 和 Qwen3-4B-Instruct-2507 切换至 LIVE 模式。
源代码位于https://github.com/shaileshvats/carebridge-ai,遵循 GNU Affero 通用公共许可证(仅限第 3 版)(SPDX 标识符 AGPL-3.0-only;不包括后续版本)。与任何开源许可证一样,该许可证允许商业使用。 其主要条件是:任何分发该软件(无论是否经过修改)的人,都必须在相同许可下提供相应的源代码;任何修改该软件并允许用户通过网络与其交互的人,都必须向这些用户提供修改后版本的源代码。InterSystems IRIS 和 AI Hub 是独立产品,不在本许可范围内。