多模态数据处理实践:分离API、识图功能开发与定时任务设置
AI-HEALTH 智能健康分析系统:
本文基于 AI-HEALTH 项目真实架构撰写,聚焦大模型 API 集成、提示词工程、识图与文字处理分离、异常数据过滤、定时任务自动生成健康报告五大核心技术领域。面向有 LLM 应用开发经验的技术读者,在工程实现和设计思路上展开论述。
一、整体架构概览
AI-HEALTH 是一个基于 Spring Boot + Vue 3 的个人健康管理系统,核心智能化能力由大语言模型驱动。系统通过调用 Sophnet API 接入两款主流大模型,构建了一条完整的"数据采集 → 多模态识别 → 结构化解析 → 智能分析 → 定时报告"流水线。
核心技术决策
| 决策项 | 选择 | 理由 |
|---|---|---|
| 视觉模型 | Kimi-K2.6 | 多模态能力强,对中文 OCR 与结构化提取效果好 |
| 文本模型 | DeepSeek-V4-Pro | 长文本分析能力强,支持复杂推理,性价比高 |
| API 调用方式 | RestTemplate 同步调用 | 业务场景对延迟要求适中,同步调用简化状态管理 |
| 响应解析 | 手动 DOM 解析 + 正则 | 避免 LLM 输出格式漂移导致 Jackson 反序列化失败 |
| 异常过滤 | Prompt 内嵌规则 | 无需额外代码层,利用 LLM 推理能力灵活处理 |
二、提示词工程:从单次问答到多模板体系
提示词工程是本项目 AI 能力的核心。我们经过多轮迭代,沉淀出一套"角色定义 + 数据注入 + 约束规格 + 格式锚定"的四段式 Prompt 构建方法论。
2.1 四段式 Prompt 构建框架
这个框架贯穿了食物识别、截图提取、健康分析、AI 助手对话四个场景。以一个具体的食物识别 Prompt 为例:
角色定义(System Prompt):"你是一个食物营养分析器。只返回 JSON,不要任何解释。"——角色限定为"食物营养分析器",输出格式锁定为 JSON,禁止解释性文本。这个 System Prompt 只有两句话,却完成了三个关键约束:专业领域限定、输出格式锁定、冗余内容抑制。
数据注入(User Prompt):将 Base64 编码的食物图片以内联方式嵌入请求,使用 OpenAI Vision 兼容的 data:image/jpeg;base64,{...} 格式。同时注入明确的结构化要求——对每种食物需要识别名称、分量、卡路里、碳水、蛋白质、脂肪,整餐需要汇总营养数据。
约束规格:要求"估算"而非精确计算、"中文名称"等,这些软约束既保证了输出的一致性,也为模型的合理推断留出了空间。
格式锚定:在 Prompt 末尾给出精确的 JSON 模板 {"foodItems": [{...}], "totalCalories": 数字, ...},并用指令"只返回 JSON,不要解释"锁死输出格式。这大大降低了解析层的复杂度。
2.2 五大场景的 Prompt 差异化设计
场景路由设计——截图分析服务通过 sceneType 参数动态切换 Prompt 模板。传入 "workout" 时,Prompt 要求提取运动时长、距离、心率、配速、步频等字段;传入 "sleep" 时,切换为深睡时长、REM 时长、睡眠评分、醒来次数等字段。无法匹配预定义字段的信息统一归入 extraNotes 字段,既保证了结构化数据的整洁性,又防止信息丢失。
2.3 温度参数的工程含义
温度(Temperature)是 LLM 输出随机性的控制参数(0~1)。在本项目中,不同场景的温度选择经过了针对性的调优:
| 场景 | 温度值 | 设计意图 |
|---|---|---|
| 食物识别 | 0.1 | 需要稳定、可预测的 JSON 输出;食物名称和营养估算不能"即兴发挥" |
| 截图提取 | 0.1 | 从固定格式截图中提数,输出的一致性远比"创造性"重要 |
| 健康分析 | 0.5 | 需要在专业分析和个性化建议之间找到平衡;过冷则报告千篇一律,过热则可能偏离医学常识 |
| AI 助手 | 0.7 | 对话场景需要自然流畅的交流体验;用户期望感受到"有温度"的陪伴 |
三、识图与文字处理分离:两条流水线的架构设计
3.1 为什么分离
最初的设计是所有 AI 调用使用同一个模型和同一个服务类。但很快我们发现两个问题:
- 模型能力不对称:视觉模型(Kimi-K2.6)擅长图像理解但文本分析能力相对较弱;文本模型(DeepSeek-V4-Pro)反之。用同一个模型同时处理图像和长文本分析,要么精度不够,要么成本过高。
- Prompt 冲突:图像识别的 Prompt 要求输出严格 JSON;健康分析的 Prompt 要求输出结构化 Markdown。两种输出格式共享同一个 System Prompt 角色设定会导致模型"人格分裂"。
事实上,这两个问题都不是最关键的,是AI在这瞎扯淡,我们决策的唯一原因是Kimi-K2.6太贵了,我们想给戴老师省点钱,再加上做这种分离的API设计好像不困难,于是就做了。

