山东大学软件学院项目实训-基于语言大模型的智能居家养老健康守护系统-个人博客(五)
智能健康陪诊与个性化干预 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"的叠加架构**:
private String buildSystemPrompt(ElderlyContext context, String interventionType) {
StringBuilder sb = new StringBuilder(BASE_PROMPT); // 通用角色定义
sb.append(getInterventionPrompt(interventionType)); // 子领域专项指令
if (context != null) {
sb.append("\n\n以下是当前老人的详细健康档案数据:\n");
sb.append(context.toFullContextString()); // 注入个人数据
}
return sb.toString();
}
以饮食模式为例,其模式 Prompt 明确定义了输出格式:
当前为【饮食营养】干预模式。重点关注:
- 根据疾病制定每日营养配比(热量、三大营养素、微量元素)
- 推荐和禁忌食物清单
- 提供具体的每日食谱建议
- 考虑药食交互作用
- 注意老年人咀嚼与消化能力
回复格式建议:
- 【饮食原则】基于病情的核心饮食要求
- 【推荐食材】可多食用的食物
- 【禁忌/少食】需要避免或减少的食物
- 【参考食谱】一日三餐的具体建议
结构化输出引导的优势:通过在 Prompt 中预设输出模板,模型生成的内容更加规范,前端也更容易做分段渲染和格式化展示。
2.3 上下文注入的隐式策略
当老人的健康档案数据被注入 System Prompt 时,需要一个关键指令:
以下是当前对话老人的背景信息,请在对话中适当参考
(不要主动提及你已知道这些信息,除非与对话内容相关)
这条"隐式规则"解决了两个问题:
- 防止 AI 每次对话开头都说"根据您的高血压病史…",让老人感到不适
- 引导模型在用户主动提问相关话题时才自然地引用背景数据
三、Spring Boot 服务层实现
3.1 统一的 Service 架构
两个 Agent 共享统一的实现范式:
Controller (接口层)
↓
Service (业务逻辑)
├── resolveSession() → 会话管理
├── loadContext() → 上下文加载
├── buildMessages() → 消息构建
└── callLLM() → 模型调用(同步/流式)
以健康陪诊的非流式实现为例:
@Override
public String chat(HealthChatRequest request) {
ElderlyContext context = elderlyContextService.getFullContext(request.getElderlyId());
ChatSession session = resolveSession(request);
session.addMessage(new ChatMessage("user", request.getMessage()));
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(deepSeekConfig.getApiKey());
Map<String, Object> body = Map.of(
"model", deepSeekConfig.getModel(),
"messages", buildMessages(session, context),
"temperature", 0.6,
"max_tokens", 1500
);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
JsonNode root = objectMapper.readTree(response.getBody());
String reply = root.path("choices").get(0).path("message").path("content").asText();
session.addMessage(new ChatMessage("assistant", reply));
chatSessionService.saveSession(session);
return reply;
}
3.2 SSE 流式响应的实现
为解决老年人等待 AI 回复时的焦虑感,两个 Agent 均实现了流式逐字输出。核心采用 Java 11 HttpClient + SseEmitter 组合:
@Override
public SseEmitter chatStream(HealthChatRequest request) {
SseEmitter emitter = new SseEmitter(120_000L);
ElderlyContext context = elderlyContextService.getFullContext(request.getElderlyId());
ChatSession session = resolveSession(request);
session.addMessage(new ChatMessage("user", request.getMessage()));
executor.execute(() -> {
try {
String fullReply = streamFromDeepSeek(session, context, emitter);
session.addMessage(new ChatMessage("assistant", fullReply));
chatSessionService.saveSession(session);
} catch (Exception e) {
log.error("流式对话异常", e);
try {
emitter.send(SseEmitter.event().name("error").data("服务暂时不可用,请稍后重试"));
} catch (Exception ignored) {}
emitter.completeWithError(e);
}
});
emitter.onTimeout(emitter::complete);
emitter.onError(t -> log.warn("SSE 连接异常断开", t));
return emitter;
}
流式解析 DeepSeek 返回的 SSE 数据流:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(response.body(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.isBlank() || !line.startsWith("data: ")) continue;
String data = line.substring(6).trim();
if ("[DONE]".equals(data)) {
emitter.send(SseEmitter.event().name("done").data("[DONE]"));
break;
}
JsonNode node = objectMapper.readTree(data);
String content = node.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 模式:
private String getInterventionPrompt(String type) {
if (type == null) return "";
return switch (type.toLowerCase()) {
case "diet" -> "\n\n" + DIET_PROMPT;
case "exercise" -> "\n\n" + EXERCISE_PROMPT;
case "lifestyle" -> "\n\n" + LIFESTYLE_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 的功能区别:
// 健康陪诊:通用问答,只需消息内容
public class HealthChatRequest {
private String sessionId;
private Long elderlyId;
private String message;
}
// 健康干预:需要指定干预类型
public class HealthInterventionRequest {
private String sessionId;
private Long elderlyId;
private String message;
private String interventionType; // 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 (context == null) {
sb.append("\n\n(注意:当前未获取到老人的健康档案数据,请引导老人先完善病历资料," +
"同时给出通用性建议并标注'建议补充个人健康数据以获得更精准的方案')");
}
4.3 长对话的 Token 管理
多轮对话累积后,messages 数组可能超出模型上下文窗口。当前采用 max_tokens 限制输出长度作为缓冲,后续规划实现基于摘要压缩的历史消息裁剪策略。
五、效果验证与测试场景
测试中验证了以下核心场景:
健康陪诊:
- 普通养生问答:“冬天怎么泡脚比较好” → 给出水温、时长建议
- 症状咨询:“最近老是头晕” → 列举可能原因 + 建议就医检查
- 急症触发:“突然胸口很痛” → 立即建议拨打 120
健康干预(高血压 + 糖尿病患者):
- 饮食模式:自动推荐低钠、低糖膳食方案,标注药食交互注意事项
- 运动模式:推荐低强度有氧运动,提示血压过高时暂停运动
- 作息模式:根据用药时间建议合理的起居安排
六、AI 辅助开发实录——提示词与人工调试
开发过程中我使用了 AI 编程工具辅助生成部分代码骨架,但实际落地远非"一句提示词就能跑"。以下记录真实的 AI 协作过程和必须人工介入的部分。
6.1 使用的核心提示词
生成健康干预 Agent 的初版 Service 时,我给 AI 的提示词如下:
参照已有的 EmotionalCompanionServiceImpl 的流式对话实现模式,为我写一个
HealthInterventionServiceImpl。要求:
1. 支持通过 interventionType 字段动态切换子领域 prompt(diet/exercise/lifestyle)
2. 每种子领域 prompt 需要定义结构化的输出格式模板
3. 当 ElderlyContext 为 null 时做降级处理,提示用户完善档案
4. temperature 设为 0.5,max_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 {
String fullReply = streamFromDeepSeek(session, context, emitter);
// streamFromDeepSeek 内部已经 emitter.complete()
session.addMessage(new ChatMessage("assistant", fullReply));
chatSessionService.saveSession(session);
emitter.complete(); // ← 重复调用,抛异常
} catch (Exception e) { ... }
});
修复:去掉外层的 emitter.complete() 调用,仅在 streamFromDeepSeek 内部流结束时调用一次。
Bug 2:buildMessages 中 System Prompt 未带上下文数据
AI 第一版将 buildMessages 写成了:
messages.add(Map.of("role", "system", "content", SYSTEM_PROMPT));
直接使用了静态常量,完全忽略了动态注入的 ElderlyContext。测试时发现模型对"我血压高应该吃什么"的回复完全是通用建议,没有引用用户实际的血压数值——这时候才发现上下文没进去。
修复:改为调用 buildSystemPrompt(context, interventionType) 方法拼接完整 Prompt。
Bug 3:interventionType 为 null 时 switch 空指针
健康干预请求中 interventionType 是选填字段。AI 生成的 switch 表达式直接对 type 调用了 .toLowerCase():
return switch (type.toLowerCase()) { // ← type 为 null 时 NPE
case "diet" -> ...
上线后前端在"综合模式"下未传该字段,直接 NPE 500。
修复:在 switch 之前加 null 判断:
private String getInterventionPrompt(String type) {
if (type == null) return "";
return switch (type.toLowerCase()) { ... };
}
Bug 4:SSE 流中 DeepSeek 返回的非标准行导致 JSON 解析异常
AI 生成的 SSE 解析代码没有对 objectMapper.readTree(data) 做 try-catch,而实际测试中发现 DeepSeek 偶尔会返回类似 : keep-alive 的心跳行(不符合 data: 前缀但也不为空),以及偶发的 data: {"error":...} 错误响应。这些情况直接导致 JsonNode 取 choices[0] 时空指针。
修复:内层加 try-catch 跳过解析失败的行,并记录 debug 日志:
try {
JsonNode node = objectMapper.readTree(data);
JsonNode delta = node.path("choices").get(0).path("delta");
String content = delta.path("content").asText("");
if (!content.isEmpty()) {
fullReply.append(content);
emitter.send(SseEmitter.event().name("message").data(content));
}
} catch (Exception e) {
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 写,但运行时行为必须人工验证和调试
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)