医疗人工智能的Harness Engineering:面向安全、可控与合规的大模型系统工程(五) 第五章 Harness 核心组件设计:上下文、工具与记忆5.1 引言:从推理到行动的编排层推理服务为医疗 AI 提供了生成文本的能力,但仅有“生成”远远不足以支撑临床决策的复杂性。一个可信的医疗 Agent 必须在正确的时刻获取正确的信息,调用可靠的外部工具进行验算与查询,并记住当前对话的进展以避免矛盾或遗漏。这正是 Harness 核心编排层的职责:上下文管理、工具调用、状态记忆。三者协同作用,将模型的统计性语言能力转化为符合临床工作流的可控行为。在传统的大模型应用中,开发者往往通过拼接字符串来构造提示词,通过if-else或简单的函数调用机制来触发工具,并将历史对话全量塞入上下文窗口。这种“自由式”构建方式在医疗场景中充满风险:未经验证的患者数据可能被错误地注入提示词,工具调用缺少参数校验和审计,而对话状态的混乱可能导致对禁忌症的反复询问或遗漏关键信息。Rust 的类型系统为上述每个环节提供了强制约束的可能。本章将展示如何利用代数数据类型(enum)、trait 抽象、typestate 模式和资源管理,设计一个可证明其部分安全属性的 Agent 编排核心。我们并不会替代模型本身,而是为模型的行为提供一个严谨的舞台——台上演员(模型)可以即兴发挥,但舞台边界、剧本框架和道具(工具)的安全使用,必须由 Harness 严格执行。5.2 上下文管理:最小化原则的类型化实施上下文(Context)是模型生成回复时所依赖的全部信息,通常包括系统指令、患者基本数据、当前对话历史以及检索到的外部知识。医疗 AI 的首要上下文原则是最小必要:只暴露当前任务绝对需要的数据,且所有数据在进入上下文前必须经过脱敏与合规性检查。5.2.1 上下文的结构化定义摒弃字符串拼接,我们用 Rust 结构体来声明上下文组件,并在序列化时统一注入提示词模板。这样,编译器可以保证我们永远不会忘记添加某个字段,也不会意外地将未脱敏数据混入。pubstructConversationContext{pubsystem_instructions:SystemInstructions,pubpatient_summary:DeidentifiedPatientSummary,pubcurrent_task:ClinicalTask,pubhistory:VecAnnotatedMessage,pubretrieved_knowledge:VecKnowledgeSnippet,}pubstructSystemInstructions{pubrole:String,pubguidelines:String,pubdisclaimer:String,}pubstructDeidentifiedPatientSummary{pubage_range:AgeRange,pubgender:AdministrativeGender,pubchief_complaint:String,// 不含姓名、ID等PHI}pubenumClinicalTask{Triage,DifferentialDiagnosis,TreatmentSuggestion,MedicationReview,}DeidentifiedPatientSummary的类型确保了其中不包含PatientNameRaw,而只能存在PatientNameDeidentified或一般文本。这通过 NewType 和 typestate 实现(见第三章),上下文模块的构造器只接受已脱敏类型。5.2.2 动态上下文窗口管理LLM 的上下文窗口有限,且 token 消耗与延迟和成本正相关。在长对话或多文档检索场景中,我们必须动态选择最相关的内容纳入上下文,而丢弃或摘要较远的历史。Harness 可以依据规则和启发式算法进行裁剪。我们可以定义一个ContextBuilder,它累加各种消息,并在最终构建时根据 token 预算进行截断。pubstructContextBuilder{system:OptionSystemInstructions,patient:OptionDeidentifiedPatientSummary,task:OptionClinicalTask,history:VecAnnotatedMessage,knowledge:VecKnowledgeSnippet,token_budget:usize,}implContextBuilder{pubfnnew(token_budget:usize)-Self{...}pubfnwith_system(mutself,sys:SystemInstructions)-Self{self.system=Some(sys);self}pubfnwith_patient(mutself,pat:DeidentifiedPatientSummary)-Self{self.patient=Some(pat);self}pubfnwith_task(mutself,task:ClinicalTask)-Self{self.task=Some(task);self}pubfnadd_history(mutself,msg:AnnotatedMessage)-Self{self.history.push(msg);self}pubfnadd_knowledge(mutself,snip:KnowledgeSnippet)-Self{self.knowledge.push(snip);self}pubfnbuild(self,tokenizer:dynTokenCounter)-ResultConversationContext,ContextError{// 计算各部分 token 数,按优先级裁剪:system - patient - task - knowledge - historyletmutused=0;// ... 裁剪逻辑 ...Ok(ConversationContext{...})}}通过将ContextBuilder与一个 traitTokenCounter配合,我们可以精确控制最终发送给推理服务的 tokens 数量。该 trait 可以封装本地 tokenizer(如 Hugging Face tokenizers 的 Rust 绑定)或远程 tokenizer 服务。5.2.3 信息的注入与防止泄漏ConversationContext最终会被转换为VecMessage发送给推理服务(第四章)。转换过程中,我们可以在消息中加入明确的角色标识和来源引用,例如:[SYSTEM] 你是一个临床决策支持助手... [PATIENT SUMMARY] 年龄范围: 45-54岁, 性别: 男, 主诉: 持续胸痛3小时... [EVIDENCE] 根据2024年ACC指南,对于ST段抬高型心肌梗死... [USER] 该患者是否需要紧急介入?所有这些插入都由 Rust 代码格式化完成,且只有DeidentifiedPatientSummary字段能被访问。这种“中间表示”确保模型不会意外获得原始 PHI,因为原始 PHI 根本不存在于该类型的值中。5.3 工具调用:安全的医疗能力扩展工具调用(Tool Calling)使 Agent 能够突破模型的知识截止日期和推理局限,执行药物相互作用查询、计算 eGFR/BMI、检索最新指南等。但每一个工具的执行都可能产生副作用(如数据库写入)或消耗资源,必须被严格管理。Harness 的工具系统必须提供:强类型接口、参数校验、超时控制、审计记录、权限检查,并确保工具调用结果在被模型消费前已经过验证。5.3.1 工具定义与注册我们定义一个Tooltrait,所有工具实现该 trait:#[async_trait]pubtraitTool:Send+Sync{/// 工具的唯一标识符,用于与模型约定fnname(self)-str;/// 工具的描述,用于生成 function calling 的 schemafndescription(self)-str;/// 输入参数 schema (JSON Schema 格式)fninput_schema(self)-serde_json::Value;/// 执行工具,接收 JSON 参数,返回 JSON 结果async