
1. 项目缘起当AI开始“编造”你的体检报告去年我们团队接手了一个听起来很“性感”的项目用大模型技术为体检中心开发一个智能报告解读与健康建议生成系统。想象一下用户拿到一堆充满医学术语和异常箭头的体检报告正一头雾水时我们的AI能像一位耐心的私人医生用通俗易懂的语言解释每一项指标的含义评估风险等级并给出生活、饮食、复查等具体建议。这不仅能提升用户体验还能为体检机构创造巨大的附加价值。项目初期进展顺利。我们用高质量的医学知识库和标注好的历史报告数据微调了一个中等规模的模型在内部测试集上表现惊艳解读准确率、语言流畅度都远超预期。然而当我们满怀信心地将第一个版本推向小范围真实用户测试时问题接踵而至。最让我印象深刻的是一位用户的反馈。他的体检报告显示“甘油三酯 2.1 mmol/L”轻度升高但AI生成的解读中赫然写着“您的甘油三酯水平显著偏高已达到3.5 mmol/L属于中度高甘油三酯血症建议立即就医并考虑药物治疗。” 用户被吓得不轻差点直接跑去挂急诊。我们一查原始数据模型完全“捏造”了一个更严重的数值和对应的诊断。这不是个例在血压、血糖、肿瘤标志物等关键指标上模型时不时就会“自由发挥”给出偏离事实的结论。这就是典型的“AI幻觉”——模型基于其训练模式“自信地”生成看似合理但不符合输入事实的内容。更棘手的是工程层面的问题。系统上线后监控面板上频繁出现“服务超时”、“内部错误”的报警。尤其是在早晨体检报告集中上传的高峰期大量请求失败。错误日志里充斥着各种网络抖动、第三方依赖服务不稳定、模型推理超时的记录。一个简单的“重试”机制在初期被我们想得过于简单直接无脑重试导致雪崩差点拖垮整个服务集群。从“AI幻觉”到“重试策略”这两个看似不相关的问题恰恰是AI工程化落地中最常见、也最致命的坑。前者关乎产品的可信度与安全性是生死线后者关乎系统的可用性与健壮性是生命线。本文将结合我们踩过的这些坑拆解背后的原因并分享我们最终采用的、经过实战检验的解决方案。2. 拆解“AI幻觉”不止于数据更是系统工程问题一提到大模型幻觉很多人的第一反应是“数据不够”或“模型不行”。但在体检报告这种强事实性、高严谨性的场景下我们发现幻觉的根源远比想象中复杂它是一个典型的系统工程问题涉及数据、模型、提示工程和校验链路多个环节。2.1 数据层面的“先天不足”与“后天污染”我们的训练数据主要来自两部分一是脱敏后的历史体检报告与人工撰写的解读文本对二是公开的医学教科书、指南和文献。问题恰恰出在这里。首先历史数据存在标注噪声。体检报告的解读本身就有一定主观性不同医生对同一指标波动的表述可能不同。例如对于边缘升高的尿酸有的医生写“建议低嘌呤饮食多饮水3个月后复查”有的可能写“尿酸偏高注意饮食控制”。模型在学习时会模糊地认为这两种表述都对“尿酸升高”这个事实导致生成时在“建议复查”和“提示风险”之间摇摆甚至混合生成。其次公开医学知识的“范围溢出”。我们为了让模型显得更“博学”喂入了大量的通用医学知识。但模型无法精准区分“通用医学事实”与“当前用户特定情况”。例如训练数据中反复出现“甘油三酯高于2.26 mmol/L可诊断为高甘油三酯血症”。当用户指标是2.1时模型可能会“联想”到与之相关的诊断标准、并发症描述并在生成时不小心“补全”了符合诊断标准的假想数值如2.5或3.5以便让后续的论述逻辑自洽。这就是一个由关联知识引发的数值幻觉。注意在医疗、金融等严肃领域盲目追求模型的“博学”和“发散性”是危险的。知识注入必须精确、有边界最好能与事实抽取模块解耦。2.2 模型微调与推理的“过度泛化”我们最初采用标准的全参数微调。这种方式虽然能让模型快速适应“体检报告解读”这个任务格式但也放大了模型固有的“生成偏好”。大模型本质是概率模型其训练目标是预测下一个词的概率。在微调后模型学会了“如何组织一份像模像样的解读报告”但并没有真正学会“严格遵从输入数据”。当输入中存在模糊或边界情况时比如指标处于临界值模型倾向于生成它认为“更完整、更典型”的叙事而这个叙事可能基于训练数据中的常见模式而非当前输入。例如训练数据中“甘油三酯升高”常与“建议戒酒、控制主食”同时出现。即使当前用户的报告未提及饮酒史和主食摄入情况模型也可能“画蛇添足”地生成这些建议。这虽然不是事实性错误但属于不准确的“建议幻觉”同样会降低可信度。2.3 提示工程的“阿喀琉斯之踵”我们最初的提示词Prompt是这样的“你是一名专业的健康管理师。请根据以下用户的体检报告数据生成一份详细、易懂的健康解读与建议报告。” 这个提示词的问题在于指令模糊“详细、易懂”是主观要求没有约束模型必须“严格基于提供的数据”。缺少结构化输出要求模型自由发挥的空间太大。没有防御性指令未明确告诉模型“如果数据不足或不确定应该怎么办”。一个糟糕的Prompt就像给一个知识渊博但粗心的医生一份病历却不告诉他“只看标红的部分”他很可能根据自己的经验侃侃而谈从而偏离重点。3. 我们的“抗幻觉”工程化方案三层过滤网认识到幻觉的多源性后我们放弃了“用一个更牛模型解决所有问题”的幻想转而设计一套工程化的防御体系。这套体系像三层过滤网逐级降低幻觉输出到用户的概率。3.1 第一层输入标准化与知识约束在数据进入模型之前我们增设了一个报告解析与知识约束模块。结构化提取使用一个轻量级的NER命名实体识别模型或规则引擎从原始报告文本中精确提取关键指标如“甘油三酯2.1 mmol/L”、参考范围、异常标志。将其转化为结构化的JSON数据例如{ indicators: [ { name: 甘油三酯, value: 2.1, unit: mmol/L, is_abnormal: true, reference_range: 1.7 } ] }知识库实时检索不把所有医学知识都塞进模型。我们构建了一个本地的、可更新的医学知识图谱包含指标含义、临床意义、临界值、典型建议模板。在生成前系统根据提取的结构化指标从知识库中检索出最相关的、准确的定义和建议模板片段。提示词重构将原始的模糊Prompt改为精确的、包含上下文的Prompt角色你是一名严谨的健康报告解读助手。 任务基于以下**严格准确**的用户体检数据和**提供的医学知识片段**生成解读。 规则 1. 所有结论必须严格基于“用户数据”部分不得自行编造、修改或推测数据。 2. 所有医学解释必须来自“参考知识”部分不得使用外部知识。 3. 如果“用户数据”中某项指标正常则不要讨论其疾病风险。 4. 如果“参考知识”中没有对应建议则该项建议留空或写“请咨询临床医生”。 5. 输出必须为JSON格式包含“指标解读”、“风险评估”、“生活建议”三个字段。 用户数据{structured_data_json} 参考知识{retrieved_knowledge_snippets}这一步的核心是将事实用户数据与知识医学常识分离并通过Prompt进行强绑定极大限制了模型“信口开河”的空间。3.2 第二层模型策略优化与采样控制在模型推理环节我们做了以下调整从微调转向检索增强生成RAG我们大幅减少了全参数微调的范围仅让模型学习“如何组织语言和遵循指令”。核心的医学事实则通过上述的“知识库检索”环节注入RAG模式。这样模型需要“编造”的内容就少了很多它更像一个严谨的“文书”根据给定的事实和资料进行撰写。采用低温度Temperature采样在文本生成时将Temperature参数设置为一个较低的值如0.1或0.2。这意味着模型在选择下一个词时会更倾向于选择概率最高的那个而不是进行更多随机、创造性的探索。这能显著提高输出的确定性和一致性减少“天马行空”的幻觉。代价是文本可能略显枯燥但对于体检报告解读准确性远重于文采。自我一致性采样与投票对于高风险结论如建议就医、提示肿瘤风险我们让同一模型在相同输入下用不同的随机种子生成3-5个版本。然后通过一个简单的规则校验器或另一个轻量模型检查这些版本在关键事实指标数值、结论定性上是否一致。如果不一致则触发第三层校验或直接返回“人工复核”状态。3.3 第三层输出后校验与人工兜底这是最后一道也是最关键的安全阀。规则校验器开发一系列基于规则的校验脚本。例如数值校验生成的文本中提取出的数值是否与输入数据一致利用正则表达式或小型提取模型逻辑校验如果输入数据中所有肿瘤标志物均正常生成文本中是否出现了“癌症风险”相关词汇禁忌校验生成的建议中是否包含了对于特定人群如孕妇、肝肾功能不全者的禁忌建议维护一个禁忌词表关键结论置信度评分用一个经过训练的、更保守的分类模型对生成报告中“风险评估等级”如“正常”、“轻度风险”、“建议就医”等关键结论进行二次判断并与主模型的生成结果对比。如果差异较大则标记为“低置信度”。人工复核队列所有被规则校验器或置信度模型标记的异常报告以及所有包含最高风险关键词如“立即就医”、“疑似”、“恶性”的报告都会自动进入人工复核队列由专业的医学编辑进行审核。系统会清晰标出AI生成的内容与原始数据的对比辅助人工快速判断。通过这三层过滤我们将生产环境中严重的“事实性幻觉”事件降低了95%以上。这套体系的精髓不在于追求100%的零幻觉这在当前技术下几乎不可能而在于通过工程手段将风险可控地收敛到一个极小的、可被人工兜底的范围。4. 重试策略的深水区从“简单重试”到“自适应熔断”解决了“对不对”的问题下一个就是“稳不稳”的问题。我们的服务依赖链包括用户请求接入 - 报告解析服务 - 向量知识库检索 - 大模型API调用 - 结果后处理。其中大模型API无论是自研模型服务还是第三方商用API和向量数据库检索是最不稳定的环节受网络、GPU负载、服务方限流等因素影响极大。我们最初的重试策略非常“朴素”在任何服务调用失败时直接无间隔重试3次。结果在第一次流量高峰时就吃了大亏。4.1 “朴素重试”如何引发雪崩假设大模型服务因为负载过高响应时间从平时的1秒延长到5秒随后开始出现部分超时失败。我们的服务在收到失败后立即重试。第一波用户请求R1超时触发重试R1‘。重试请求R1’和新的用户请求R2同时到达使得大模型服务的并发请求数几乎翻倍。服务负载进一步加重响应时间变得更慢如10秒并产生更多失败。更多失败触发更多重试R1‘’ R2‘形成正反馈循环。短时间内大量重试请求堆积不仅拖垮了大模型服务也耗尽了我们自身服务的线程池资源导致整个服务集群不可用。这就是典型的雪崩效应。此外对于某些非临时性的错误如请求参数错误、权限认证失败、模型不存在重试是毫无意义的只会浪费资源。4.2 设计“智能”重试策略的核心原则我们重新设计了重试机制其核心思想是区分错误类型尊重下游状态保护自身系统。1. 错误分类与重试决策表我们首先对所有可能的错误响应进行分类错误类型示例是否重试理由与策略网络层错误连接超时、连接拒绝、TCP重置是通常是临时性网络抖动适合重试。服务端5xx错误500 Internal Server Error, 503 Service Unavailable谨慎重试下游服务内部错误或过载。需结合熔断器状态和退避策略。客户端4xx错误400 Bad Request (参数错误), 401 Unauthorized, 429 Too Many Requests否400/401是自身请求问题重试无用。429是限流应等待并可能降低请求频率。业务逻辑错误模型内容过滤触发、输入过长否请求本身不合规需修正请求内容。超时错误读超时、写超时是可能是下游处理慢也可能是网络问题。需配合超时时间调整。2. 指数退避与随机抖动对于决定重试的请求绝不能立即重试。我们采用指数退避策略第一次重试等待1秒第二次等待2秒第三次等待4秒……以此类推给下游服务恢复的时间。同时在退避时间上增加一个随机抖动如±0.5秒避免大量失败请求在同一时刻同时重试形成“重试风暴”。3. 熔断器模式这是防止雪崩的终极武器。我们为每一个下游依赖如大模型API设置一个熔断器。它有三种状态关闭请求正常通过并统计失败率。打开当失败率或慢调用率在时间窗口内超过阈值如50%熔断器“跳闸”进入打开状态。此时所有对该依赖的请求立即失败不再尝试调用。这给了下游服务宝贵的恢复时间。半开熔断器打开一段时间如10秒后进入半开状态。允许少量试探请求通过。如果这些请求成功则认为下游已恢复熔断器关闭如果失败则熔断器再次打开并等待下一个周期。熔断器确保了当某个下游服务不可用时故障被隔离不会蔓延到整个系统。我们的服务可以快速失败并可能返回一个缓存值、默认值或友好的降级提示如“系统正在优化请稍后刷新”。4.3 实战配置示例以下是我们使用Go语言和github.com/sony/gobreaker熔断器库结合context超时控制的一个简化版核心逻辑package main import ( context fmt math/rand time github.com/sony/gobreaker ) // 模拟一个不稳定的下游服务调用 func callUnstableService(ctx context.Context, req string) (string, error) { // ... 模拟网络调用可能失败或超时 // 这里简化处理随机返回成功或失败 if rand.Intn(10) 3 { // 30% 失败率 return , fmt.Errorf(service unavailable) } time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) // 随机延迟 return response for req, nil } func main() { // 配置熔断器 cb : gobreaker.NewCircuitBreaker( gobreaker.Settings{ Name: UnstableService, MaxRequests: 5, // 半开状态下允许的最大请求数 Interval: 10 * time.Second, // 关闭状态下的统计周期 Timeout: 15 * time.Second, // 打开状态后的超时时间之后进入半开 ReadyToTrip: func(counts gobreaker.Counts) bool { // 当失败率超过50%且总请求数大于10时触发熔断 failureRatio : float64(counts.TotalFailures) / float64(counts.Requests) return counts.Requests 10 failureRatio 0.5 }, }, ) // 带有重试和退避的调用函数 callWithRetry : func(ctx context.Context, req string, maxRetries int) (string, error) { var lastErr error for i : 0; i maxRetries; i { // 通过熔断器执行调用 result, err : cb.Execute(func() (interface{}, error) { // 设置单次调用的超时 ctxWithTimeout, cancel : context.WithTimeout(ctx, 2*time.Second) defer cancel() return callUnstableService(ctxWithTimeout, req) }) if err nil { return result.(string), nil } lastErr err // 判断错误类型决定是否重试 (此处简化仅模拟) // 如果是熔断器打开错误直接退出 if err gobreaker.ErrOpenState { return , fmt.Errorf(circuit breaker is open: %v, err) } // 如果不是最后一次重试则进行指数退避 if i maxRetries { backoff : time.Duration(1uint(i)) * time.Second // 指数退避 jitter : time.Duration(rand.Intn(500)) * time.Millisecond // 随机抖动 select { case -time.After(backoff jitter): continue case -ctx.Done(): return , ctx.Err() } } } return , fmt.Errorf(after %d retries, last error: %v, maxRetries, lastErr) } // 模拟调用 ctx : context.Background() for i : 0; i 20; i { resp, err : callWithRetry(ctx, fmt.Sprintf(req-%d, i), 2) // 最多重试2次 if err ! nil { fmt.Printf(Request %d failed: %v\n, i, err) } else { fmt.Printf(Request %d success: %s\n, i, resp) } time.Sleep(300 * time.Millisecond) } }这套组合拳实施后服务在面对下游波动时表现得异常“坚韧”。监控图表上以前频繁出现的毛刺和断崖式下跌变成了平滑的曲线即使在大模型服务临时故障时我们的服务也能通过快速失败和优雅降级保持核心功能的可用性。5. 监控、评估与持续迭代将“坑”转化为护城河解决了幻觉和重试这两个核心工程坑并不意味着项目的结束。恰恰相反这只是一个可靠AI系统运行的起点。要让系统在线上持续稳定、可靠地运行必须建立完善的监控、评估和迭代闭环。5.1 可观测性不仅监控错误更要洞察质量我们建立了多层次的监控仪表盘基础设施层CPU、内存、GPU使用率网络延迟服务响应时间P99 P95。这是基础健康度。业务流量层请求量、成功率、失败类型分布幻觉触发规则数、各类4xx/5xx错误数。这能快速定位问题范围。AI质量层核心这是我们自定义的关键。幻觉率采样每天对1%的线上请求进行抽样由医学背景的同事进行人工复核计算“事实性错误”的比例。这个指标是核心生命线。输出稳定性对于同一份标准测试报告每天定时跑一次对比AI输出关键结论如风险等级的一致性。用户反馈收集在报告页面设置“反馈”按钮“内容有误”、“建议不准确”等将用户直接反馈的问题作为最高优先级的优化样本。5.2 A/B测试与渐进式发布任何针对抗幻觉策略或模型版本的更新都绝不直接全量上线。我们采用严格的A/B测试流程小流量实验将新策略或新模型部署在实验组仅对5%的流量生效与对照组旧策略进行对比。核心对比指标不仅是幻觉率还包括用户停留时间、反馈好评率、后续付费咨询转化率等业务指标。逐步放量如果实验组在核心指标上显著优于对照组且未发现新的严重问题则逐步将流量比例提升至20%、50%最后全量。每一步都观察至少一个完整的业务周期如24小时。快速回滚机制所有发布都具备一键快速回滚的能力。监控到任何关键指标如幻觉率、错误率的异常飙升都能在分钟级内回退到上一个稳定版本。5.3 数据飞轮用线上数据反哺模型线上系统运行中产生的数据是最宝贵的资产。我们构建了一个数据闭环自动收集所有被规则校验器拦截的、被人工复核修改的、以及用户主动反馈错误的案例都会自动进入“问题案例库”。分析归因定期如每周分析案例库对幻觉问题进行归因。是某个指标的知识片段缺失是Prompt在某个边界场景下指令不清还是模型对某种表述有固有偏见定向优化根据归因结果进行精准优化。例如补充特定指标的知识片段修改Prompt中关于边界值的描述或者从问题案例中构造高质量的“负样本”即输入数据正确输出模型之前的错误输出用于模型的对抗性训练或强化学习微调让模型学会“避开”这些错误。这个闭环使得我们的系统不再是静态的而是一个能够从错误中学习、不断进化的有机体。最初让我们头疼的“坑”反而成了我们迭代和提升的燃料。6. 总结与反思AI工程化是“脏活累活”也是价值所在回顾从“AI幻觉”到“重试策略”这一路的踩坑与填坑我最大的体会是将AI能力转化为稳定、可靠、可信的产品其难度和复杂度远超模型研发本身。算法工程师追求的是指标上的几个百分点提升而AI工程师或算法产品工程师需要面对的是整个系统工程的海量细节。关于幻觉它不是一个能一劳永逸解决的算法问题而是一个需要持续管理的系统性风险。我们的三层过滤网方案本质上是将“完全信任模型”转变为“有限信任多重校验”。在严肃应用中对AI的信任必须通过严谨的工程约束来建立而不是单纯依靠模型的“能力”。关于稳定性重试、熔断、降级、限流这些并不是什么新技术而是传统分布式系统领域的成熟模式。但在AI系统中由于下游依赖大模型服务的不可控性更高、成本更昂贵这些模式的应用需要更加精细和审慎。一个健壮的AI服务其韧性往往不体现在模型有多聪明而体现在这些“脏活累活”做得有多扎实。这个项目上线大半年后幻觉率维持在极低的水平服务可用性达到了99.95%以上。更重要的是我们建立了一套应对不确定性、持续监控和快速迭代的工程方法论。这套方法论的价值已经超越了体检报告解读这个具体场景成为我们团队处理其他AI落地项目的标准流程。AI工程化的道路没有捷径每一个坑都值得深挖因为填平它的过程就是在构筑你产品最坚实的护城河。