医疗 Function Calling:辅助问诊工具链的安全校验层设计

📅 2026/7/22 13:40:55
医疗 Function Calling:辅助问诊工具链的安全校验层设计
医疗 Function Calling辅助问诊工具链的安全校验层设计一、当 AI 替你查药典一次误调用就能致命Function Calling 给了大模型调用外部工具的能力在医疗场景下这个能力尤其危险。想象一个场景医生问这个患者对青霉素过敏推荐替代抗生素Agent 调用药物数据库工具查询但如果查询参数被错误解析如把不过敏识别为过敏返回的药物禁忌就会被颠覆。医疗 Function Calling 的工程难点不在于怎么调用工具而在于调用之前怎么校验调用之后怎么兜底。传统的 API 网关安全校验API Key、限流、参数类型检查在医疗场景下远不够用——需要一个专门针对语义安全的校验层。二、三层安全校验架构从语法到语义的层层把关这里设计一个三阶段校验模型每个阶段拦截不同层面的风险三个阶段的分工明确语法校验只看格式是否正确这是 LLM 最容易出错的地方业务规则校验看有没有权限和数据是否存在语义安全校验看组合起来有没有风险。最后还有一道输出审核防止工具返回的结果本身包含不当内容。四、边界分析与 Trade-offs白名单 vs 黑名单策略医疗 Function Calling 必须用白名单策略。只有预定义的工具可以被调用任何不在白名单中的工具调用都直接拒绝。但白名单需要定期维护新工具上线前必须通过安全评审。一个折中方案是将工具分为只读和写操作两类只读工具可以放宽白名单规则写操作工具如修改患者档案必须经过二次确认。校验延迟 vs 安全完整性三层校验全部同步执行单次调用增加 50-100ms 延迟。对于实时问诊场景这个延迟基本无感医生打字和思考的时间远大于 100ms。但如果需要批量校验如自动生成 200 个检验申请单可以用异步预校验来减少等待时间。拒绝之后的降级策略工具被拦截时不应该让 LLM假装没看到继续回答。正确的做法是返回一个结构化的拒绝原因给 LLM让它可以生成对用户友好的解释。例如抱歉您查询的某种药物记录未在授权数据库中。建议您通过医院 HIS 系统核实。这样用户不会感知到底层发生了什么只看到一个合理的降级响应。三、Go 代码实现Function Calling 安全校验层package medical import ( context encoding/json fmt strings ) // ToolCategory 工具分类 type ToolCategory string const ( CatReadOnly ToolCategory readonly // 只读查询 CatWrite ToolCategory write // 写操作 CatHighRisk ToolCategory highrisk // 高危操作 ) // ToolDefinition 工具定义 type ToolDefinition struct { Name string json:name Description string json:description Category ToolCategory json:category // 允许的参数白名单 AllowedParams []string json:allowed_params // 是否需要人工确认 RequireConfirm bool json:require_confirm } // ToolCall LLM 生成的工具调用请求 type ToolCall struct { ToolName string json:tool_name Parameters map[string]interface{} json:parameters } // ValidationResult 校验结果 type ValidationResult struct { Passed bool json:passed Stage string json:stage // 失败时记录在哪个阶段 Reason string json:reason // 拒绝原因 Fallback string json:fallback // 降级话术 } // SafetyValidator 安全校验器 type SafetyValidator struct { whitelist map[string]*ToolDefinition // 药物禁忌组合简化示例 drugContraindications map[string]map[string]bool } // NewSafetyValidator 初始化校验器 func NewSafetyValidator() *SafetyValidator { sv : SafetyValidator{ whitelist: map[string]*ToolDefinition{ query_drug_info: { Name: query_drug_info, Description: 查询药品基本信息, Category: CatReadOnly, AllowedParams: []string{drug_name, drug_id}, RequireConfirm: false, }, check_drug_interaction: { Name: check_drug_interaction, Description: 检查药物相互作用, Category: CatReadOnly, AllowedParams: []string{drug_ids}, RequireConfirm: false, }, update_patient_record: { Name: update_patient_record, Description: 更新患者健康记录, Category: CatWrite, AllowedParams: []string{patient_id, field, value}, RequireConfirm: true, }, }, drugContraindications: map[string]map[string]bool{ penicillin: { penicillin_derivative: true, }, }, } return sv } // Validate 执行三层安全校验 func (sv *SafetyValidator) Validate(call *ToolCall) *ValidationResult { // 阶段 1语法校验 if call.ToolName { return ValidationResult{ Passed: false, Stage: 语法校验, Reason: 工具名称为空, Fallback: 系统暂不支持该操作请尝试其他查询。, } } if _, err : json.Marshal(call.Parameters); err ! nil { return ValidationResult{ Passed: false, Stage: 语法校验, Reason: fmt.Sprintf(参数序列化失败: %v, err), Fallback: 查询参数格式异常请重新描述您的需求。, } } // 阶段 2业务规则校验 toolDef, exists : sv.whitelist[call.ToolName] if !exists { return ValidationResult{ Passed: false, Stage: 业务规则校验, Reason: fmt.Sprintf(工具 %s 不在白名单中, call.ToolName), Fallback: 该功能暂未开放请联系管理员。, } } // 校验参数是否在允许范围内 for paramName : range call.Parameters { if !sv.isAllowedParam(toolDef, paramName) { return ValidationResult{ Passed: false, Stage: 业务规则校验, Reason: fmt.Sprintf(参数 %s 不在允许列表中, paramName), Fallback: 检测到不合规的查询参数已拦截。, } } } // 高危工具需要二次确认 if toolDef.Category CatHighRisk !sv.hasConfirmFlag(call.Parameters) { return ValidationResult{ Passed: false, Stage: 安全校验, Reason: 高危操作需要人工确认, Fallback: 该操作涉及患者数据变更请确认后重试。, } } // 阶段 3语义安全校验 if call.ToolName check_drug_interaction { if sv.hasContraindication(call.Parameters) { return ValidationResult{ Passed: false, Stage: 语义安全校验, Reason: 检测到已知药物禁忌组合, Fallback: 检测到潜在药物相互作用风险建议药师审核后再查询。, } } } return ValidationResult{Passed: true} } func (sv *SafetyValidator) isAllowedParam(def *ToolDefinition, name string) bool { for _, allowed : range def.AllowedParams { if strings.EqualFold(allowed, name) { return true } } return false } func (sv *SafetyValidator) hasConfirmFlag(params map[string]interface{}) bool { v, ok : params[_confirmed] if !ok { return false } confirmed, ok : v.(bool) return ok confirmed } func (sv *SafetyValidator) hasContraindication(params map[string]interface{}) bool { drugIDs, ok : params[drug_ids] if !ok { return false } ids, ok : drugIDs.([]interface{}) if !ok || len(ids) 2 { return false } // 检查任意两两组合是否有禁忌 for i : 0; i len(ids); i { drugA : fmt.Sprintf(%v, ids[i]) interactions, exists : sv.drugContraindications[drugA] if !exists { continue } for j : i 1; j len(ids); j { drugB : fmt.Sprintf(%v, ids[j]) if interactions[drugB] { return true } } } return false }五、总结医疗 Function Calling 的安全校验不是锦上添花而是生死攸关。三层校验模型语法→业务→语义覆盖了从格式错误到医学禁忌的完整风险链路。白名单策略是底线药物禁忌数据库的准确性和时效性决定了语义校验的实际效果。最重要的是校验失败后的降级设计——用户的体感应该是系统帮我规避了风险而不是系统出错了。这份代码省掉的是后期可能发生的医疗事故值得在每个 Function Calling 调用链上都套一层。