资讯动态

电子病历数据库智能体评测:从对话到查询执行的NL2SQL基准测试

发布时间:2026/10/10 18:19:17 来源:尧图企业网站定制
最近几个月一直在折腾一件事把大模型智能体接到电子病历数据库上让医生用自然语言就能查数做分析。项目推进到一半发现最卡人的不是模型能不能写SQL而是我们根本说不清楚智能体到底行不行。于是花了六周搭了一套面向电子病历数据库智能体的用户与工具交互基准测试也就是标题里那个从对话到查询执行的评估体系。这篇文章把整个设计思路、踩坑过程、指标定义和实测结果都摊开聊给想做医疗数据库智能体或类似垂直领域NL2SQL评测的朋友一个可参考的样本。我默认你至少接触过大模型API、Function Calling和关系型数据库但不用是医疗信息化专家。电子病历库确实特殊但评测框架这套方法论搬到金融、政务数据上同样能跑。1. 内容整体设计与思路拆解1.1 为什么通用NL2SQL基准不够用之前我们试过拿现成的NL2SQL数据集来做能力摸底效果很惨。问题不在SQL生成能力本身而在任务形态完全不同。通用数据集给一句查询、给一个Schema、要一条SQL这是单轮短链路。但电子病历场景的真实交互是用户先说一句模糊需求智能体需要反问澄清或者自己去查数据字典、查Schema、查历史查询示例最后可能还要对执行结果做二次验证。举个例子医生问帮我看看最近血糖控制不好的病人有多少这句话里最近是多久血糖控制不好在电子病历库中到底对应糖化血红蛋白大于7%还是空腹血糖多次超标如果没有澄清和工具检索机制再强的SQL生成模型也只能瞎猜。通用基准测试不会暴露这种问题但我们的基准测试会把用户与工具交互这个中间层单独拎出来评估。1.2 评估的是智能体而不是SQL生成器这个基准测试和传统NL2SQL评测最大的区别是它把智能体视为一个需要调度工具的自主系统而不只是一个文本生成器。传统评测输入是自然语言数据库Schema输出是SQL语句然后比对SQL正确性。我们的框架输入是一段多轮对话上下文输出是一个完整的执行轨迹其中包括调用了哪些工具、每个工具的入参出参、最终生成的SQL、执行结果、以及给用户返回的自然语言解释。这也意味着评测不能只看终态正确性还要看过程合理性。比如一个智能体为了回答近三个月血糖控制不佳的患者人数反复调用了五次Schema查询工具虽然最终SQL写对了但这种交互效率放在真实医院环境里不可接受。所以我们把工具调用次数、单次执行的冗余度、澄清轮数都纳入了评分体系。1.3 数据集与真实场景的映射逻辑一开始我们天真地以为直接把某三甲医院的真实查询日志拿来做测试就行结果发现查询日志里大量SQL是信息科的人对着数据字典硬凑出来的自然语言和SQL之间根本没有一对一的映射关系。后来我们改了策略先由临床医生口述需求再由工程师把它翻译成SQL最后用大模型把SQL反推成多种自然语言变体构成一轮问题对。这样既能保证查询和真实医学语义对齐又能生成足够多的提问变体来模拟不同表达习惯。比如同一个医学查询近半年使用胰岛素但血糖仍未达标的2型糖尿病患者会生成半年内打胰岛素血糖还高的二型糖尿病人有哪些最近6个月用胰岛素治疗但糖化血红蛋白高于7.5的T2DM患者请统计胰岛素治疗后HbA1c不达标的患者数量这三个不同复杂度的问题。这种问题变体分布的构建我们花了将近一半的项目时间但它是整个基准测试可信度的根基。1.4 为什么把工具交互单独建模医生使用电子病历数据库的方式远比给我跑一条SQL复杂。真实场景里用户会先问这个表里的数据到几月份了再问糖化血红蛋白在哪个表然后才说能不能按科室统计一下控制率。每个中间问题都对应一个工具操作。所以我们的测试集把每一条任务都拆成了多轮对话链每个对话轮次都标注了预期工具调用类型、预期参数和预期回复格式。这样设计的好处是能定位错误发生在哪一层。如果智能体在意图识别阶段就歪了后续SQL生成再准也没用。如果是工具参数传错了我们也能明确说是检索层的问题。我见过很多团队评测智能体直接report一个端到端准确率出问题后根本分不清是该调Prompt还是该调检索逻辑这就是评测设计没拆够细的典型症状。2. 核心细节解析与实操要点2.1 任务类型定义与难度分层我们定义了七种核心任务类型覆盖电子病历查询的高频场景。为了让评测结果更有分析价值每种任务又被标记了简单/中等/困难三档难度。难度划分标准主要看三个维度涉及表的数量、查询条件的复杂性、是否需要常识推理或数值计算。任务类型典型问题涉及工具常见难度点单表筛选统计某科室出院患者总数Schema查询SQL执行字段名映射、过滤条件翻译跨表关联聚合糖尿病患者的平均住院天数Schema查询SQL执行表连接关系、聚合函数选择时间序列查询近半年每月门诊量变化Schema查询SQL执行结果解释时间字段格式化、环比计算否定条件查询未服用抗凝药的心房颤动患者数据字典SQL执行布尔逻辑、空值处理单位换算查询血糖高于7.8mmol/L的患者数据字典SQL执行单位体系、参考范围差异多轮澄清查询先问数据范围再发起统计对话澄清Schema查询SQL执行上下文指代消解异常解释查询为什么本月指标突然下降SQL执行结果解释归因推断、防幻觉这张表不是拍脑袋定的它来自我们对某医院信息科过去半年查询工单的复盘。你会发现真正高频的任务反而集中在前面几类而大家最喜欢用大模型炫技的复杂多跳推理反而占比不高。这提醒我们做评测不能只顾着堆难度任务分布要和真实使用频率对齐。2.2 工具集设计原则与接口规范我们的智能体一共暴露了六个工具每个工具都是独立可调用的Function。设计原则就一条工具粒度要小到能被独立评测又要大到单个操作有完整业务含义。query_schema(table_name?): 获取数据库表结构支持按关键词过滤query_dictionary(term): 查询数据字典获取字段含义、单位、编码映射search_sql_examples(question): 检索与当前问题语义相似的查询示例execute_sql(sql): 在只读副本上执行SQL返回表格式结果validate_result(claim, evidence): 校验自然语言结论和SQL执行结果是否一致ask_clarify(question): 向用户发起澄清询问接口上我们统一采用JSON格式入参出参便于录制轨迹和回放评测。这里有个细节值得提execute_sql必须强制走只读副本不能直连生产库。我们为此专门搭了一套从生产库脱敏同步的测评库同步时对所有患者标识字段做不可逆哈希日期偏移随机化诊断文本用映射表替换成近义词。这样既保证了业务逻辑的完整性又不会把患者真实信息带进测试环境。2.3 基准测试的运行管线评测不是把问题丢给智能体然后看结果就完了。我们设计了一条五步管线每一步都有独立的日志和缓存任务输入标准化把测试集里的JSON任务导入评测框架生成统一的运行实例记录任务ID、难度标签、预期工具调用序列。智能体驱动执行评测框架作为运行时环境把用户问题发给智能体智能体自主决定调用哪些工具框架负责返回工具真实执行结果。轨迹录制完整记录每一步的输入输出、工具返回、模型中间推理并打上时间戳。这一步是后面排查问题的最重要依据。自动评分用执行结果比对和语义相似度双重机制打分生成结构化评估报告。错误归类对未通过的任务进行自动聚类结合人工复核定位错误环节。如果你准备做类似的评测项目我强烈建议在第一步就设计好缓存机制。同一个任务的多次运行工具返回结果尤其是Schema查询和示例检索完全可以用缓存命中来加速否则评测跑一轮要几小时迭代Prompt时根本等不起。2.4 人工标注与质检的流程测试集的质量直接决定评测的可信度。我们有专职的标注小组成员包括一名临床医生、两名数据分析师、一名DBA。标注流程是先生成候选查询对由医生审核医学语义是否准确然后DBA审核SQL在目标Schema下是否最优最后分析师统一审核自然语言与SQL的对应关系。质检层面我们采用了双人独立标注加仲裁机制。所有任务都有问题变体、目标SQL、预期工具调用序列、预期回答要点四个字段。双人一致率大约在92%不一致的全部由医生和DBA共同仲裁。最终入选测试集的任务一共有三百二十条每条都附带难度标签和维护说明。3. 实操过程与核心环节实现3.1 电子病历数据库的模拟建设我们没有直接使用真实生产库而是先用模拟环境构建了一个缩略版电子病历数据库。这个库包含五张核心表患者基本信息表、住院就诊记录表、诊断记录表、检验结果表、用药记录表。字段命名刻意保留了很多医疗信息系统的历史遗留风格比如mix了拼音缩写和英文缩写有的字段还带单位后缀。模拟数据填充时要注意保持临床逻辑的一致性。比如一个患者的诊断记录里有2型糖尿病那么他的检验结果里糖化血红蛋白的数值分布就要符合糖尿病患者特征用药记录也要与治疗路径基本吻合。如果这一步偷懒生成的数据逻辑混乱智能体很容易学到错误的关联模式评测结果也就失真了。我们是用某医院脱敏后的统计分布特征来生成模拟数据的先算均值方差和相关矩阵再用程序合成确保表间关系合理。3.2 Prompt与工具调用协议的关键代码智能体核心采用了ReAct风格的推理循环模型根据当前任务决定下一步动作执行工具后把结果追加到上下文再继续推理。为了让不同模型的评测公平我们没有用任何特定厂商的Agent框架而是自己用标准API调用搭了一个轻量级运行时。核心循环的伪代码如下def agent_loop(user_input, tools, max_steps10): messages [{role: system, content: SYSTEM_PROMPT}, {role: user, content: user_input}] for step in range(max_steps): response llm.chat(messages) action parse_action(response) if action.type final_answer: return action.content if action.type tool_call: tool_result execute_tool(action.tool_name, action.arguments) messages.append(response_message) messages.append({role: tool, content: tool_result}) else: # 如果模型吐出了不可解析的action记录并终止 return error(unparseable_action) return error(max_steps_exceeded)System Prompt里我们写了五条铁律只能使用给定工具execute_sql之前必须确认表结构涉及时间范围必须先确认时间字段口径结果解释不能脱离SQL执行结果有歧义时优先调用ask_clarify而不是猜测。实际跑下来铁律只对模型有约束意义真正兜底的是评测框架里的规则校验器。3.3 两阶段Schema检索与上下文压缩一个不算新的经验但很管用不要让智能体一次性看到全库Schema。电子病历库动辄几百张表全量塞进上下文既超窗口又引入大量噪声。我们做的是两阶段检索第一阶段用用户问题里的名词去匹配表名和字段名召回候选表第二阶段才加载候选表的完整结构。匹配用的是向量检索加关键词倒排的混合方法实测能把Schema信息的token占用缩减70%以上。这里有个容易踩的坑第一阶段召回如果漏了表后面生成的SQL必错而且这种错误非常隐蔽因为SQL本身可能在语法上完全合法。我们的缓解方案是在execute_sql执行前加一道工具校验检查SQL引用的表是否都在已加载的Schema里如果不在就自动触发一次补充检索。3.4 评测执行的一次完整记录我们从测试集中选一条中等难度任务做样例展示。任务描述是统计一下2023年第四季度办理出院的心内科患者中做过糖化血红蛋白检验且结果高于7%的人数比例。这条任务难点在于需要关联三张表涉及时间范围过滤还有一次比例计算。智能体的执行轨迹大致是先调用query_dictionary确认糖化血红蛋白对应的字段和单位再调用query_schema查找住院就诊表和检验结果表的关联键之后调用execute_sql执行中间查询发现结果里有部分患者没有检验记录导致分母不一致于是重新写SQL用子查询限定有检验记录的住院患者最终生成了正确结果。整个过程调用了五次工具耗时约42秒没有启用澄清工具。这个记录被标记为通过同时工具调用效率得分偏高因为每一步都是必要的。3.5 模型对比与核心数据结果我们评测了三类模型通用大模型A、通用大模型B、在医疗数据上做过指令微调的模型C。统一温度设0每任务跑三次取最优。结果如下模型查询执行准确率可执行SQL比率平均工具调用次数多轮澄清正确率通用A62.5%88.1%6.254.3%通用B68.1%91.2%5.861.2%医疗微调C74.7%93.6%5.167.8%看完数据有个值得玩味的结论模型C虽然在SQL正确率上领先但在可执行SQL比率上并没有拉开绝对差距说明垂直领域微调的主要收益集中在理解医学语义和字段映射上而不是SQL语法泛化上。另外所有模型在时间序列类任务上准确率都低于均值15个百分点这块是共性短板值得后续单独攻。4. 实测中的常见问题与排查技巧实录4.1 Schema盲区工具查得到模型看不见项目早期遇到最频繁的问题是模型生成的SQL引用了不存在或不相关的字段。排查轨迹发现模型确实调用了query_schema工具但工具返回的字段列表里目标字段是存在的模型却没能把它用进SQL。比如数据字典里字段叫hb_gly_ref模型写了个hba1c直接报错。这本质上不是检索失败而是模型在长上下文里注意力分配不足漏看了关键字段。我们试过两种缓解方案一是把Schema返回格式改成一个字段一行、类型和注释用制表符分隔减少格式噪声二是在query_schema返回结果后追加一段模型可判读的摘要提示目标字段疑似为某某注意包含关系。第二种方案提升最明显把这类错误降低了约三成。4.2 上下文污染历史工具返回值干扰后续决策多轮交互的副作用是历史工具返回值会污染后续判断。一个很典型的场景医生先问了门诊量工具返回了一堆时间格式各异的统计值接着问住院患者血糖控制情况模型生成的SQL里居然把上一轮门诊表的时间字段也带进来了导致跨表连接条件错乱。排查时看轨迹才发现是历史消息里残留的字段名诱导了模型。后来我们在每次新任务开启时做一次上下文裁剪只保留对话中用户的原始问题工具返回一律截断后隐藏模型需要具体数值时再重新调工具查询。这样做会让工具调用次数略有上升但整体准确率净提升接近5个百分点。4.3 医疗查询中的否定表达陷阱电子病历查询里大量存在否定条件比如没有用过胰岛素的患者未进行过冠脉造影检查不伴肾功能不全。LLM在处理否定时表现很不稳定尤其是当目标字段是枚举类型、且空值也代表某种业务含义的时候。比如未做过糖化血红蛋白检验在库里既可能是检验记录表里没有对应行也可能是检验结果字段为NULL两种写法完全不同。我们最终在Prompt里加了一条硬性规则如果用户询问的是否定条件必须先调用query_dictionary确认目标字段的取值逻辑检查空值是否具有业务含义禁止直接使用IS NULL这一条规则把否定类查询的准确率从51%提升到68%。4.4 时间范围模糊与日期口径不一致医生口头说最近三个月和信息技术上理解的90天内经常对不上。更麻烦的是电子病历里的时间字段往往分成入院时间、出院时间、检验执行时间、报告时间等多个口径同一条查询换一个口径结果就完全不一样。我们测试集中专门设计了这类问题结果所有模型的首次正确率都不到一半。实测有效的做法是强制智能体在时间过滤前显示地调用query_schema确认时间字段含义并在SQL里把时间口径字段用注释标注出来。我们还给execute_sql增加了一个参数叫require_timefield_confirmation置为true时如果SQL中出现时间过滤条件框架会自动把对应表的全部时间类字段返回给模型二次确认。这个机制虽然拖慢了执行速度但把时间类任务的准确率拉高了近20%我认为非常值。4.5 工具返回结果太长导致的截断与幻觉执行一个聚合查询后返回的表格可能有一万多行如果直接把全部结果塞进上下文很快撑爆模型窗口还会让后面的结果解释环节开始编造数据。我们在execute_sql工具里内置了结果集大小感知能力超过200行就自动分页截取同时返回一个结果总行数列级摘要的统计块。模型需要用完整数据时可以再调用一个传页码的工具取后续数据。刚开始有人觉得多此一举但实测后发现这一步对防止幻觉至关重要。模型看到截断后的前二十行数据很容易产生总数就是二十这类错误推断。加上统计块之后模型的回答中涉及数值的引用错误率显著下降。4.6 评测中容易误判的三个坑这里专门说说评测框架本身会产生的误判因为很多人会忽略。第一个坑是SQL执行结果比对时数据库返回顺序不稳定导致漏判。我们统一对结果集排序后再比对排序键选择主键或所有列拼接。第二个坑是空结果被误判为错误结果。有些查询在数据集中本来就没有匹配记录SQL写对了也返回空表评分逻辑必须区分执行成功但结果为空和执行失败。我们在结果里加了query_status字段只有status为SUCCESS且无结果时才判定为有效空结果。第三个坑是模型输出JSON格式不稳定评测框架解析失败直接打零分。后来统一做了一个容错解析器支持从代码块中提取JSON并自动修复未转义字符。4.7 低风险条件下的场景扩展评测框架搭建完以后我们又扩展了两种低风险场景。一是写操作风险评估允许智能体生成UPDATE或DELETE语句但执行前必须通过一个只读的Explain计划检查器检查是否有完整的WHERE条件且影响行数在人工阈值内。不实际执行只评分能否生成合规的写操作SQL。二是结果解释简洁度评估要求智能体把SQL执行结果转成一段医生能看懂的话并由医生标注打分。这两个扩展都还在验证中但已经发现一个有意思的规律模型生成的SQL越复杂结果解释越容易过度自信两者需要分开评估。5. 工具选型与工程化经验补充5.1 运行时框架选型自研还是用现成框架我们调研了一轮市面上的Agent框架结论是评测场景下自研轻量运行时比直接套框架更合适。原因是评测需要精确控制每一步的输入输出还需要录制完整轨迹第三方框架往往会封装掉一部分内部日志出了问题不方便定位。但如果是做产品原型用现成框架确实更快。我们最后采用的方案是产品原型用一个成熟Agent框架驱动评测时换成自研运行时两者通过统一的工具接口规范对接。这样既保证产品迭代速度又保证评测的透明度。5.2 数据库隔离与评测安全医疗数据安全是底线任何评测都不能碰生产库。我们搭了三层隔离生产库到脱敏同步库、脱敏同步库到评测执行库、评测执行库到沙箱结果库。脱敏同步脚本每天定时跑所有直接标识符做哈希脱敏间接标识符如出生日期只保留年份诊断文本替换为脱敏映射表里的等价表述。评测执行库只开放只读账号且配置了单查询超时和内存限制。这些设置看起来繁琐但能在合规和效率之间取得平衡宁可慢一点也不能冒数据泄露风险。5.3 可复现性与版本管理评测社区里一个通病是结果不可复现换个环境跑分数就漂移。我们从一开始就上了一些措施模型版本锁定到具体快照依赖库全量锁版本测试集每一版都有变更记录评测脚本用哈希确认工具返回内容未被修改。还有一个细节是随机数种子固定因为模型采样在温度大于0时会有随机性我们统一把温度设0并对每个任务跑三次取最优同时记录三次结果的一致性。这些措施保证了团队内不同成员复跑同一评测结果偏差能控制在1.5个百分点以内。5.4 结合需求侧持续迭代的思路评测搭建出来不是为了做一次就完事。我们给它设计了一个增量更新机制每个月从真实查询工单里抓取新的查询模式走一遍医生确认语义、DBA确认SQL、分析师构建变体的流程扩充测试集。优先加入那些现有模型错误率高但临床上很重要的任务类型。这样基准测试才能持续反映真实场景而不是变成一道永远不变的题库。从我个人的经验看如果测试集三个月没有新增一条用例那它大概率已经和实际需求脱节了。6. 最后的几点个人体会这套基准测试做完给我最大触动的不是模型在某个指标上又涨了多少而是把交互链路拆开评测这件事本身的价值。原来我们只觉得智能体效果不好拆开以后才发现大量错误发生在工具选择、字段确认和上下文管理这些外围环节真正SQL生成错误占比反而没那么高。这说明接下来做优化的重心应该放在Agent框架和工具设计上而不是一味换更大的模型。再分享一个小技巧录制轨迹一定要从项目第一天就开始做。我们早期觉得轨迹文件太多占磁盘定期清理过一次后来排查某个持续存在的错误时找不到当时的完整记录被迫重新跑了整轮评测白白浪费两天时间。轨迹日志是评测里最贵的资产宁可多存不要删。如果后续要扩展这个项目我会优先做两件事一是把测试集变成社区可共享的开放格式让不同机构能拿同一套任务跑结果对比二是加入更多交互形态比如语音输入和医生主动纠正的中间反馈。从对话到查询执行这条路还很长但先把度量尺子做准后面的路才好走。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