ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

慢病AI系统如何用可计算数学模块根治大模型幻觉问题

慢病AI系统如何用可计算数学模块根治大模型幻觉问题 1. 问题拆解慢病场景下大模型幻觉为什么这么致命先说结论慢病AI陪伴系统和通用聊天机器人最大的区别在于它犯错的代价完全不是一个量级。普通问答答错了用户笑一笑就过去了慢病场景里一个饮食建议、一个用药提醒、一次血糖解读错了就是实实在在的健康风险。我接触过不少做AI医疗产品的团队大家最初的想法都非常一致把大模型接进来把慢病指南、药品说明书、临床路径喂进去让模型变成一个“什么都知道”的健康管家。这个思路本身没错但落地之后问题就来了。慢病管理有三件事是通用大模型天然做不好的。第一数值计算类问题。比如“我身高170体重72帮我算一下BMI”模型可能给你算出一个27.3也可能给你算出一个26.8——都不是瞎算但也不是同一个数。问题在于大模型本质上是根据token的概率分布来“猜”答案的它不是在执行计算它是在模仿人类回答计算的语气。再比如“我今天摄入的碳水大概是多少”“我这几天的血糖波动系数是多少”这类精确计算任务让模型自由发挥就是在碰运气。第二需要长期动态跟踪的问题。慢病管理是一个连续过程用户上周的空腹血糖、这周的餐后血糖、运动和饮食的依从性这些数据之间有关系。但大模型每一次回复时它能看到的是当前对话的上下文和知识库它没有真正的长期记忆更不会对数据进行跨时间段的统计和建模。你问它“我这一周的血糖控制怎么样”你要是给它一堆历史数据它也能说出个大概但如果你想让它基于累计数据主动预警、动态调整方案它很难靠“聊”来完成。第三对错误信息的“自信”问题。这其实是大模型最折磨人的一点它给出错误答案的时候语气和给出正确答案的时候一模一样。在慢病场景里这种没有根据的自信是最危险的因为它会诱导用户产生信任感。用户不一定会去核对AI给的运动建议是否合理他可能真的照着做了。所以纯靠大模型做慢病陪伴本质上是在用一个“会聊天的模糊推理机”去干“需要精确计算和持续跟踪”的活。这个错配就是幻觉问题的根源。我个人的判断是慢病AI的系统架构必须从“让模型背知识、讲道理”转向“让模型做交互、做表达把计算和推理交给确定性的模块”。这就是这篇文章要讲的思路用可计算数学模块去兜底一切可计算的环节把大模型的发挥空间严格约束在理解和生成这两件事上。1.1 为什么慢病场景对确定性有极高要求慢病管理的核心特征是“长期”和“量化”。一个糖尿病患者需要管理的是几十年的日常生活每一次饮食、运动、用药都可能是变量。这个场景要求系统给出的建议是可追溯、可复现的今天BMI是25.1明天也必须算出25.1同一份血糖记录输入进去出的波动系数必须完全一致。人类医生能做到这一点是因为他依赖的是公式、指南和检查数据。大模型靠的是参数空间里的记忆它记下了“BMI体重/身高/身高”这个公式但它不会真的去计算它是在生成一段大概率正确的算式和答案。一次两次可能对一旦输入的数值特殊一点——比如身高是158厘米不是1.58米体重是“大约70公斤”——模型就可能在单位换算、整数取整这些细节上出错。可计算数学模块的价值就在这里它把“公式”从模型的大脑里搬到了代码里。公式不是被“记住”的而是被“执行”的。输入什么、输出什么、中间经过哪些步骤每一环都是确定性的。这从根本上消除了计算类幻觉。1.2 幻觉问题的本质语言模型能做的事和不能做的事为了把解决方案讲清楚我们得先明确边界。大模型擅长的是语义理解、意图识别、多轮对话、知识归纳、文本生成。这些能力来自大规模预训练本质上是模式匹配和概率采样。大模型不擅长的是精确数值计算、逻辑推理的链式执行、跨时段数据的状态维护、对事实一致性的约束。这些不是“做得不够好”而是架构上就不适合——它在生成答案时根本没有“算”这个动作它的每一步都是从一个概率分布里采样。明白了这一层系统的设计原则就出来了凡是能用确定性方法解决的就不要让模型做凡是模型做不了的就不要让模型做。比如说算BMI、算BMR、算TDEE、算血糖波动系数——交给可计算模块。判断用户当前的血糖值是否超出阈值、是否触发预警——交给规则引擎。判断用户说这句话背后的情绪和意图、组织一段有温度的话来叮嘱用户——交给大模型。边界划清楚了系统的可靠性上限就完全不一样了。2. 方案选型可计算数学模块的架构定位可计算数学模块听起来高大上实际上可以理解为一套能够被大模型调用的、确定性好的计算函数库。它有四种常见的存在形态我接触过的项目里基本都可以归到这几类纯Python/Rust函数库通过Function Calling机制被模型调用。基于NumPy/Pandas的数据分析管道负责处理批量数据和生成统计指标。独立的规则引擎或计算服务通过API对外提供能力。符号计算与方程组求解模块用于更复杂的给药建议、营养配比优化。这篇文章主要讲前两种因为它们覆盖了慢病AI陪伴系统里80%以上的计算需求而且实施成本最低——你不用引入额外的复杂基础设施在现有代码库里加一层工具层就行。2.1 可计算模块的三边界原则在设计这个模块时我总结了一个“三边界原则”你可以直接拿去做架构评审的检查清单。边界一状态存储归模块记忆归数据库。大模型本身不保存用户状态。用户所有的历史生理数据、依从性记录、画像标签都应该存在独立的数据库里。计算模块直接从数据库取数算完结果回写数据库。模型只负责在单轮对话里接收计算结果然后用自然语言表达出来。边界二决策建议归规则和公式话术生成归大模型。这个很重要。比如“今天要不要增加运动量”这个决策不应该让模型根据感觉回答而应该由规则引擎根据用户最近三天的血糖趋势、运动记录、当前状态来算出结论然后把这个结论交给模型去“润色”成一句人话。决策和表达分离是慢病AI系统逻辑洁癖的核心。边界三异常数据拦截归计算层模型永远不许对错误数据将错就错。用户输入的数据可能本身不合理。比如血糖值输入了“58”正常人空腹血糖根本不可能到这个数。这个判断要在数据进入计算层之前就完成由计算层直接抛出异常信息给模型让模型引导用户重新输入。模型没有权限对非法数据做出任何“基于用户输入数据”的健康解读。这三条边界不是凭空想出来的是我从好几个实际项目里踩坑踩出来的。早期我们让模型直接解析用户输入、直接算结果、直接给建议结果就是系统里到处都是被模型“想当然”处理掉的异常值和错误结论。2.2 为什么选择Python作为计算模块基础语言慢病AI系统的计算模块我首推Python。原因有三个。第一生态匹配。慢病相关的计算无论是统计分析、时间序列平滑、还是未来的营养优化、风险预测Python的SciPy、Pandas、scikit-learn生态是现成的。你不需要为了一个计算模块去引入另一套技术栈团队维护成本低。第二和大模型的集成就很顺畅。现在主流的Agent框架——不管是OpenAI Function Calling、还是国内的百度千帆、通义百炼——都支持把Python函数直接注册成工具。你写好一个函数一个tool装饰器挂上去模型就知道“有这么个工具可以用”调用约定直接通过JSON Schema描述。数据格式都是JSON两边的亲和度很高。第三逻辑可测试性好。计算模块是确定性代码你完全可以写单元测试来验证每个函数的输入输出是否正确。这在医疗场景里是刚需——你不能上线一个连算数都能算错的模块而Python标准的unittest/pytest体系和CI流程能保证每次修改都不破坏原有逻辑。2.3 模块与知识库的关系计算归计算知识归知识还有一个方案选型问题很容易被忽略可计算模块和大模型的知识库到底是什么关系我的建议是知识库管“宽泛的知识”计算模块管“精确的推导”。知识库里存的是慢病指南、饮食宜忌、用药常识、常见症状的科普解释。这些内容是相对静态的大模型可以基于这些内容做语义层面的回答和解释。但是一旦用户的追问涉及到“根据我昨天的空腹血糖6.1和今天早上运动后的5.4我为今天的午餐应该做怎样的碳水调整”知识库就管不了了——这个问题不是靠查资料能回答的它需要基于用户个体数据做计算推导。所以知识库负责给模型提供“背景知识”计算模块负责给模型提供“个体化结论”。模型把二者组合成最终话术。这种分工可以让系统在保持专业知识宽度的同时又保证个体化建议的准确度。3. 核心计算函数设计与实现现在进入正题。我把慢病AI陪伴系统里最常见、最高频的核心计算函数拆出来逐个讲清楚设计思路和实现代码。你完全可以照着这套代码去搭自己的模块。3.1 生理指标计算BMI、BMR、TDEE这三个指标是慢病管理的基石很多具体的饮食和运动建议都要依赖它们。它们的公式都是公开的标准公式但落地时有一些细节要处理好。# core_metrics.py 慢病AI陪伴系统 - 生理指标计算模块 标准公式BMI / Mifflin-St Jeor BMR / 基于活动系数的TDEE def calculate_bmi(weight_kg: float, height_cm: float) - dict: 计算BMI身体质量指数 Args: weight_kg: 体重公斤 height_cm: 身高厘米 Returns: 包含BMI值和分级的字典 # 单位转换厘米 - 米 height_m height_cm / 100.0 # 边界检查拒绝明显不合理的输入 if not (20 height_cm 250): raise ValueError(f身高数值异常: {height_cm}cm请确认输入是否正确) if not (20 weight_kg 500): raise ValueError(f体重数值异常: {weight_kg}kg请确认输入是否正确) bmi round(weight_kg / (height_m * height_m), 1) # BMI分级中国成人标准 if bmi 18.5: level 偏瘦 elif bmi 24.0: level 正常 elif bmi 28.0: level 超重 else: level 肥胖 return { bmi: bmi, level: level, advice: f您的BMI为{bmi}属于{level}范围。 }这里有个小细节值得展开说明单位问题。用户报身高的时候喜欢说“一米七”“170”“1米70”体重喜欢说“140斤”“70公斤”。早期系统踩过坑模型把“140斤”当成140千克去算BMI结果算出个离谱的“肥胖”。后来我们在计算函数里统一做了一步在进入函数前先用一个归一化函数把中文描述转换成标准单位数值。这个转换逻辑一定要放在调用计算函数之前否则再精确的公式也架不住脏输入。BMR的计算方法目前医学界比较认可的是Mifflin-St Jeor公式它比传统的Harris-Benedict公式在普通人群中的准确度更高。def calculate_bmr(weight_kg: float, height_cm: float, age: int, sex: str) - float: 使用Mifflin-St Jeor方程计算基础代谢率BMR BMR(男) 10 * 体重(kg) 6.25 * 身高(cm) - 5 * 年龄 5 BMR(女) 10 * 体重(kg) 6.25 * 身高(cm) - 5 * 年龄 - 161 if sex not in (male, female): raise ValueError(sex参数必须为 male 或 female) if sex male: bmr 10 * weight_kg 6.25 * height_cm - 5 * age 5 else: bmr 10 * weight_kg 6.25 * height_cm - 5 * age - 161 return round(bmr, 1)TDEE的计算就是在BMR的基础上乘以“活动系数”这个系数一般有五个档位久坐1.2、轻度活动1.375、中度活动1.55、高度活动1.725、极高强度活动1.9。做慢病管理我一般建议在给用户建议摄入热量时不要用最高的两个档位因为慢病用户的日常活动量大部分集中在轻度到中度之间。用高了容易把热量目标调高不利于体重控制。3.2 血糖波动评估不只是看单点数值很多初做慢病陪伴的产品都会犯一个同样的错误只盯着用户的“当前血糖值”高于某个阈值就预警低于某个阈值就提醒加餐。但实际上单点血糖值的意义远不如血糖波动趋势重要。一个血糖平稳但略高的人和一个血糖大起大落但平均值正常的人后者的并发症风险反而更高。所以计算模块里必须有一个用来评估血糖波动情况的函数。医学界常用的指标叫“血糖变异系数CV”计算公式是血糖标准差 / 平均血糖正常参考值一般建议在36%以下。def calculate_glucose_variability(glucose_records: list) - dict: 计算血糖波动评估指标 Args: glucose_records: 血糖记录列表每个元素为 {value: 5.6, time: 2025-01-01 07:30} Returns: 包含平均值、标准差、变异系数(CV)、最高最低值等指标 if not glucose_records or len(glucose_records) 3: return { status: insufficient_data, message: 至少需要3条血糖记录才能计算波动指标 } values [record[value] for record in glucose_records] mean sum(values) / len(values) # 注意这里应该用样本标准差n-1不是总体标准差n variance sum((v - mean) ** 2 for v in values) / (len(values) - 1) std_dev variance ** 0.5 cv (std_dev / mean) * 100 if mean ! 0 else 0 if cv 20: trend 血糖波动较小控制良好 elif cv 36: trend 血糖波动在可接受范围内建议继续保持 else: trend 血糖波动偏大建议关注饮食规律和用药依从性 return { mean_glucose: round(mean, 1), std_dev: round(std_dev, 1), cv: round(cv, 1), max_glucose: max(values), min_glucose: min(values), trend: trend }有一段时间我们只用了内置的statistics.stdev函数后来发现这个函数在数据量特别小的时候结果波动很大但用病区真实血糖记录做过验证后发现当样本量少于3条时任何统计指标都没有意义所以直接在函数里加了最小样本量检查。这条经验想分享给各位计算模块要有自己的“拒绝服务”逻辑不要什么数据都接接了就算。3.3 饮食摄入估算与营养配比慢病AI陪伴系统里用户问得最多的一类问题是“我今天吃了XXX大约摄入多少热量”这个问题看似简单实则容易踩坑。我的做法是构造一个食物营养数据库本地JSON或SQLite存常见食物的每100g热量、蛋白质、脂肪、碳水含量。计算模块按食物名和估算重量去查表然后加权汇总。这里的关键不是数据库本身而是用户数据输入的不确定性。用户说“吃了一碗米饭”到底是多少克这个时候不能靠模块猜而是要在对话里让模型和用户澄清“这一碗米饭大概是你平时吃饭的小碗还是面馆那种大碗”澄清完再进入计算。# nutrition.py FOOD_DB { 米饭: {heat: 116, protein: 2.6, fat: 0.3, carb: 25.9}, # 每100g 馒头: {heat: 223, protein: 7.0, fat: 1.1, carb: 47.0}, 鸡胸肉: {heat: 133, protein: 19.4, fat: 5.0, carb: 2.5}, 西兰花: {heat: 36, protein: 4.1, fat: 0.6, carb: 4.3}, # ... 更多常见食物 } def estimate_meal_heat(foods: list) - dict: 估算一餐的总热量和宏量营养素 Args: foods: [{name: 米饭, amount_gram: 150}, {name: 鸡胸肉, amount_gram: 100}] Returns: 汇总的营养数据 total_heat 0 total_protein 0 total_fat 0 total_carb 0 for item in foods: name item[name] gram item[amount_gram] if name not in FOOD_DB: continue # 或者返回错误让模型提示用户 per_100g FOOD_DB[name] ratio gram / 100.0 total_heat per_100g[heat] * ratio total_protein per_100g[protein] * ratio total_fat per_100g[fat] * ratio total_carb per_100g[carb] * ratio return { total_heat_kcal: round(total_heat, 1), protein_g: round(total_protein, 1), fat_g: round(total_fat, 1), carb_g: round(total_carb, 1) }这个函数本身的逻辑很简单但集成到系统里有三个设计要点不要在函数内部内置“这个食物能不能吃”的判断。同一个食物对不同慢病患者的意义不同糖尿病患者关注GI痛风患者关注嘌呤肾功能不全的人关注蛋白质。计算模块只负责“算出来是多少”至于“这个人能不能吃”那是基于用户画像的另一套规则系统的事。换算要严格。用户如果说“喝了一碗粥”你要通过对话澄清出“大米粥还是小米粥”“稠的还是稀的”然后按对应食材去查表。知识库可以提供“一碗粥大约是多少克”的参考但最终进入计算的一定是经过确认的数值。3.4 运动建议中的能量消耗估算运动建议这块我的处理思路和饮食类似——计算模块负责估算“运动消耗了多少热量”再结合用户的BMR和当天的摄入情况给出热量差判断。def estimate_exercise_burn(activity: str, duration_min: int, weight_kg: float) - dict: 估算运动消耗热量METs法 消耗(千卡) METs * 3.5 * 体重(kg) / 200 * 持续时间(分钟) METS { 散步: 3.0, 快走: 5.0, 慢跑: 7.0, 游泳: 6.0, 骑自行车: 6.8, 瑜伽: 2.5, 打太极拳: 3.0, } if activity not in METS: raise ValueError(f暂不支持的运动类型: {activity}) met METS[activity] burn met * 3.5 * weight_kg / 200 * duration_min return { activity: activity, duration_min: duration_min, burn_kcal: round(burn, 1), met: met }用METs公式估算运动消耗虽然不能做到像专业运动手环那样精准但在慢病管理的日常场景里已经足够。它是一个有生理学依据的近似值而且只要输入确定输出就完全确定——这满足了我们系统对“可复现性”的要求。4. 系统集成如何让大模型正确使用计算模块计算函数写好了只完成了30%的工作。真正决定系统成败的是把这些函数接入大模型推理链路的“胶水层”设计。我见过太多项目死在最后这一步函数写得很好但模型各种调不对、调了不用、用了结果不给用户讲清楚。4.1 Function Calling驱动的工具调用机制目前主流的大模型平台都支持Function Calling。它的核心机制是你在请求里声明有哪些工具函数可用模型在生成回复之前会先判断当前用户的输入是否需要调用某个工具如果需要就输出一个结构化的函数调用请求——注意这时候模型并没有真的执行这个函数它只是告诉你“我打算调用这个函数参数是什么”。这个“参数生成”环节是幻觉的高发区。模型有可能把参数名搞错、把数值格式弄错甚至自己编一个函数名出来。所以工程上一定要做两层防护结构化输出约束通过JSON Schema严格定义参数类型比如身高必须是number、性别必须是枚举值模型生成的参数要经过校验不合格就重试或让模型追问用户。工具白名单系统只允许模型调用预先注册的、经过测试的函数。模型如果输出一个不在白名单里的函数名直接判定为调用失败引导模型重新组织语言。下面以OpenAI风格的Function Calling为例给一个接入慢病系统的参考实现。# llm_tool_bridge.py 大模型与计算模块之间的工具桥接层 import json from typing import Callable, Dict from core_metrics import calculate_bmi, calculate_bmr, calculate_glucose_variability from nutrition import estimate_meal_heat from exercise import estimate_exercise_burn # 注册可被大模型调用全部工具 TOOL_REGISTRY: Dict[str, Callable] { calculate_bmi: calculate_bmi, calculate_bmr: calculate_bmr, calculate_glucose_variability: calculate_glucose_variability, estimate_meal_heat: estimate_meal_heat, estimate_exercise_burn: estimate_exercise_burn, } # 工具的JSON Schema描述用于让模型理解何时触发 TOOL_SCHEMAS [ { type: function, function: { name: calculate_bmi, description: 计算用户的身体质量指数BMI当用户提供身高和体重时调用, parameters: { type: object, properties: { weight_kg: {type: number, description: 体重单位为公斤}, height_cm: {type: number, description: 身高单位为厘米} }, required: [weight_kg, height_cm] } } }, { type: function, function: { name: calculate_glucose_variability, description: 计算血糖波动指标平均值、标准差、变异系数当用户提供多条血糖记录时调用, parameters: { type: object, properties: { glucose_records: { type: array, description: 血糖记录列表每个元素包含value和time字段, items: { type: object, properties: { value: {type: number, description: 血糖值单位mmol/L}, time: {type: string, description: 测量时间例如2025-01-01 07:30} }, required: [value, time] } } }, required: [glucose_records] } } }, # ... 其余工具schema ] def execute_tool_call(tool_name: str, arguments: dict): 安全的执行工具调用 1. 校验工具名是否在白名单中 2. 校验参数是否符合预期可进一步用pydantic做约束 3. 执行并返回结构化结果 if tool_name not in TOOL_REGISTRY: return { status: error, message: f未知的工具调用: {tool_name} } func TOOL_REGISTRY[tool_name] try: result func(**arguments) return {status: success, result: result} except TypeError as e: # 参数不匹配返回错误信息让模型自行处理 return {status: error, message: f参数错误: {str(e)}} except ValueError as e: # 数值校验失败比如身高不可能低于20cm return {status: error, message: str(e)}在实际项目中我把整个调用流程封装成了一个标准循环描述在这段代码的注释里了第一步把用户问题可用的工具Schema一起发给大模型。第二步模型返回一个工具调用请求或者直接返回一个自然语言回答。第三步如果是工具调用请求系统执行对应的Python函数。第四步把函数执行结果以“工具响应”的消息类型回传给模型。第五步模型基于工具结果生成最终面向用户的自然语言答复。这个循环是标准的Agent模式。它的精妙之处在于模型不再需要自己算数它只需要负责把用户的自然语言转成结构化的函数参数再把函数返回的结构化结果转回自然语言。两件都是语言模型最擅长的事而计算本身交给了确定性的代码。4.2 参数提取与校准模型容易在此处翻车我测试过很多模型的参数提取能力结论是大模型在从自由对话里提取结构化参数时错误率远高于直接给一段说明文让它总结。这不难理解因为自然语言里充满歧义、省略和模糊表达。举一个真实测试中反复遇到的场景用户“我身高一米七十五体重这段时间一直在一百四十五斤左右帮我算算BMI呗。”这段话要正确解析需要三个关键步骤单位统一一米七十五 175厘米一百四十五斤 72.5公斤。注意“斤”和“公斤”是两倍关系模型能干成1:1转换并不罕见。模糊量词处理“左右”这个词表示用户给的是一个近似值。这时候不应该直接拿“145斤”去算而应该跟用户确认“请问您最近的体重以72.5公斤计算可以吗”或者取一个区间值算出BMI的范围。缺失信息补充如果用户只报了体重没报身高模型应该追问而不是“猜一个常见身高”。这个行为要在System Prompt里明确约束甚至要通过后置校验来兜底。所以我强烈建议在设计Tool Schema时把每个参数的描述写得极其具体——不仅说明“这个参数是什么”还要说明“什么情况下该填什么值”。同时在System Prompt里写上一句硬性规则“如果用户的表述中含有不确定的量词比如‘左右’‘大概’‘约’不要直接计算先向用户确认准确数值。”4.3 一个核心技巧让模型复述计算过程即使Function Calling机制很成熟我还是建议在System Prompt里给模型加一个“解释计算过程”的要求。不是让模型自己算而是让模型把计算模块返回的结果用自己的话复述一遍让用户能看懂。比如计算模块返回{status: success, result: {bmi: 23.7, level: 正常, advice: 您的BMI为23.7属于正常范围。}}模型应该怎么回复我期望它说“我帮您算了一下用身高175厘米和体重72.5公斤计算您的BMI是23.7这个数值在正常范围内中国成人参考标准是18.5到24.0。说明您目前体重控制得还不错继续保持。”而不是只丢一句话“你的BMI是23.7。”复述计算过程有两个作用第一是让用户感受到系统是“真的算了”而不是随口一说第二是暴露潜在的逻辑错误——如果模型把23.7复述成“偏胖”你一眼就能看出中间出了问题。5. 实操案例三场典型交互的完整实现理论讲再多不如看实际效果。这里我结合真实项目模拟三个慢病AI陪伴系统的高频使用场景每一步都附上完整的交互与中间数据方便你照葫芦画瓢。5.1 场景一用户自查BMI和BMR用户输入“我今年45岁男身高172体重80公斤平时办公室坐一天不怎么动帮我算算我的BMI和基础代谢。”系统执行链路第1步大模型解析意图 检测到用户提供了身高(172cm)、体重(80kg)、年龄(45)、性别(male) 且明确要求计算BMI和BMR判定需要调用工具。 第2步生成工具调用请求 [调用 calculate_bmi(weight_kg80, height_cm172)] [调用 calculate_bmr(weight_kg80, height_cm172, age45, sexmale)] 第3步计算模块执行结果 calculate_bmi - {bmi: 27.0, level: 超重, advice: 您的BMI为27.0属于超重范围。} calculate_bmr - 1634.0 // Mifflin-St Jeor公式: 10*80 6.25*172 - 5*45 5 第4步大模型综合生成话术 先生我帮您做了两个指标的计算。您的BMI是27.0按中国成人标准属于超重范畴正常范围18.524.0。 考虑到您平时以久坐办公为主您的基础代谢率BMR大约是1634千卡/天这是您身体静息状态下 维持基本生命活动需要消耗的热量。如果需要进一步估算您每天的总消耗我们可以再结合您的日常 活动量来推算。这个回复的质量明显优于让模型直接“临场发挥”的结果。关键数据全部来自代码计算模型只做了自然语言的组装和润色。5.2 场景二用户上传一周血糖记录用户输入“我最近一周的血糖记录都在这里了早上空腹分别是6.2、6.5、6.0、6.8、6.3、5.9、6.4帮我看看波动大不大。”系统执行链路第1步大模型识别到这是一组时间序列血糖数据触发calculate_glucose_variability工具。 第2步调用工具 glucose_records [ {value: 6.2, time: 空腹}, {value: 6.5, time: 空腹}, {value: 6.0, time: 空腹}, {value: 6.8, time: 空腹}, {value: 6.3, time: 空腹}, {value: 5.9, time: 空腹}, {value: 6.4, time: 空腹} ] 第3步计算模块返回结果 mean_glucose6.3, std_dev0.3, cv4.8, max6.8, min5.9 血糖波动很小处于良好控制状态。 第4步大模型话术组装 看您这周的空腹血糖平均在6.3 mmol/L左右最高6.8、最低5.9变异系数不到5% 说明您的血糖控制得相当平稳。不过从数值上看空腹血糖整体还是略偏高一点建议 继续关注晚餐摄入和早晨用药的配合情况。注意对话中提到的时间信息被简化了我们在系统里可以用“第1次空腹”或具体日期时间戳来标记。真实系统里不推荐让用户手动输入JSON数组更好的方式是通过前端录入或导入设备数据这里则是为了说明工具调用的参数传递逻辑。5.3 场景三饮食与运动综合建议用户输入“我身高165体重65公斤50岁女性今天中午吃了一碗米饭、一份清蒸鱼和一份炒青菜然后去快走了30分钟帮我算算今天中午这顿饭的热量和运动消耗。”系统执行链路第1步模型拆分任务 识别出两个子任务 A. 估算饮食摄入热量和宏量营养素 B. 估算快走30分钟的运动消耗 第2步调用两个工具 [调用 estimate_meal_heat(foods[ {name: 米饭, amount_gram: 150}, {name: 清蒸鱼, amount_gram: 100}, {name: 炒青菜, amount_gram: 200} ])] [调用 estimate_exercise_burn(activity快走, duration_min30, weight_kg65)] 第3步工具返回结果 meal_heat {total_heat_kcal: 约250, protein_g: 约28, fat_g: 约6, carb_g: 约45} exercise_burn {burn_kcal: 约170, met: 5.0} 第4步模型组装回答 您午餐的热量估算大约是250千卡蛋白质约28克脂肪6克碳水45克。这个搭配在三大 营养素比例上是比较均衡的蛋白质略高一点有助于延缓餐后血糖上升。您快走30分钟 的运动消耗大约在170千卡左右。这一餐的摄入量和运动消耗之间动的部分补掉了一大部分 热量缺口整体节奏是健康的。这里有一个值得注意的点模型在没有说明的情况下自动挑了“炒青菜”作为食物条目并估算了一个200克的重量。这个动作需要约束——应该在系统规则里要求模型对“用户没说重量”的食物进行反问而不是自作主张地补一个数值。6. 效果验证测试结果与业务指标变化方案做出来了如何证明“可计算数学模块”确实能降低幻觉、提升系统质量我建议从两个维度来验证。6.1 幻觉压测一组可执行的测试用例围绕慢病AI最常见的问题我整理了一组“基准测试集”用来量化系统改进前后的效果。每组测试由“问题 期望答案类型 可接受误差范围”组成。测试类别示例问题期望结果类型可接受误差/判定标准数值计算身高175cm、体重70kgBMI是多少数值分级数值误差±0.1分级正确公式推算45岁男性BMR是多少数值数值误差±5千卡数据统计一周血糖的变异系数数值解读数值精确解读符合阈值饮食记录估算一碗米饭鸡胸肉的热量数值营养素数值在合理范围内来源有依据异常输入血糖值58mmol/L拒绝计算并引导重新输入不得给出任何健康解读缺失信息用户只说“帮我算BMI”没说身高体重追问信息不得猜测数值改进前直接让大模型回答这些问题的整体通过率大约在六到七成剩下三成多要么数值算错要么在边界情况下给出误导性结论。引入计算模块后数值类测试的通过率可以做到接近100%剩下的失败案例全部集中在“模型调错工具”或“参数解析错误”上。这说明系统的确定性已经由计算层兜住但模型的调度能力仍然值得关注。6.2 业务层面的指标变化从技术指标到用户价值技术指标只是一部分慢病AI系统最终要回答的问题是它有没有让用户的健康行为发生正向改变我观察到一个非常明显的对比。在没有计算模块支持的版本里用户对系统建议的信任度在对话进行到中后期会下降。原因很简单用户发现AI说的东西不太稳定有时候BMI给27.1过两天重新问又变成26.9。这种“不靠谱感”是慢病陪伴的致命伤。而接入确定性计算模块后同一个用户无论问多少遍同样的输入永远得到同样的输出。这个简单的“一致性”本身就极大提升了用户对系统的信任感。另一个指标是用户主动上传数据的频率。当用户发现AI会认真对待自己上传的血糖记录、运动数据并且能基于这些数据给出个性化分析时用户上传数据的意愿会明显增强。数据量多了系统可以做更精准的趋势分析形成正向循环。6.3 不推荐的做法为什么不能用prompt硬约束替代计算模块有些团队可能会想既然大模型会算错那我就在prompt里强调“你必须仔细计算”行不行我可以直接告诉你这个方案在工程上不可靠。原因在于大模型的推理机制是概率性的它当前时刻生成“7.2”和下一篇生成“7.0”之间没有本质的区别——都是概率采样。prompt可以让模型在大部分时候“表现得像在认真计算”但它无法保证100%正确尤其在你无法通过单元测试覆盖全部输入空间的边界情况下。而对于医疗健康场景100%这个要求不是“高要求”是“及格线”。所以可计算模块不是prompt的替代品而是所有prompt都失效时的最后防线。7. 真实交付中的坑与排查建议最后聊聊我在实际落地中遇到过的几个问题。这些坑很典型你有很大概率也会踩。7.1 血糖记录的数据清洗不能依赖大模型很多用户上传血糖记录时数据格式五花八门“空腹6.5”“餐后2小时8.0”“睡前5.9”。有的带单位有的不带有的写“7.2mmol/L”有的写“早上测的7.2”。如果让大模型直接解析并转换为结构化数据它出错概率不低。我的建议是数据清洗分成两步第一步用规则和正则表达式做预处理把明显能识别的格式先结构化第二步不能识别的、模糊的数据走大模型做“二次理解”但大模型输出后必须再由规则层校验一遍。没有规则层兜底的数据绝不能直接进入计算模块。7.2 工具调用的参数校验要写清楚返回语义Function Calling链路里如果参数校验失败比如用户输入的身高是“1米7”模型把它解析成了“1.7厘米”计算模块抛出一个ValueError。这个错误信息回传给模型后模型应该能理解“哦参数单位不对我要再问用户确认一下”。但如果错误信息写得不够清楚模型可能直接把这个数字传给下一个工具白白浪费一轮调用。我建议在计算函数内部统一使用“错误信息 提示如何纠正”的返回结构。举个例子单位校验失败时返回的错误信息应该是“身高数值异常1.7cm请确认是否把‘1米7’解析为170cm。”这样模型可以根据提示自动修正而不是把错误原样抛给用户。7.3 避免计算模块在业务逻辑里“过度膨胀”最后一条经验是一个反模式警告。可计算模块的功能强大之后很容易越做越重最终把本该由业务规则、状态机、甚至人工运营负责的事情也塞进来。比如要不要提醒用户复诊、要不要调整用药方案这些本质上属于需要医学知识和临床决策树的事情不能简单用“公式”去解决。我给自己定的边界很简单计算模块只解决“给定确定的数算出确定的结果”这类问题涉及“要不要做、怎么做”的决策问题交给独立的决策引擎或者引入人工审核。一旦计算模块开始尝试“做决策”系统的可解释性就会急剧下降出了问题你也很难向用户交代。8. 写在最后这是我能想到的最省力但最可靠的架构做慢病AI陪伴系统这几年我最深的体会是大模型的幻觉问题不是靠“换个更大的模型”就能解决的也不是靠“prompt写得再细一点”就能规避的。它在架构层面就注定了需要用确定性组件去兜底。可计算数学模块就是这个确定性组件的核心。它不一定需要很复杂几个公式、一张食物数据库表、一组统计函数就能把系统里最容易出错、最影响信任感的数值类功能全部接管过来。大模型则退回到它最擅长的领域——理解用户、组织语言、表达温度。两者配合系统的上限和下限都会提高一个档次。如果这个思路对你有启发建议你先从最简单的一个计算函数开始比如就做一个BMI计算器接入你现有的对话系统然后逐步扩展到血糖评估、饮食记录、运动消耗。模块边界划清楚之后每一步的增量开发成本都很低但系统可靠性的提升会非常明显。最后再分享一个小技巧给你的每个计算函数都写一段中文描述说明“何时调用”“何时不应该调用”“参数怎么填”。这段描述会直接参与模型生成工具调用请求的决策写得好模型的调度准确率能高出一大截。这个投入产出比极高强烈推荐你试试。
返回列表