智能健康陪诊与个性化干预 Agent 的设计与实现前言在基于语言大模型的智能居家养老系统中我主要负责面向老人端的两个核心 AI Agent 的构建健康陪诊 Agent与健康干预 Agent。前者作为首页全科问答入口提供 24 小时健康咨询服务后者深度联动数字病历夹为老人生成个性化的饮食、运动与作息方案。本文完整记录这两个 Agent 从需求分析、Prompt 设计到 Spring Boot 工程实现的全过程。一、需求分析与功能定位1.1 健康陪诊 Agent——首页核心交互入口根据系统设计目标健康陪诊 Agent 扮演全科陪诊员角色驻留于 App 首页为老年人提供常见养生保健知识问答如吃什么对血管好身体不适的初步判断与就医引导如头晕可能是什么原因就诊前准备事项提醒与情绪安抚疑似急症的紧急升级建议拨打 120核心定位是有温度的全科导诊台——专业但不生硬有边界但不拒人于外。1.2 健康干预 Agent——千人千面的方案引擎健康干预 Agent 的设计目标是打破传统健康建议千篇一律的困境。它必须做到深度读取老人的数字病历夹数据疾病史、用药清单、体征指标基于真实病理特征进行 LLM 推理而非给出通用建议支持饮食营养、运动康复、作息指导三个细分方向输出具体、可执行的量化建议如每日钠盐控制在 5g 以内二、Prompt 工程设计2.1 健康陪诊的 Prompt 策略健康陪诊 Agent 面对的问题最为多样化需要 Prompt 同时具备广度覆盖和安全约束你是一位专业的全科健康陪诊助手名叫康伴。 你驻留于智能居家养老健康守护系统的首页是老年人日常健康咨询的核心交互入口。 你的职责 1. 提供24小时全天候的专业养生问答与健康科普 2. 帮助老人初步判断身体不适的可能原因引导合理就医 3. 提供日常保健建议包括饮食、作息、运动等方面 4. 解答常见疾病的预防知识和注意事项 5. 在老人描述症状时帮助整理信息以便就诊时表达清晰 6. 缓解老人就医前的紧张情绪提供陪伴支持 重要原则 - 你不是医生绝不做明确诊断或开具处方 - 遇到疑似急症胸痛、呼吸困难、突发偏瘫、剧烈腹痛等立即建议拨打120 - 用简单易懂的语言解释医学概念避免过度专业术语 - 对不确定的问题坦诚表达不确定性建议就医确认设计要点角色命名赋予康伴这个名字不仅是人格化设计更能让老年用户形成固定的交互预期——找康伴问问比打开 AI 对话更符合老年人的认知习惯。否定式安全边界在医疗场景中明确告知模型不做什么比列举做什么更有效。绝不做明确诊断这类强否定指令可以显著降低模型幻觉风险。紧急升级机制在 Prompt 中显式列举急症特征胸痛、呼吸困难等确保模型在识别到这些关键词时立即切换为建议就医模式而非继续给出调养建议。Temperature 设置为 0.6这个值在创造性回答多样化问题和保持医学表述准确性之间取得平衡。2.2 健康干预的分模式 Prompt 架构健康干预 Agent 面临一个工程挑战一个 Agent 需要覆盖三个专业子领域。如果把所有指令塞进一个 Prompt模型容易在回复中混杂多个领域内容。我采用**基础 Prompt 模式 Prompt的叠加架构**privateStringbuildSystemPrompt(ElderlyContextcontext,StringinterventionType){StringBuildersbnewStringBuilder(BASE_PROMPT);// 通用角色定义sb.append(getInterventionPrompt(interventionType));// 子领域专项指令if(context!null){sb.append(\n\n以下是当前老人的详细健康档案数据\n);sb.append(context.toFullContextString());// 注入个人数据}returnsb.toString();}以饮食模式为例其模式 Prompt 明确定义了输出格式当前为【饮食营养】干预模式。重点关注 - 根据疾病制定每日营养配比热量、三大营养素、微量元素 - 推荐和禁忌食物清单 - 提供具体的每日食谱建议 - 考虑药食交互作用 - 注意老年人咀嚼与消化能力 回复格式建议 - 【饮食原则】基于病情的核心饮食要求 - 【推荐食材】可多食用的食物 - 【禁忌/少食】需要避免或减少的食物 - 【参考食谱】一日三餐的具体建议结构化输出引导的优势通过在 Prompt 中预设输出模板模型生成的内容更加规范前端也更容易做分段渲染和格式化展示。2.3 上下文注入的隐式策略当老人的健康档案数据被注入 System Prompt 时需要一个关键指令以下是当前对话老人的背景信息请在对话中适当参考 不要主动提及你已知道这些信息除非与对话内容相关这条隐式规则解决了两个问题防止 AI 每次对话开头都说根据您的高血压病史…让老人感到不适引导模型在用户主动提问相关话题时才自然地引用背景数据三、Spring Boot 服务层实现3.1 统一的 Service 架构两个 Agent 共享统一的实现范式Controller (接口层) ↓ Service (业务逻辑) ├── resolveSession() → 会话管理 ├── loadContext() → 上下文加载 ├── buildMessages() → 消息构建 └── callLLM() → 模型调用同步/流式以健康陪诊的非流式实现为例OverridepublicStringchat(HealthChatRequestrequest){ElderlyContextcontextelderlyContextService.getFullContext(request.getElderlyId());ChatSessionsessionresolveSession(request);session.addMessage(newChatMessage(user,request.getMessage()));StringurldeepSeekConfig.getBaseUrl()/v1/chat/completions;HttpHeadersheadersnewHttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);headers.setBearerAuth(deepSeekConfig.getApiKey());MapString,ObjectbodyMap.of(model,deepSeekConfig.getModel(),messages,buildMessages(session,context),temperature,0.6,max_tokens,1500);HttpEntityMapString,ObjectentitynewHttpEntity(body,headers);ResponseEntityStringresponserestTemplate.exchange(url,HttpMethod.POST,entity,String.class);JsonNoderootobjectMapper.readTree(response.getBody());Stringreplyroot.path(choices).get(0).path(message).path(content).asText();session.addMessage(newChatMessage(assistant,reply));chatSessionService.saveSession(session);returnreply;}3.2 SSE 流式响应的实现为解决老年人等待 AI 回复时的焦虑感两个 Agent 均实现了流式逐字输出。核心采用 Java 11HttpClientSseEmitter组合OverridepublicSseEmitterchatStream(HealthChatRequestrequest){SseEmitteremitternewSseEmitter(120_000L);ElderlyContextcontextelderlyContextService.getFullContext(request.getElderlyId());ChatSessionsessionresolveSession(request);session.addMessage(newChatMessage(user,request.getMessage()));executor.execute(()-{try{StringfullReplystreamFromDeepSeek(session,context,emitter);session.addMessage(newChatMessage(assistant,fullReply));chatSessionService.saveSession(session);}catch(Exceptione){log.error(流式对话异常,e);try{emitter.send(SseEmitter.event().name(error).data(服务暂时不可用请稍后重试));}catch(Exceptionignored){}emitter.completeWithError(e);}});emitter.onTimeout(emitter::complete);emitter.onError(t-log.warn(SSE 连接异常断开,t));returnemitter;}流式解析 DeepSeek 返回的 SSE 数据流try(BufferedReaderreadernewBufferedReader(newInputStreamReader(response.body(),StandardCharsets.UTF_8))){Stringline;while((linereader.readLine())!null){if(line.isBlank()||!line.startsWith(data: ))continue;Stringdataline.substring(6).trim();if([DONE].equals(data)){emitter.send(SseEmitter.event().name(done).data([DONE]));break;}JsonNodenodeobjectMapper.readTree(data);Stringcontentnode.path(choices).get(0).path(delta).path(content).asText();if(!content.isEmpty()){fullReply.append(content);emitter.send(SseEmitter.event().name(message).data(content));}}}3.3 健康干预的多模式路由健康干预 Agent 的独特之处在于通过interventionType字段动态切换 Prompt 模式privateStringgetInterventionPrompt(Stringtype){if(typenull)return;returnswitch(type.toLowerCase()){casediet-\n\nDIET_PROMPT;caseexercise-\n\nEXERCISE_PROMPT;caselifestyle-\n\nLIFESTYLE_PROMPT;default-;// comprehensive 模式使用基础 Prompt 即可};}前端通过一个选择器让用户选定干预类型后端根据类型动态组装 System Prompt实现一个 Agent 多种人格的效果。3.4 RESTful API 设计两个 Agent 的 Controller 层暴露统一风格的接口POST/api/health-companion/chat/stream// 健康陪诊-流式对话POST/api/health-companion/chat// 健康陪诊-普通对话GET/api/health-companion/sessions// 会话列表GET/api/health-companion/sessions/{id}// 会话详情DELETE/api/health-companion/sessions/{id}// 删除会话POST/api/health-intervention/chat/stream// 健康干预-流式对话POST/api/health-intervention/chat// 健康干预-普通对话GET/api/health-intervention/sessions// 会话列表...请求 DTO 的差异体现了两个 Agent 的功能区别// 健康陪诊通用问答只需消息内容publicclassHealthChatRequest{privateStringsessionId;privateLongelderlyId;privateStringmessage;}// 健康干预需要指定干预类型publicclassHealthInterventionRequest{privateStringsessionId;privateLongelderlyId;privateStringmessage;privateStringinterventionType;// diet / exercise / lifestyle / comprehensive}四、开发中的关键决策与踩坑4.1 Temperature 参数调优初版实现中两个 Agent 都使用了 0.7 的默认 temperature。测试中发现健康陪诊在回答头晕怎么办类问题时偶尔会给出不够严谨的建议 → 降低至 0.6健康干预在生成饮食方案时过于模板化 → 尝试 0.7 后发现会编造不存在的食谱 → 最终选定 0.5结论医疗场景的 Temperature 宁可偏低用 Prompt 中的结构化模板来引导多样性比靠高 Temperature 获取创造性更可控。4.2 上下文数据缺失的降级处理当老人尚未上传任何病历资料时ElderlyContextService返回的数据可能为空。健康干预 Agent 需要优雅降级if(contextnull){sb.append(\n\n注意当前未获取到老人的健康档案数据请引导老人先完善病历资料同时给出通用性建议并标注建议补充个人健康数据以获得更精准的方案);}4.3 长对话的 Token 管理多轮对话累积后messages数组可能超出模型上下文窗口。当前采用max_tokens限制输出长度作为缓冲后续规划实现基于摘要压缩的历史消息裁剪策略。五、效果验证与测试场景测试中验证了以下核心场景健康陪诊普通养生问答“冬天怎么泡脚比较好” → 给出水温、时长建议症状咨询“最近老是头晕” → 列举可能原因 建议就医检查急症触发“突然胸口很痛” → 立即建议拨打 120健康干预高血压 糖尿病患者饮食模式自动推荐低钠、低糖膳食方案标注药食交互注意事项运动模式推荐低强度有氧运动提示血压过高时暂停运动作息模式根据用药时间建议合理的起居安排六、AI 辅助开发实录——提示词与人工调试开发过程中我使用了 AI 编程工具辅助生成部分代码骨架但实际落地远非一句提示词就能跑。以下记录真实的 AI 协作过程和必须人工介入的部分。6.1 使用的核心提示词生成健康干预 Agent 的初版 Service 时我给 AI 的提示词如下参照已有的 EmotionalCompanionServiceImpl 的流式对话实现模式为我写一个 HealthInterventionServiceImpl。要求 1. 支持通过 interventionType 字段动态切换子领域 promptdiet/exercise/lifestyle 2. 每种子领域 prompt 需要定义结构化的输出格式模板 3. 当 ElderlyContext 为 null 时做降级处理提示用户完善档案 4. temperature 设为 0.5max_tokens 设为 2000 5. 其余部分保持与 EmotionalCompanionServiceImpl 一致的会话管理和 SSE 流模式AI 生成的代码基本可用但有若干问题需要我手动修复见下文。另一个关键提示词用于设计健康陪诊 Agent 的 System Prompt 本身我需要写一段 LLM 的 system prompt角色是面向老年人的全科健康陪诊助手。 要求 - 不能给诊断结论必须引导就医 - 遇到急症关键词胸痛、呼吸困难、偏瘫必须建议 120 - 语言要通俗但不能丧失专业性 - 不能主动暴露自己已知用户的病史信息 给我一版 prompt我再基于实际测试效果迭代调整AI 给出的第一版 Prompt 过于冗长约 800 字且安全边界描述太笼统只写了不给医疗建议。我手动迭代了三轮砍掉重复表述将核心指令压缩到 400 字以内把不给医疗建议拆解为具体场景不诊断、不开处方、急症升级增加回答风格段落约束回复的结构和节奏6.2 AI 生成代码的 Bug 与人工修复Bug 1流式 SSE 中emitter.complete()的重复调用AI 生成的代码在streamFromDeepSeek方法末尾调用了emitter.complete()同时在外层executor.execute的 try 块结束后又调用了一次。两次 complete 导致第二次抛出IllegalStateException日志中出现大量异常栈。// AI 生成的有问题的代码executor.execute(()-{try{StringfullReplystreamFromDeepSeek(session,context,emitter);// streamFromDeepSeek 内部已经 emitter.complete()session.addMessage(newChatMessage(assistant,fullReply));chatSessionService.saveSession(session);emitter.complete();// ← 重复调用抛异常}catch(Exceptione){...}});修复去掉外层的emitter.complete()调用仅在streamFromDeepSeek内部流结束时调用一次。Bug 2buildMessages中 System Prompt 未带上下文数据AI 第一版将buildMessages写成了messages.add(Map.of(role,system,content,SYSTEM_PROMPT));直接使用了静态常量完全忽略了动态注入的 ElderlyContext。测试时发现模型对我血压高应该吃什么的回复完全是通用建议没有引用用户实际的血压数值——这时候才发现上下文没进去。修复改为调用buildSystemPrompt(context, interventionType)方法拼接完整 Prompt。Bug 3interventionType为 null 时 switch 空指针健康干预请求中interventionType是选填字段。AI 生成的 switch 表达式直接对type调用了.toLowerCase()returnswitch(type.toLowerCase()){// ← type 为 null 时 NPEcasediet-...上线后前端在综合模式下未传该字段直接 NPE 500。修复在 switch 之前加 null 判断privateStringgetInterventionPrompt(Stringtype){if(typenull)return;returnswitch(type.toLowerCase()){...};}Bug 4SSE 流中 DeepSeek 返回的非标准行导致 JSON 解析异常AI 生成的 SSE 解析代码没有对objectMapper.readTree(data)做 try-catch而实际测试中发现 DeepSeek 偶尔会返回类似: keep-alive的心跳行不符合data:前缀但也不为空以及偶发的data: {error:...}错误响应。这些情况直接导致 JsonNode 取choices[0]时空指针。修复内层加 try-catch 跳过解析失败的行并记录 debug 日志try{JsonNodenodeobjectMapper.readTree(data);JsonNodedeltanode.path(choices).get(0).path(delta);Stringcontentdelta.path(content).asText();if(!content.isEmpty()){fullReply.append(content);emitter.send(SseEmitter.event().name(message).data(content));}}catch(Exceptione){log.debug(跳过无法解析的 SSE 数据: {},data);}6.3 小结AI 辅助开发确实加速了代码骨架的搭建尤其是重复结构的 Controller 和 DTO但在以下环节人工介入不可替代Prompt 的迭代调优需要反复实测真实场景才能打磨出合格的 System Prompt运行时异常排查SSE 的线程模型和流式 API 的边界情况只有跑起来才能暴露业务逻辑的正确性上下文是否真的注入了、null 场景是否覆盖了这些 AI 无法自行验证七、总结两个 Agent 的开发实践让我认识到Prompt 工程是核心竞争力相同的模型不同的 Prompt 可以产出天差地别的效果分模式架构比多 Agent 拆分更经济健康干预的 diet/exercise/lifestyle 三个方向共享基础设施通过 Prompt 叠加实现差异化医疗场景安全边界必须在 Prompt 和代码双层设定不能仅靠模型自觉流式输出不是锦上添花而是刚需对于老年用户3-5 秒的等待空白足以让他们以为系统死机了AI 辅助编码不等于无脑生成骨架可以让 AI 写但运行时行为必须人工验证和调试
山东大学软件学院项目实训-基于语言大模型的智能居家养老健康守护系统-个人博客(五)
智能健康陪诊与个性化干预 Agent 的设计与实现前言在基于语言大模型的智能居家养老系统中我主要负责面向老人端的两个核心 AI Agent 的构建健康陪诊 Agent与健康干预 Agent。前者作为首页全科问答入口提供 24 小时健康咨询服务后者深度联动数字病历夹为老人生成个性化的饮食、运动与作息方案。本文完整记录这两个 Agent 从需求分析、Prompt 设计到 Spring Boot 工程实现的全过程。一、需求分析与功能定位1.1 健康陪诊 Agent——首页核心交互入口根据系统设计目标健康陪诊 Agent 扮演全科陪诊员角色驻留于 App 首页为老年人提供常见养生保健知识问答如吃什么对血管好身体不适的初步判断与就医引导如头晕可能是什么原因就诊前准备事项提醒与情绪安抚疑似急症的紧急升级建议拨打 120核心定位是有温度的全科导诊台——专业但不生硬有边界但不拒人于外。1.2 健康干预 Agent——千人千面的方案引擎健康干预 Agent 的设计目标是打破传统健康建议千篇一律的困境。它必须做到深度读取老人的数字病历夹数据疾病史、用药清单、体征指标基于真实病理特征进行 LLM 推理而非给出通用建议支持饮食营养、运动康复、作息指导三个细分方向输出具体、可执行的量化建议如每日钠盐控制在 5g 以内二、Prompt 工程设计2.1 健康陪诊的 Prompt 策略健康陪诊 Agent 面对的问题最为多样化需要 Prompt 同时具备广度覆盖和安全约束你是一位专业的全科健康陪诊助手名叫康伴。 你驻留于智能居家养老健康守护系统的首页是老年人日常健康咨询的核心交互入口。 你的职责 1. 提供24小时全天候的专业养生问答与健康科普 2. 帮助老人初步判断身体不适的可能原因引导合理就医 3. 提供日常保健建议包括饮食、作息、运动等方面 4. 解答常见疾病的预防知识和注意事项 5. 在老人描述症状时帮助整理信息以便就诊时表达清晰 6. 缓解老人就医前的紧张情绪提供陪伴支持 重要原则 - 你不是医生绝不做明确诊断或开具处方 - 遇到疑似急症胸痛、呼吸困难、突发偏瘫、剧烈腹痛等立即建议拨打120 - 用简单易懂的语言解释医学概念避免过度专业术语 - 对不确定的问题坦诚表达不确定性建议就医确认设计要点角色命名赋予康伴这个名字不仅是人格化设计更能让老年用户形成固定的交互预期——找康伴问问比打开 AI 对话更符合老年人的认知习惯。否定式安全边界在医疗场景中明确告知模型不做什么比列举做什么更有效。绝不做明确诊断这类强否定指令可以显著降低模型幻觉风险。紧急升级机制在 Prompt 中显式列举急症特征胸痛、呼吸困难等确保模型在识别到这些关键词时立即切换为建议就医模式而非继续给出调养建议。Temperature 设置为 0.6这个值在创造性回答多样化问题和保持医学表述准确性之间取得平衡。2.2 健康干预的分模式 Prompt 架构健康干预 Agent 面临一个工程挑战一个 Agent 需要覆盖三个专业子领域。如果把所有指令塞进一个 Prompt模型容易在回复中混杂多个领域内容。我采用**基础 Prompt 模式 Prompt的叠加架构**privateStringbuildSystemPrompt(ElderlyContextcontext,StringinterventionType){StringBuildersbnewStringBuilder(BASE_PROMPT);// 通用角色定义sb.append(getInterventionPrompt(interventionType));// 子领域专项指令if(context!null){sb.append(\n\n以下是当前老人的详细健康档案数据\n);sb.append(context.toFullContextString());// 注入个人数据}returnsb.toString();}以饮食模式为例其模式 Prompt 明确定义了输出格式当前为【饮食营养】干预模式。重点关注 - 根据疾病制定每日营养配比热量、三大营养素、微量元素 - 推荐和禁忌食物清单 - 提供具体的每日食谱建议 - 考虑药食交互作用 - 注意老年人咀嚼与消化能力 回复格式建议 - 【饮食原则】基于病情的核心饮食要求 - 【推荐食材】可多食用的食物 - 【禁忌/少食】需要避免或减少的食物 - 【参考食谱】一日三餐的具体建议结构化输出引导的优势通过在 Prompt 中预设输出模板模型生成的内容更加规范前端也更容易做分段渲染和格式化展示。2.3 上下文注入的隐式策略当老人的健康档案数据被注入 System Prompt 时需要一个关键指令以下是当前对话老人的背景信息请在对话中适当参考 不要主动提及你已知道这些信息除非与对话内容相关这条隐式规则解决了两个问题防止 AI 每次对话开头都说根据您的高血压病史…让老人感到不适引导模型在用户主动提问相关话题时才自然地引用背景数据三、Spring Boot 服务层实现3.1 统一的 Service 架构两个 Agent 共享统一的实现范式Controller (接口层) ↓ Service (业务逻辑) ├── resolveSession() → 会话管理 ├── loadContext() → 上下文加载 ├── buildMessages() → 消息构建 └── callLLM() → 模型调用同步/流式以健康陪诊的非流式实现为例OverridepublicStringchat(HealthChatRequestrequest){ElderlyContextcontextelderlyContextService.getFullContext(request.getElderlyId());ChatSessionsessionresolveSession(request);session.addMessage(newChatMessage(user,request.getMessage()));StringurldeepSeekConfig.getBaseUrl()/v1/chat/completions;HttpHeadersheadersnewHttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);headers.setBearerAuth(deepSeekConfig.getApiKey());MapString,ObjectbodyMap.of(model,deepSeekConfig.getModel(),messages,buildMessages(session,context),temperature,0.6,max_tokens,1500);HttpEntityMapString,ObjectentitynewHttpEntity(body,headers);ResponseEntityStringresponserestTemplate.exchange(url,HttpMethod.POST,entity,String.class);JsonNoderootobjectMapper.readTree(response.getBody());Stringreplyroot.path(choices).get(0).path(message).path(content).asText();session.addMessage(newChatMessage(assistant,reply));chatSessionService.saveSession(session);returnreply;}3.2 SSE 流式响应的实现为解决老年人等待 AI 回复时的焦虑感两个 Agent 均实现了流式逐字输出。核心采用 Java 11HttpClientSseEmitter组合OverridepublicSseEmitterchatStream(HealthChatRequestrequest){SseEmitteremitternewSseEmitter(120_000L);ElderlyContextcontextelderlyContextService.getFullContext(request.getElderlyId());ChatSessionsessionresolveSession(request);session.addMessage(newChatMessage(user,request.getMessage()));executor.execute(()-{try{StringfullReplystreamFromDeepSeek(session,context,emitter);session.addMessage(newChatMessage(assistant,fullReply));chatSessionService.saveSession(session);}catch(Exceptione){log.error(流式对话异常,e);try{emitter.send(SseEmitter.event().name(error).data(服务暂时不可用请稍后重试));}catch(Exceptionignored){}emitter.completeWithError(e);}});emitter.onTimeout(emitter::complete);emitter.onError(t-log.warn(SSE 连接异常断开,t));returnemitter;}流式解析 DeepSeek 返回的 SSE 数据流try(BufferedReaderreadernewBufferedReader(newInputStreamReader(response.body(),StandardCharsets.UTF_8))){Stringline;while((linereader.readLine())!null){if(line.isBlank()||!line.startsWith(data: ))continue;Stringdataline.substring(6).trim();if([DONE].equals(data)){emitter.send(SseEmitter.event().name(done).data([DONE]));break;}JsonNodenodeobjectMapper.readTree(data);Stringcontentnode.path(choices).get(0).path(delta).path(content).asText();if(!content.isEmpty()){fullReply.append(content);emitter.send(SseEmitter.event().name(message).data(content));}}}3.3 健康干预的多模式路由健康干预 Agent 的独特之处在于通过interventionType字段动态切换 Prompt 模式privateStringgetInterventionPrompt(Stringtype){if(typenull)return;returnswitch(type.toLowerCase()){casediet-\n\nDIET_PROMPT;caseexercise-\n\nEXERCISE_PROMPT;caselifestyle-\n\nLIFESTYLE_PROMPT;default-;// comprehensive 模式使用基础 Prompt 即可};}前端通过一个选择器让用户选定干预类型后端根据类型动态组装 System Prompt实现一个 Agent 多种人格的效果。3.4 RESTful API 设计两个 Agent 的 Controller 层暴露统一风格的接口POST/api/health-companion/chat/stream// 健康陪诊-流式对话POST/api/health-companion/chat// 健康陪诊-普通对话GET/api/health-companion/sessions// 会话列表GET/api/health-companion/sessions/{id}// 会话详情DELETE/api/health-companion/sessions/{id}// 删除会话POST/api/health-intervention/chat/stream// 健康干预-流式对话POST/api/health-intervention/chat// 健康干预-普通对话GET/api/health-intervention/sessions// 会话列表...请求 DTO 的差异体现了两个 Agent 的功能区别// 健康陪诊通用问答只需消息内容publicclassHealthChatRequest{privateStringsessionId;privateLongelderlyId;privateStringmessage;}// 健康干预需要指定干预类型publicclassHealthInterventionRequest{privateStringsessionId;privateLongelderlyId;privateStringmessage;privateStringinterventionType;// diet / exercise / lifestyle / comprehensive}四、开发中的关键决策与踩坑4.1 Temperature 参数调优初版实现中两个 Agent 都使用了 0.7 的默认 temperature。测试中发现健康陪诊在回答头晕怎么办类问题时偶尔会给出不够严谨的建议 → 降低至 0.6健康干预在生成饮食方案时过于模板化 → 尝试 0.7 后发现会编造不存在的食谱 → 最终选定 0.5结论医疗场景的 Temperature 宁可偏低用 Prompt 中的结构化模板来引导多样性比靠高 Temperature 获取创造性更可控。4.2 上下文数据缺失的降级处理当老人尚未上传任何病历资料时ElderlyContextService返回的数据可能为空。健康干预 Agent 需要优雅降级if(contextnull){sb.append(\n\n注意当前未获取到老人的健康档案数据请引导老人先完善病历资料同时给出通用性建议并标注建议补充个人健康数据以获得更精准的方案);}4.3 长对话的 Token 管理多轮对话累积后messages数组可能超出模型上下文窗口。当前采用max_tokens限制输出长度作为缓冲后续规划实现基于摘要压缩的历史消息裁剪策略。五、效果验证与测试场景测试中验证了以下核心场景健康陪诊普通养生问答“冬天怎么泡脚比较好” → 给出水温、时长建议症状咨询“最近老是头晕” → 列举可能原因 建议就医检查急症触发“突然胸口很痛” → 立即建议拨打 120健康干预高血压 糖尿病患者饮食模式自动推荐低钠、低糖膳食方案标注药食交互注意事项运动模式推荐低强度有氧运动提示血压过高时暂停运动作息模式根据用药时间建议合理的起居安排六、AI 辅助开发实录——提示词与人工调试开发过程中我使用了 AI 编程工具辅助生成部分代码骨架但实际落地远非一句提示词就能跑。以下记录真实的 AI 协作过程和必须人工介入的部分。6.1 使用的核心提示词生成健康干预 Agent 的初版 Service 时我给 AI 的提示词如下参照已有的 EmotionalCompanionServiceImpl 的流式对话实现模式为我写一个 HealthInterventionServiceImpl。要求 1. 支持通过 interventionType 字段动态切换子领域 promptdiet/exercise/lifestyle 2. 每种子领域 prompt 需要定义结构化的输出格式模板 3. 当 ElderlyContext 为 null 时做降级处理提示用户完善档案 4. temperature 设为 0.5max_tokens 设为 2000 5. 其余部分保持与 EmotionalCompanionServiceImpl 一致的会话管理和 SSE 流模式AI 生成的代码基本可用但有若干问题需要我手动修复见下文。另一个关键提示词用于设计健康陪诊 Agent 的 System Prompt 本身我需要写一段 LLM 的 system prompt角色是面向老年人的全科健康陪诊助手。 要求 - 不能给诊断结论必须引导就医 - 遇到急症关键词胸痛、呼吸困难、偏瘫必须建议 120 - 语言要通俗但不能丧失专业性 - 不能主动暴露自己已知用户的病史信息 给我一版 prompt我再基于实际测试效果迭代调整AI 给出的第一版 Prompt 过于冗长约 800 字且安全边界描述太笼统只写了不给医疗建议。我手动迭代了三轮砍掉重复表述将核心指令压缩到 400 字以内把不给医疗建议拆解为具体场景不诊断、不开处方、急症升级增加回答风格段落约束回复的结构和节奏6.2 AI 生成代码的 Bug 与人工修复Bug 1流式 SSE 中emitter.complete()的重复调用AI 生成的代码在streamFromDeepSeek方法末尾调用了emitter.complete()同时在外层executor.execute的 try 块结束后又调用了一次。两次 complete 导致第二次抛出IllegalStateException日志中出现大量异常栈。// AI 生成的有问题的代码executor.execute(()-{try{StringfullReplystreamFromDeepSeek(session,context,emitter);// streamFromDeepSeek 内部已经 emitter.complete()session.addMessage(newChatMessage(assistant,fullReply));chatSessionService.saveSession(session);emitter.complete();// ← 重复调用抛异常}catch(Exceptione){...}});修复去掉外层的emitter.complete()调用仅在streamFromDeepSeek内部流结束时调用一次。Bug 2buildMessages中 System Prompt 未带上下文数据AI 第一版将buildMessages写成了messages.add(Map.of(role,system,content,SYSTEM_PROMPT));直接使用了静态常量完全忽略了动态注入的 ElderlyContext。测试时发现模型对我血压高应该吃什么的回复完全是通用建议没有引用用户实际的血压数值——这时候才发现上下文没进去。修复改为调用buildSystemPrompt(context, interventionType)方法拼接完整 Prompt。Bug 3interventionType为 null 时 switch 空指针健康干预请求中interventionType是选填字段。AI 生成的 switch 表达式直接对type调用了.toLowerCase()returnswitch(type.toLowerCase()){// ← type 为 null 时 NPEcasediet-...上线后前端在综合模式下未传该字段直接 NPE 500。修复在 switch 之前加 null 判断privateStringgetInterventionPrompt(Stringtype){if(typenull)return;returnswitch(type.toLowerCase()){...};}Bug 4SSE 流中 DeepSeek 返回的非标准行导致 JSON 解析异常AI 生成的 SSE 解析代码没有对objectMapper.readTree(data)做 try-catch而实际测试中发现 DeepSeek 偶尔会返回类似: keep-alive的心跳行不符合data:前缀但也不为空以及偶发的data: {error:...}错误响应。这些情况直接导致 JsonNode 取choices[0]时空指针。修复内层加 try-catch 跳过解析失败的行并记录 debug 日志try{JsonNodenodeobjectMapper.readTree(data);JsonNodedeltanode.path(choices).get(0).path(delta);Stringcontentdelta.path(content).asText();if(!content.isEmpty()){fullReply.append(content);emitter.send(SseEmitter.event().name(message).data(content));}}catch(Exceptione){log.debug(跳过无法解析的 SSE 数据: {},data);}6.3 小结AI 辅助开发确实加速了代码骨架的搭建尤其是重复结构的 Controller 和 DTO但在以下环节人工介入不可替代Prompt 的迭代调优需要反复实测真实场景才能打磨出合格的 System Prompt运行时异常排查SSE 的线程模型和流式 API 的边界情况只有跑起来才能暴露业务逻辑的正确性上下文是否真的注入了、null 场景是否覆盖了这些 AI 无法自行验证七、总结两个 Agent 的开发实践让我认识到Prompt 工程是核心竞争力相同的模型不同的 Prompt 可以产出天差地别的效果分模式架构比多 Agent 拆分更经济健康干预的 diet/exercise/lifestyle 三个方向共享基础设施通过 Prompt 叠加实现差异化医疗场景安全边界必须在 Prompt 和代码双层设定不能仅靠模型自觉流式输出不是锦上添花而是刚需对于老年用户3-5 秒的等待空白足以让他们以为系统死机了AI 辅助编码不等于无脑生成骨架可以让 AI 写但运行时行为必须人工验证和调试