AWS 医疗 Agent 实践:让应用掌握权限
作者以合成患者数据构建 AWS 演示,将可预测步骤交给固定工作流,仅让 Agent 处理需要动态决策的部分。医生身份与访问权限由应用控制,即使 Agent 尝试读取其他患者记录也会被拒绝。
AI 深度解读
给 AI 接上病历查询和转诊工具,最难的不是让它会调用 API,而是让它越界时真的办不成。作者在 AWS 上做了一个临床助手演示:模型尝试查询另一位医生负责的患者,应用直接拒绝,随后它继续处理当前患者。
这套方案的结论很明确:固定步骤交给工作流,有变化的任务才交给 Agent;身份、权限、状态转换由代码控制,最终决定留给医生。对正在做 AI 编程助手或业务 Agent 的程序员,它最有参考价值的部分,是怎样把这些原则落实成模型无法绕过的限制。
固定流程为什么不需要 Agent
作者是牙科外科医生出身,也是 Arthurite Integrated 的 Lead Solutions Engineer。这套系统用于 AWS Community Day West Africa 2026 演讲,患者均为虚构,问诊音频由 Amazon Polly 生成,不能当作真实医疗产品的验证结果。
系统分成两半:前半段把录音变成经过医生审核的病历,后半段才让 Agent 查记录、补信息,并提出一次转诊或复诊建议。
上传、转写、收集结果、人工审核,这些步骤顺序固定,无须模型决定下一步。
浏览器先取得有效期五分钟、限定单个 S3 key 的预签名 URL,音频直接上传 S3,不经过 Lambda 或 API Gateway。随后 API 调用 StartMedicalScribeJob,由 AWS HealthScribe 生成带说话人角色的转写和病历草稿。
浏览器每秒查询一次 encounter,后端最多每五秒查一次 HealthScribe,节流时间戳保存在 DynamoDB。作者采用轮询,是因为原文所用的批处理任务不发送 EventBridge 事件,流式任务才支持。
录音左右声道分别对应医生和患者,通过 ChannelIdentification 和 ChannelDefinitions 指定身份。没有牙科模板,就使用 HISTORY_AND_PHYSICAL。草稿中的句子还关联转写片段 ID,医生点击即可查看依据。
作者测得,132 秒音频处理约需 76 秒,点击 Start 后约 85 秒看到草稿。按文中 us-east-1 每音频分钟约 $0.10 的标价,这段录音约花 $0.22。这是单段演示的结果,没有延迟分布或更多样本。
人工审核也确实发现了问题:医生只描述智齿上方牙龈瓣发炎,草稿却写出「复发性冠周炎」,还归入既往病史。诊断可能合理,但它是模型推断,分类也错了。因此,Agent 只能读取医生批准的病历,不能直接读取音频或原始转写。
模型可以申请,权限必须由应用裁决
Agent 使用 Amazon Bedrock 托管的 gpt-oss-120b,通过 Converse API 在 agent-worker Lambda 中循环调用工具,最多 10 轮,每次模型调用限时 60 秒。每轮模型响应、工具请求和结果都先写入 DynamoDB timeline,再继续执行。
模型配置是一个 Terraform 变量,IAM 也据此限制可调用的模型。但更关键的限制,是模型不能提供可信身份。
医生通过 Amazon Cognito 登录,ID token 中的 custom:doctor_id 可读不可写。API Gateway 的 JWT authorizer 验证 token,handler 还要检查它确实是 ID token:同一客户端的 access token 也可能通过 authorizer,却没有医生 ID。
创建 encounter 时,应用保存已验证的医生身份。以后调用工具,身份由 worker 补入:
// 模型只提供工具输入,例如 { patientId: "PT-003" }。
// 身份来自已保存的 encounter,绝不来自模型输出。
const result = await invokeTool(fn(), {
encounterId,
doctorId: encounter.doctorId,
input: toolUse.input,
});
要看的是 doctorId: encounter.doctorId:它取自应用保存的数据。即使模型在 input 中塞入 doctorId,严格 schema 也会拒绝这个额外字段。
作者还让工具校验和模型看到的 JSON Schema 共用同一份 zod 定义,避免两套接口描述逐渐不一致:
// 工具用这份 schema 校验,模型的 toolSpec 也由它生成。
export const getPatientRecordInput = z.strictObject({ patientId: patientIdSchema });
export const toolSpecs: Tool[] = toolNames.map((name) => ({
toolSpec: {
name,
description: DESCRIPTIONS[name],
inputSchema: { json: jsonSchema(TOOL_INPUTS[name]) },
},
}));
这里关键是 z.strictObject,以及 TOOL_INPUTS[name] 同时服务于描述和执行校验。不过,参数合法不代表访问合法,工具 Lambda 仍须检查患者是否分配给当前医生。
演示中,医生要求查询患者兄弟 PT-003 的记录。模型发起 get_patient_record,worker 补入 DR-A,工具发现患者属于另一位医生,返回 DENIED,并记录 NOT_ASSIGNED。不存在的患者 ID 也返回同样的拒绝结果,避免借接口探测哪些患者存在。
模型拒绝越权可以加分,应用拒绝越权才是权限控制。
规则、暂停和审批,都要有明确的执行路径
病历是否包含必要章节,作者交给普通代码检查,模型只决定何时调用 check_note_completeness。
检查覆盖主诉、病史、既往病史、检查、评估和计划,并区分两种容易混淆的情况:病历没写过敏史,但记录里已有过敏信息,就提示医生而不阻塞;药物列表为空,却不能当作医生确认「当前未用药」,仍需补问。
演示患者 Ada 的青霉素过敏没有出现在问诊中,这条规则从记录里找出它,Agent 随后将其加入转诊草稿。作者也遇到过执行顺序问题:模型同轮调用 get_patient_record 和完整性检查,后者没有读到前者刚返回的结果,误报过敏信息缺失。修复方式是让检查读取同轮先前产生的结果。
需要医生回答时,request_clinician_input 保存整个对话和待回答问题 ID,将状态设为 NEEDS_INPUT,然后结束 Lambda。收到回答后,API 写入 timeline,把状态改回 AGENT_RUNNING,再以 resume 模式启动 worker,恢复对话并补上工具结果。
状态变化使用条件写入,重复点击或顺序错误返回 409;异步 worker 关闭自动重试,避免静默重跑。
最终审批则有独立通道。create_referral 和 schedule_recall 只能创建 PENDING_APPROVAL 草稿,其 IAM 角色没有更新 Actions 表的权限。行动 ID 由 encounter ID 派生,配合 attribute_not_exists 限制每次就诊只能提出一个行动。
批准和拒绝由独立的 approve-action Lambda 处理,必须携带对应医生的 ID token,再通过 DynamoDB 事务同时更新行动与 encounter。Agent 的角色只能调用三个工具 Lambda,够不到审批函数。测试还通过 SimulatePrincipalPolicy 检查它能否调用审批或更新 Actions 表,两项都应被拒绝。
我的判断
我认同这套职责划分:固定流程先写清楚,明确规则落到代码,模型负责存在变化的选择。相比把权限要求写进 system prompt,身份注入、条件写入和 IAM 限制提供了可检查的执行约束。
但「完整性检查有正确答案」需要限定范围。代码能判断章节和关键词是否出现,不足以证明内容在医学上完整、准确。原文没有给出检查器的误报率、漏报率或临床对照数据。
人工批准病历也只是减少未经审核内容进入 Agent 的机会。作者认为患者说出的恶意指令不会直接进入模型,这条输入路径确实被切断了;但记录、医生回答仍会进入上下文,不能据此认定提示注入已经解决。
模型行为测试的经验值得保留:有模型拒绝跨患者查询,有模型尝试后被拒,还有模型直接忽略请求;某模型把未提供的转诊紧急程度补成「soon」,因此工具增加 NOT_STATED;temperature 设为 0,也曾让某模型稳定地出错。作者没给模型间对照数据,这些是调试观察,不能推广为默认采样一定更安全。
这套真实 AWS 栈上的场景测试属于冒烟测试,不是模型基准。安全架构也不等于合规或临床安全。作者列出的生产缺口还包括 AWS BAA、HIPAA 合格服务、客户管理的 KMS 密钥、VPC endpoints 或 PrivateLink、Bedrock Guardrails、持续评估、真实 EHR 集成及临床验证。
能拿来做什么
- 给 Node Agent 接业务工具时,从已验证的会话或任务记录注入用户身份,再由工具检查资源归属;用同一份 zod schema 生成工具描述并校验输入。
- 给前端设计人工确认流程时,将「草稿」「等待补充」「等待审批」做成明确状态;长时间等待保存后退出,恢复时接续原任务,并处理重复点击。
- 给创建、修改类工具设计参数时,为信息缺失提供 NOT_STATED 这类合法值,避免必填枚举迫使模型猜测;把最终生效动作放到独立授权路径。
- 更换模型或修改 prompt 前,重跑正常完成、缺失信息、越权、拒绝和模型失败场景。若遇到 AccessDeniedException 且提示「not available for this account」,按作者的排障经验先查 Service Quotas:模型可用性查询通过,也可能仍有 tokens-per-minute 配额为 0 的问题。