3.2 架构分离策略
分离带来的直接收益:
- Prompt 独立性:每个服务维护自己的 Prompt 模板,彼此不受影响。食物识别的
"只返回 JSON,不要解释"不会污染健康分析的 Markdown 输出要求。 - 模型匹配:视觉任务走 Kimi-K2.6,文本分析走 DeepSeek-V4-Pro,各展所长。
- 独立迭代:截图提取新增一种穿戴设备格式时,只需修改 ScreenshotAnalysisService 的 Prompt,不影响其他服务。
- 容错隔离:某个识别服务的解析异常不会导致整个健康分析管道崩溃。
3.3 视觉流水线的 JSON 解析策略
视觉模型的输出格式是 "JSON 包裹在 Markdown 代码块中",即模型可能返回:
{"foodItems": [...], "totalCalories": 850}
也可能直接返回裸 JSON,甚至夹杂解释性文字。为应对这种不确定性,我们选择手动 DOM 解析而非 Jackson 自动反序列化。
核心思路:先通过正则清洗 Markdown 标记(^```json / ^````/ ```$),再定位最外层 {…}边界,然后逐字段提取数值。对于嵌套数组(如foodItems`),使用括号匹配算法找到数组起止位置后逐元素解析。这种方案虽然代码量更大,但对 LLM 输出格式的微小偏移容忍度极高,在真实生产环境中远比 Jackson + try-catch 兜底更可靠。
四、异常数据过滤:让 AI 做"数据清洗师"
健康数据来源多样——手动录入、AI 识图估算、截图 OCR 提取——不可避免地存在异常值。传统的做法是在入库前做硬编码校验(if calories > 10000: reject),但这有两个缺陷:一是指定阈值难以覆盖所有场景(运动员的单餐热量可能确实很高);二是硬拒绝会丢失数据,用户可能不知道自己录入了异常值。
4.1 方案设计:Prompt 内嵌过滤
我们将异常检测逻辑内嵌到健康分析的 Prompt 中,让 LLM 在生成报告时自动识别并处理:
**异常数据过滤**:如果某条记录存在明显不合理的数据
(如单餐热量超过 5000 千卡、一顿饭超过 50 个食物、
运动时长超过 24 小时连续、单次运动消耗超过 10000 千卡、
睡眠超过 20 小时等),请在分析中标注并使用合理范围估算,
不要直接引用异常值
4.2 设计决策:过滤 vs 拒绝
选择"标注而非拒绝"的理由:
- 数据不丢失:异常值可能是用户真实情况的反映(如马拉松运动员的日消耗),全量保留给予人工复核空间。
- 用户可感知:报告中的异常标注本身就是一种用户教育——“您的某条记录看起来不太合理,建议核实”。
- 阈值可动态调整:修改 Prompt 文本即可调整异常判定规则,无需改动代码、重新部署。
4.3 分级阈值体系
| 类别 | 阈值 | 说明 |
|---|---|---|
| 单餐热量 | > 5000 kcal | 正常成人一餐约 500-1200 kcal |
| 单餐食物数 | > 50 种 | 自助餐极端情况也极少超过 30 种 |
| 连续运动时长 | > 24 小时 | 排除超马等极端赛事的数据录入错误 |
| 单次运动消耗 | > 10000 kcal | 全程马拉松约消耗 2500-3500 kcal |
| 单次睡眠 | > 20 小时 | 排除因病卧床等特殊情况 |
五、大模型 API 调用封装:从混乱到有序
5.1 调用链路标准化
在项目早期,每个 Service 类各自封装 HTTP 调用逻辑,导致大量重复代码。重构后,虽然各 Service 仍保留独立的 RestTemplate 实例(同步调用的设计选择),但调用的链路上已形成统一模式:
构建 Headers(Bearer Token + Content-Type)
→ 构建 Messages(System Prompt + User Prompt)
→ 设置模型参数(model / temperature / max_tokens)
→ POST /chat/completions
→ 提取 choices[0].message.content
→ 清洗输出(去 Markdown 标记、定位 JSON 边界)
→ 解析为业务对象
→ 异常兜底(返回空结果 / 默认值)
5.2 模型参数调优经验
在实践过程中,我们总结了以下调参经验:
max_tokens的分级设置:食物识别 800(短 JSON),截图提取 800,健康分析 4000(长 Markdown 报告),AI 助手 2000(中等长度对话)。- 内容截断保护:当 AI 返回内容被
max_tokens截断导致 JSON 不完整时,解析层的括号匹配算法能检测到数组未闭合,触发异常兜底逻辑,而非返回残缺数据给前端。 - 重试机制的阶段性取舍:当前版本未实现自动重试(
temperature=0.1的场景下输出高度一致,重试意义有限)。计划在后续引入指数退避重试,主要针对网络抖动导致的超时。
5.3 配置外部化
所有 API 相关配置统一管理在 application.properties 中,支持环境差异:
ai.api.key=${AI_API_KEY:default_key}
ai.api.url=${AI_API_URL:https://www.sophnet.com/api/open-apis/v1}
ai.analysis.model=DeepSeek-V4-Pro
ai.vision.model=Kimi-K2.6
通过 Spring 的 @Value 注解注入,支持通过环境变量覆盖(${AI_API_KEY:default_key}),实现开发/测试/生产环境的无缝切换。
六、定时任务自动生成报告
6.1 调度体系设计
系统内置三级定时任务,覆盖日、周、月三个时间维度:
三个 Cron 表达式驱动:
| 任务 | Cron | 语义 |
|---|---|---|
| 日报 | 0 50 9 * * * | 每天上午 9:50,分析前一日数据 |
| 周报 | 0 0 10 * * MON | 每周一上午 10:00,分析上周一至周日数据 |
| 月报 | 0 0 10 1 * * | 每月 1 日上午 10:00,分析上月整月数据 |
时间窗口的选取经过了考量:日报选在 9:50 而非午夜,是因为用户可能在凌晨录入数据;周报选在周一上午,符合职场用户"周一查看上周总结"的习惯。
6.2 遍历用户生成 + 容错机制
定时任务的核心逻辑是遍历所有用户逐个生成报告。这意味着 N 个用户会触发 N 次 LLM 调用,整个周期可能持续数分钟。为此设计了以下容错策略:
- 单用户异常隔离:每个用户的报告生成包裹在
try-catch中——用户 A 生成失败不会阻塞用户 B。 - 日志记录:异常信息通过 SLF4J 记录完整的用户名和堆栈信息,便于追溯。
- 通知推送:报告生成成功后,通过 NotificationService 创建站内通知,引导用户查看。
- 幂等性考虑:当前版本按时间窗口生成报告(指定 startDate/endDate),天然支持幂等——同一时段多次执行不会产生重复的差异化报告。
6.3 报告从 Markdown 到 HTML 的转换
LLM 输出的健康报告是 Markdown 格式,存储为 LONGTEXT。在用户查看时,ReportHtmlService 将其实时转换为 HTML 页面。转换逻辑不依赖第三方 Markdown 解析库,而是用正则直接处理:
**加粗**→<strong># ## ### ####→<h1>~<h4>- 列表项→<ul><li>- 连续的段落文本 →
<p>
选择手写转换而非引入 Markdown 库的理由:LLM 输出的 Markdown 格式高度规整(因为 Prompt 中已指定了格式规范),不存在"脏 Markdown"的兼容性问题。手写转换器仅需覆盖已知的 6 种格式标记,代码量不足 100 行。
七、数据闭环:从识别到分析的完整链路
以一条完整的用户旅程为例,串联所有模块:
阶段一的关键点
食物识别的输入是一张照片,输出是带营养估算的结构化记录。这里有两个工程挑战:
- 图片传输格式:前端将图片转 Base64 后通过 FormData 上传。后端接收后直接拼接为
data:image/jpeg;base64,{...}传入 LLM,避免服务器端文件存储再读取的 IO 开销。 - JSON 解析容错:上文已详述,手动 DOM 解析处理 LLM 输出的各种格式变体。
阶段二的关键点
定时任务的核心挑战是遍历大量用户的耗时控制。当前实现是顺序遍历,若用户量增长,可改进为并行流(parallelStream)或消息队列异步处理。
阶段三的关键点
报告的"生成"和"查看"是解耦的:定时任务预生成报告存入数据库,用户查看时只需从 DB 读取并转换 HTML。这种设计避免了用户查看时实时调用 LLM 的延迟(15-60 秒),将首屏加载控制在毫秒级。
八、演进方向的思考
8.1 当前阶段的不足
- 视觉识别的局限性:仅有食物识别和两大运动/睡眠场景的截图提取,尚未覆盖心率变异性(HRV)、血氧、血压等更多健康维度。
- 异常过滤的精度依赖:Prompt 内嵌阈值依赖 LLM 的推理能力,在极端边缘场景可能漏判或误判。
- 定时任务的单点风险:遍历用户顺序执行,用户量增长后存在超时风险。
8.2 后续规划
| 方向 | 说明 |
|---|---|
| 向量化检索增强生成(RAG) | 引入嵌入模型对历史健康报告建立向量索引,新报告生成时检索相似历史案例作为上下文,提升分析的个性化程度 |
| 式输出(SSE) | 将 AI 助手对话从同步请求-响应改为 Server-Sent Events 流式输出,改善用户体验 |
| 多轮对话记忆 | AI 助手引入滑动窗口对话历史管理,支持跨会话的健康建议连续性 |
| 用户反馈闭环 | 收集用户对报告的满意度评分,微调 Prompt 策略 |
| 异步报告生成 | 引入消息队列解耦定时任务和 LLM 调用,支持水平扩展 |
九、总结
AI-HEALTH 的大模型集成实践,核心围绕一个命题展开:如何让大模型在健康管理场景下既"看得准"又"说得对"。
"看得准"依赖于视觉流水线和文本流水线的分离架构——Kimi-K2.6 专注图像理解,DeepSeek-V4-Pro 专注文本分析,各自的 Prompt 独立调优。
"说得对"依赖于精心设计的四段式 Prompt 框架——角色定位锁定输出域,数据注入提供事实基础,约束规格嵌入业务规则,格式锚定降低解析复杂度。同时将异常数据过滤逻辑从硬编码校验升级为 Prompt 内嵌的智能标注,兼顾了数据完整性和分析准确性。
这些设计不是一个"完美方案",而是一个在模型能力边界、业务需求、工程成本三者之间不断权衡和迭代的结果——这也是 LLM 应用开发中最真实的一面。
更多推荐



所有评论(0)