
一、业务目标杏林堂中医药服务平台集成了四项 AI 能力能力输入输出与展示药性图谱分析单味药材资料性味、归经、功效及关系图智能推荐症状、体质等信息平台在售药材范围内的辅助建议药方检测药材、剂量、特殊人群风险等级、依据、调整建议智能问答自由问题科普型自然语言回答工程上的难点不是“调用一次 API”而是让模型输出能够被程序稳定解析、阻止模型引用平台不存在的药材、保存调用记录并在模型或网络异常时给出可控反馈。医疗健康相关系统还必须明确边界AI 输出只能作为科普与辅助参考不能替代医师诊断和处方出现急重症、过敏或不良反应时应引导用户及时就医。二、为什么选择 Java 17 HttpClient项目没有引入大模型 SDK而是直接使用 JDK 自带的java.net.http.HttpClient。优点是依赖少、请求结构透明、便于根据 API 文档调整参数。客户端初始化如下privatestaticfinalStringAI_URLhttps://api.deepseek.com/chat/completions;privatefinalObjectMapperobjectMappernewObjectMapper();privatefinalHttpClienthttpClientHttpClient.newBuilder().connectTimeout(Duration.ofSeconds(20)).build();API Key 不应写死在源码中。项目将ai_enabled和deepseek_api_key保存在系统配置表由超级管理员在后台维护publicvoidensureAiEnabled(){if(!1.equals(getConfigValue(ai_enabled))){thrownewBusinessException(AI功能已关闭);}}publicStringrequireApiKey(){StringapiKeygetConfigValue(deepseek_api_key);if(!StringUtils.hasText(apiKey)){thrownewBusinessException(DeepSeek API Key 未配置);}returnapiKey;}正式环境中还应对 Key 加密存储或通过密钥管理服务注入并确保接口、日志和异常信息都不会输出完整 Key。三、构造 JSON Mode 请求AI 结果需要直接映射为 Java 对象因此请求中启用 JSON 输出模式ObjectNoderootobjectMapper.createObjectNode();root.put(model,deepseek-chat);root.put(stream,false);ObjectNoderesponseFormatobjectMapper.createObjectNode();responseFormat.put(type,json_object);root.set(response_format,responseFormat);ArrayNodemessagesobjectMapper.createArrayNode();ObjectNodesystemMsgobjectMapper.createObjectNode();systemMsg.put(role,system);systemMsg.put(content,systemPrompt);ObjectNodeuserMsgobjectMapper.createObjectNode();userMsg.put(role,user);userMsg.put(content,userPrompt);messages.add(systemMsg);messages.add(userMsg);root.set(messages,messages);然后发送 HTTP 请求HttpRequestrequestHttpRequest.newBuilder().uri(URI.create(AI_URL)).timeout(Duration.ofSeconds(90)).header(Authorization,Bearer apiKey).header(Content-Type,application/json).POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(root))).build();HttpResponseStringresponsehttpClient.send(request,HttpResponse.BodyHandlers.ofString());除了设置response_format提示词中仍需明确“只输出一个合法 JSON 对象不要输出解释文字不要使用 Markdown 代码块”并给出字段名称、类型和示例。JSON Mode 可以提高稳定性但不能代替服务端校验。四、解析失败自动重试与 JSON 清洗模型偶尔仍可能返回 Markdown 围栏或第一次输出字段不完整。项目封装统一入口首次调用或解析失败后自动重试一次publicAiJsonResultcallJson(StringapiKey,StringsystemPrompt,StringuserPrompt){try{returndoCall(apiKey,systemPrompt,userPrompt);}catch(Exceptionfirst){try{returndoCall(apiKey,systemPrompt,userPrompt);}catch(Exceptionsecond){thrownewBusinessException(AI服务暂时不可用请稍后重试);}}}解析前对常见围栏进行清理privateStringcleanJson(Stringraw){if(rawnull){return{};}Stringtextraw.trim();if(text.startsWith()){texttext.replaceFirst(^json\\s*,).replaceFirst(^\\s*,).replaceFirst(\\s*$,);}returntext.trim();}重试次数不能无限增加。模型输出结构错误时盲目重试会放大费用和接口延迟。更合理的做法是有限重试、记录错误摘要并向前端返回可理解的降级提示。五、智能推荐中的“药材白名单”大模型可能生成平台数据库中不存在或已经下架的药材。若前端直接根据模型返回的medicineId跳转会出现空页面甚至把不受平台管理的内容包装成可购买商品。项目采用“两层约束”第一层将数据库药材目录注入 PromptpublicStringbuildCatalogText(){ListMedicineInfolistmedicineInfoMapper.selectList(newLambdaQueryWrapperMedicineInfo().eq(MedicineInfo::getStatus,1));returnlist.stream().map(m-String.join(|,String.valueOf(m.getId()),nullToEmpty(m.getMedicineName()),nullToEmpty(m.getNature()),nullToEmpty(m.getMeridian()),nullToEmpty(m.getEffect()))).collect(Collectors.joining(\n));}紧凑的id|名称|性味|归经|功效格式比完整 JSON 更节省 Token。提示词要求模型只能从目录中选择medicineId。第二层后端二次过滤publicSetLongvalidMedicineIds(){returnmedicineInfoMapper.selectList(newLambdaQueryWrapperMedicineInfo().eq(MedicineInfo::getStatus,1)).stream().map(MedicineInfo::getId).collect(Collectors.toSet());}解析模型结果后将返回 ID 与合法集合求交集不在白名单中的 ID 一律剔除。这里体现了一条重要原则Prompt 约束属于“软约束”服务端校验才是“硬约束”。当在售药材很多时不宜把整个目录都塞进 Prompt。可以先使用关键词、向量检索或数据库规则召回候选药材再让模型只在候选集内分析。六、四项能力如何分别落地1. 药性图谱分析后端把药材名称、性味、归经、功效、禁忌等字段传给模型要求返回节点与关系。结果写入ai_medicine_analysis一味药材对应一条缓存记录。前端使用 EChartsgraph力导向图将药材放在中心把性味、归经、功效和配伍关系作为周边节点。缓存可以减少相同药材的重复调用。管理员更新药材资料后可通过“强制重新生成”刷新分析结果。2. 智能推荐用户输入症状和体质系统拼接在售药材目录请模型返回建议药材、推荐理由和注意事项。返回结果先经过 ID 白名单过滤再写入ai_recommend。这里应避免使用“自动开方”“确诊”等表述。更稳妥的产品定位是健康知识推荐并持续显示“请咨询专业医师”的提示。3. 药方检测用户添加药材和剂量并选择是否为儿童或妊娠人群。模型检查十八反、十九畏、剂量异常和特殊人群风险返回safe、warn或danger同时给出风险条目、依据和调整建议记录写入ai_prescription_check。高风险规则不能只依赖大模型。正式医疗系统应把明确、稳定的禁忌和剂量规则沉淀为可审计的规则库模型负责解释和补充最终仍由药师或医师审核。4. 智能问答智能问答允许匿名访问前端左侧展示分组常见问题右侧进行对话。问答记录写入ai_chat可统计热门关键词和调用成本。匿名访问也应设置频率限制、敏感内容过滤和单次输入长度限制避免接口被滥用。七、结果落库的价值AI 接口返回后立即丢弃结果看似简单实际上不利于维护。将结果落库可以获得相同药材分析直接命中缓存减少费用和等待时间追踪模型、Token 数与生成时间后台审计风险内容统计用户关注的症状与药材Prompt 或模型升级后对比新旧结果出现争议时保留必要的调用记录。数据库中不应保存不必要的敏感健康信息。需要明确数据保留周期、访问权限、脱敏方式和删除机制。八、前端悬浮 AI 面板设计四项能力统一放入右下角悬浮入口并复用固定尺寸的面板壳。业务页面通过 Pinia 保存待打开的面板类型和预填参数。例如用户在药材详情页点击“加入药方检测”系统先把药材写入暂存篮再打开检测面板。这种pending-key模式避免了页面组件直接操作远处的弹窗实例药材详情页 - 写入 Pinia 待办状态 - 全局 Dock 监听 - 打开指定面板并预填所有面板共享标题栏、遮罩、关闭行为和尺寸规范业务组件只负责输入表单与结果展示能够显著减少重复代码。九、生产化还需要补充什么课程设计中的实现已经形成完整链路但若进入真实生产环境还应补充对 API Key 加密存储并定期轮换增加限流、超时、熔断、监控和调用费用告警使用 JSON Schema 或 DTO 校验每个返回字段对危险医学内容设置规则库与人工审核对 Prompt 版本化保存模型名和提示词版本对个人健康信息进行最小化收集、脱敏和权限隔离对药材目录使用检索召回避免 Prompt 随数据量无限增长设计清晰的免责声明、急症提示和转人工入口。十、总结大模型接入业务系统不能停留在“请求 API然后把文本显示出来”。一个可维护的 AI 功能至少包含配置开关、密钥管理、结构化输出、有限重试、服务端校验、白名单约束、结果缓存、审计记录、前端状态编排和安全提示。尤其在中医药与健康场景中应让规则和专业审核掌握最终决定权把模型定位为解释、整理和辅助工具。