核心实战项目(必掌握)
核心实战项目(必掌握)
🧠 先对齐底层认知:Java 做 AI 的三板斧
不管项目怎么变,核心永远是这三件事:
- Embedding(向量化)—— 把文本映射到高维向量空间,用余弦相似度衡量语义距离
- RAG(检索增强生成)—— 先从私有知识库检索,再拼接上下文喂给 LLM,大幅降低幻觉
- Agent / Function Calling—— 让模型学会“用工具”,比如查数据库、调接口、发消息
Java 生态选型:Spring AI + LangChain4j + 向量数据库(Milvus / Pgvector) + Redis + Kafka,足够覆盖 90% 的企业级 AI 场景。
项目全景总览 📊
| 项目名称 | 业务定位 | 核心技术范式 | Java 核心技术栈 | 上线规模 | 核心业务指标 | 垂直核心特性 |
|---|---|---|---|---|---|---|
| AI 智能客服 | 电商平台 7x24 小时用户咨询答疑 | 三级意图路由 + 多轮对话 + RAG 召回 | Spring Boot + LangChain4j + Redis + 双数组 Trie | 日均对话 5w+,对接 8 条业务线 | 问题解决率 87%,人工转接率 13%,单轮响应 < 1s | 会话记忆、敏感词拦截、工单自动结构化生成 |
| AI 内部 Wiki 知识库 | 千人级企业内部知识检索问答 | 语义分片 + 向量检索 + 权限管控 | Spring Boot + Milvus + Canal + RocketMQ | 文档量 10w+,覆盖 12 个部门 | 知识命中率 92%,检索响应 < 400ms | 字段级权限隔离、多格式解析、分钟级增量同步 |
| AI 睡眠讲故事系统 | C 端小程序助眠内容生成 | 流式生成 + TTS 分片合成 + 场景 Prompt | Spring WebFlux + 讯飞 TTS + SSE + Reactor | 月活用户 8w+,日均生成故事 3w 条 | 首字延迟 760ms,音频合成延迟 < 900ms | 边生成边播报、分龄内容适配、断点续生成 |
| AI 电子病历系统 | 三甲医院内科文书结构化辅助 | 医疗实体抽取 + 术语映射 + 合规脱敏 | Spring Boot + 医疗大模型 + HBase + KMS | 日均处理病历 2000+,覆盖 6 个科室 | 字段抽取准确率 93%,人工复核率 < 10% | ICD 编码映射、全链路脱敏、操作审计留痕 |
通用 Java AI 工程化底座架构 ⚙️
四个项目完全复用这套工程化底座,核心设计思想是大模型只负责 “理解 + 生成”,所有业务规则、性能保障、合规管控全部在 Java 层落地,避免大模型的不可控性传导到业务层。
底座采用四层解耦架构,层与层通过接口抽象隔离,更换大模型、向量库仅需替换实现类,无需改动业务代码:
- 接入层:统一鉴权、限流、前置校验,拦截非法与违规请求
- 编排层:基于责任链模式实现请求全流程管控,节点可插拔
- AI 能力层:统一封装模型调用、检索、文档处理能力,屏蔽底层差异
- 基础设施层:对接向量库、缓存、消息队列等中间件,提供稳定支撑
分项目专属架构 + 深度技术细节与难点拆解 ✨
1. AI 智能客服 🎧
业务背景:替代 80% 一线人工客服,覆盖售后、物流、活动规则等咨询场景,核心目标是降本提效。
专属三级路由对话流程图
核心技术细节:
- 三级意图路由架构(成本优化核心)
- 一级路由:采用双数组 Trie 实现的 AC 自动机,加载 1200 + 高频 FAQ 关键词库,单条匹配耗时 < 5ms,命中后直接返回 Caffeine 本地缓存的标准答案,完全不调用大模型,覆盖 70% 标准问题
- 二级路由:接入 7B 参数开源轻量模型,采用 3-shot 分类 Prompt,固定输出「咨询 / 投诉 / 闲聊 / 转人工」4 类意图标签;Java 层通过枚举类做结果映射,异常值自动降级到三级路由,单轮耗时 < 200ms,覆盖 15% 简单问题
- 三级路由:剩余 15% 复杂问题走通用大模型 + RAG 知识库召回,生成精准答案
- 精细化会话记忆管理
采用 Redis Hash 存储会话,分为滑动窗口短期记忆 + 摘要式长期记忆两级结构:短期记忆保留最近 3 轮完整对话,长期记忆存储历史对话的压缩摘要。Java 层基于 JTokkit 工具包实时统计 token 数,累计每满 5 轮对话,自动调用大模型生成 100 字以内的摘要更新长期记忆,将上下文 token 数稳定控制在模型窗口的 70% 以内,预留生成空间 - 工单自动结构化抽取
用户诉求判定为投诉 / 售后时,自动触发实体抽取;采用函数调用(Function Calling)强制大模型输出订单号、问题类型、用户诉求、联系方式等固定字段,Java 层直接映射为工单实体写入客服系统,无需人工二次录入
专属技术难点与解决方案:
| 难点描述 | Java 端落地方案 | 落地效果 |
|---|---|---|
| 多轮对话上下文漂移,偏离原始问题 | 加入关键词锚定机制,Java 层提取用户初始问题的 3 个核心关键词,每轮生成 Prompt 时强制注入; 每 3 轮对话做一次主题相关性校验,偏离则主动引导回归 | 上下文偏离率从 22% 降至 6% |
| 大模型输出敏感违规内容 | 前置 DFA 敏感词过滤用户输入,后置二次校验模型输出; 同时在系统 Prompt 中注入合规规则,违规内容直接拦截并返回兜底话术; 所有违规请求记入审计日志 | 违规内容输出率降至 0.01% 以下 |
2. AI 内部 Wiki 知识库 📚
业务背景:解决企业内部知识分散、新人上手慢、老员工重复答疑的问题,覆盖研发规范、人事制度、业务流程等内部知识。
专属双链路架构图(入库 + 检索)
核心技术细节:
- 父子语义分片策略(准确率优化核心)
摒弃固定长度分片,采用基于标题层级的递归语义分片算法:先按文档标题层级拆分章节作为父分片(约 2000token,保留完整上下文),再对每个章节按段落做子分片(512token,重叠 128token);相邻段落通过余弦相似度判断关联性,相似度低于 0.6 则切分,避免语义断裂。每个分片挂载部门权限标签、文档 ID、版本号,父子分片通过分片 ID 关联 - Milvus 向量库深度性能优化
索引采用 HNSW 算法,参数设置为 M=16、efConstruction=200,查询时 ef=64;按部门做分区键,单分区向量规模控制在 100w 以内,避免单索引过大导致检索性能下降;同时开启 Milvus 缓存层,高频查询结果缓存 1 小时,平均检索延迟稳定在 380ms 以内 - 分钟级增量同步与幂等设计
基于 Canal 监听 Wiki 业务库 document 表的 binlog,文档新增 / 修改 / 删除事件携带文档版本号发送到 RocketMQ;消费端基于「文档 ID + 版本号」做幂等校验,旧版本消息直接丢弃;向量化任务提交到自定义线程池(核心线程 8,最大 16,CallerRunsPolicy 拒绝策略),失败自动重试 3 次,异常消息进入死信队列告警 - 多路召回 + 轻量重排
同时执行向量语义召回和 BM25 关键词召回,两路结果去重后,用本地部署的轻量交叉编码器做 Rerank 重排;Java 层通过 HTTP 调用本地推理服务,重排耗时 < 50ms,Top3 召回准确率提升 25%
专属技术难点与解决方案:
| 难点描述 | Java 端落地方案 | 落地效果 |
|---|---|---|
| 长文档碎片化召回,上下文缺失导致答案不准确 | 检索召回子分片后,自动关联对应父分片的完整章节上下文一起喂给大模型; 同时加入查询改写,Java 层先调用小模型把用户问题扩写成 2-3 个同义查询,多路召回去重 | Top3 召回准确率从 72% 提升至 91% |
| 不同部门知识隔离,权限管控粒度粗 | 每个向量分片挂载部门权限标签数组,检索时 Java 层传入当前用户部门列表,作为过滤条件传入 Milvus 查询参数,实现向量检索层面的权限过滤,无权限文档不会被召回 | 权限泄露风险降为 0,满足企业数据安全要求 |
3. AI 睡眠讲故事系统 💤
业务背景:面向 C 端小程序用户,提供定制化助眠故事生成,支持选择主题、角色、语速,适配儿童和成人不同人群。
专属流式生成 + TTS 流水线架构图
核心技术细节:
- 全链路流式响应与流水线并行
基于 Spring WebFlux 的Flux<ServerSentEvent>实现 SSE 文本推送,大模型采用响应式流式调用,每收到一个 token 就写入 Flux 流;同时设置句尾触发逻辑,检测到句号、问号、感叹号等句末标点时,将完整句子提交到 TTS 线程池异步合成语音,音频片段以 byte 数组存入 Redis 并设置过期时间,前端按句序号顺序拉取播放,实现「生成 - 合成 - 播放」三级流水线并行 - 背压与速率匹配优化
针对大模型生成速度与 TTS 合成速度不匹配的问题,基于 Reactor 的onBackpressureBuffer实现背压机制,设置 20 条句子的缓冲队列;TTS 合成速度跟不上时自动降低大模型拉取速率,同时预加载下一片音频,保证播放连续无卡顿 - 断点续生成与会话快照
每生成 500 字文本就持久化一次会话快照到 Redis,包含当前上下文、已生成内容、故事进度;用户中途断连重连后,直接从快照恢复生成状态,无需从头开始 - 场景化 Prompt 动态配置
封装 12 套分龄分主题的 Prompt 模板,支持温度(temperature)、最大生成长度等参数动态调整:儿童模式 temperature=0.7,严格注入合规约束;成人助眠模式 temperature=0.9,风格更舒缓;生成前先经过本地敏感词 + 第三方内容安全接口双校验
专属技术难点与解决方案:
| 难点描述 | Java 端落地方案 | 落地效果 |
|---|---|---|
| 大模型生成与 TTS 合成速率不匹配,播放卡顿、音画不同步 | 基于 Reactor 实现响应式背压机制,设置缓冲队列调节生成速率; 同时采用分片预加载策略,保证播放端始终有 2-3 片已合成音频待播 | 播放卡顿率从 15% 降至 1% 以内 |
| 长故事生成中途断连,用户体验差 | 实现会话快照机制,每生成 500 字持久化一次进度与上下文; 用户重连后自动从断点接续生成,支持暂停、继续操作 | 长故事完成率从 78% 提升至 98% |
4. AI 电子病历系统 🏥
业务背景:辅助医生快速完成病历结构化录入,自动抽取关键医疗实体,映射标准编码,减少医生文书工作量。
专属病历结构化处理流程图
核心技术细节:
- 规则 + 模型双层实体抽取架构
- 规则层:基于 Apache Commons Lang3 正则工具 + 医疗术语词典,抽取检验指标、数值、日期、药品剂量等结构化字段,准确率 99.5%,完全不依赖大模型
- 模型层:调用院内本地化部署的微调医疗大模型,抽取病症、诊断、手术史、既往史等非结构化实体;采用 JSON Schema 强制输出嵌套实体结构,每个实体包含实体名、类别、位置、置信度四个字段
- ICD 编码自动映射
抽取到的诊断实体,先与本地 ICD-10 标准码表做匹配:优先精确匹配,匹配失败则计算实体名称与编码名称的余弦相似度 + 编辑距离加权得分,得分≥0.8 自动映射,低于 0.8 标记人工复核,整体映射准确率 91% - 全链路数据合规与脱敏
自定义 Spring MVC 拦截器实现请求前置脱敏,采用正则匹配 + NLP 实体识别混合方案,识别姓名、身份证号、手机号、住址等 12 类敏感字段,分别采用替换、掩码、泛化三种脱敏策略;向量入库前二次校验,确保无敏感明文进入 Milvus;原始病历存储在加密 HBase 集群,行键按「科室 + 日期 + 病历号」设计,密钥由 KMS 统一管理,全程操作留痕审计 - 输出格式强校验
Java 层基于 Jackson 的 JsonSchemaValidator 做输出格式校验,格式错误自动携带错误信息触发大模型重试,最多 2 次;仍失败则进入人工标注队列,不阻塞主流程
专属技术难点与解决方案:
| 难点描述 | Java 端落地方案 | 落地效果 |
|---|---|---|
| 医疗术语歧义多,大模型抽取准确率不稳定 | Prompt 中注入当前科室的专属术语词典,缩小模型语义范围; 同时加入结果规则校验,比如 “药品必须对应剂量”“诊断必须对应部位”,缺失字段自动触发大模型二次补全 | 核心字段抽取准确率从 85% 提升至 93% |
| 大模型输出格式不固定,无法直接落库 | 强制约束 JSON Schema 输出格式,Java 层做结构校验,格式错误自动重试; 关键字段缺失触发二次补全,极端情况走规则兜底 | 格式解析错误率从 12% 降至 0.2% |
全场景通用核心技术难点与落地方案汇总 🛠️
这是四个项目都会遇到的共性工程问题,也是 Java 工程师做 AI 落地的核心考核点:
| 技术难点 | 影响场景 | Java 端落地方案 | 优化效果 |
|---|---|---|---|
| 大模型调用阻塞主线程,接口吞吐量低 | 全场景同步请求 | WebFlux 响应式调用 + 独立线程池隔离,大模型调用全部提交到自定义 IO 密集型线程池,不占用 Tomcat 主线程 | 接口吞吐量提升 3.5 倍,无请求堆积 |
| Token 调用成本高,预算超支 | 智能客服、讲故事系统 | 三级路由分流 + Caffeine 本地缓存高频问答 + Redis 缓存通用问题答案 + 会话窗口动态裁剪 + 摘要压缩上下文 | 单月 token 成本降低 62% |
| 大模型服务不稳定,超时 / 报错频发 | 全场景 | Sentinel 熔断降级 + 主备模型自动切换,配置超时时间 30s,连续 3 次超时触发熔断,自动切换备用模型; 极端故障降级为关键词检索兜底 | 服务可用性从 94.5% 提升至 99.92% |
| 大模型幻觉,输出虚假无依据信息 | 知识库、病历系统 | Prompt 强制约束「仅基于召回内容回答,未知则说明」 + Java 层事实溯源校验,核心信息与召回原文做关键词匹配度比对,匹配度低于 0.7 标注「信息待核实」并附原文来源 | 幻觉率从 18% 降至 3.8% |
| 向量库数据规模大,检索耗时高 | 知识库、病历系统 | Milvus 分区索引 + HNSW 参数调优 + 高频查询结果缓存; 单分区向量控制在 100w 以内,避免索引膨胀 | 平均检索耗时从 1.2s 降至 380ms |
面试总结 💡
作为 Java 研发工程师做 AI 项目落地,核心竞争力从来不是训练模型、调优算法,而是用成熟的工程化能力,把大模型这个不稳定的黑盒,封装成稳定、低成本、合规、可管控的业务服务。核心要解决的就是性能、成本、安全、稳定性这四类生产环境的真实问题,让 AI 能力真正落地产生业务价值,这也是我做这几个项目最深的体会。
项目实战
🤖 项目一:AI 智能客服 —— 最经典的 RAG 落地
业务痛点
- 客服知识库频繁更新,不可能实时微调模型
- 用户问题口语化,需精准匹配 FAQ
- 多轮对话要记住上下文
🏗️ 系统架构图
┌───────────┐ SSE 流式响应 ┌──────────────────┐
│ 用户端 │ ◄─────────────────── │ Spring Gateway │
│ (H5/APP) │ └────────┬─────────┘
└───────────┘ │
▼
┌──────────────────┐
│ AI-ChatService │ 核心编排
│ (Spring AI) │
└──────┬───────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ Redis │ │ Milvus │ │ LLM API │
│(对话历史) │ │(知识库向量) │ │(GPT-4o) │
└──────────┘ └──────────────┘ └──────────┘🔄 请求处理流程图
用户输入问题
│
▼
┌─────────────┐
│ 敏感词过滤 │──── 命中 ────▶ 返回固定话术,终止
│ (DFA算法) │
└──────┬──────┘
│ 通过
▼
┌─────────────┐
│ Query 改写 │ 口语 → 标准化(LLM 辅助)
└──────┬──────┘
▼
┌─────────────────────┐
│ Redis 热点缓存查询 │─── 命中 ────▶ 直接返回缓存结果
└──────┬──────────────┘
│ 未命中
▼
┌─────────────────────────┐
│ Milvus 向量检索 (Top-20) │ 粗筛
└──────┬──────────────────┘
▼
┌─────────────────────────┐
│ CrossEncoder 精排 (Top-3)│ 精排
└──────┬──────────────────┘
▼
┌──────────────┐ 分数 < 0.7 ┌──────────────┐
│ 相似度判断 │──────────────▶│ 转人工客服 │
└──────┬───────┘ └──────────────┘
│ 分数 ≥ 0.7
▼
┌──────────────────────┐
│ 拼接 Prompt + 历史 │ 取 Redis 最近 5 轮对话
└──────┬───────────────┘
▼
┌──────────────┐
│ LLM 流式生成 │─── SSE ───▶ 前端逐字展示
└──────┬───────┘
▼
┌──────────────┐
│ 输出敏感词过滤 │
└──────┬───────┘
▼
┌──────────────┐
│ 对话历史回存 │ Redis ZSET,TTL 30min
└──────────────┘🔍 项目细节 —— 这些才是真正吃功夫的地方
① 知识库切分不是简单的按字符切
我们用了 DocumentTransformer 链:先用 MarkdownTextSplitter 按自然段落+标题层级切分,保证每个 chunk 是一个完整的 FAQ 问答对;然后再用 TokenTextSplitter 兜底,确保单 chunk 不超过模型上限(512 tokens)。切分后每个 chunk 的 metadata 里存上 faqId、category、keywords,方便后续做精准召回。
② 检索不是一步到位,而是“粗筛+精排”两阶段
- 粗筛:问题向量化后,去 Milvus 做 ANN 检索,取 top-20 候选
- 精排:用
CrossEncoder模型对候选做 rerank,输出 top-3 真正相关的文档。这个方案比单纯的向量检索准确率提升了约 15%
③ 多轮对话的状态管理
不是无脑把所有历史塞进 Prompt,那样 token 成本会爆炸。我们只保留最近 5 轮对话,并且用 Redis 的 ZSET 按时间戳排序存储,每次取最新 N 条;同时做一个话题切换检测:当用户问题与上一轮的语义相似度低于 0.3 时,自动清空历史,开启新话题。
④ 流式响应的容错处理
SSE 连接中断时,前端通过 Last-Event-ID header 告诉后端从哪个位置继续推送,后端从 Redis 里取缓存的 token 序列,做到断点续传。这个细节在用户网络不稳定的场景下特别管用。
关键代码思维
// 1. 用户问题先向量化,去 Milvus 检索 top-K 相似文档
List<Document> docs = vectorStore.similaritySearch(
SearchRequest.query(question).withTopK(3)
);
// 2. 拼接 Prompt 模板,带上历史对话
PromptTemplate template = new PromptTemplate("""
你是客服助手,根据以下知识回答问题:
{knowledge}
历史对话:{history}
用户问题:{question}
""");
// 3. 调用 LLM 流式输出,通过 SSE 推给前端
Flux<String> flux = chatClient.stream(template.create(params));🚧 技术难点 & 解决方案
| 难点 | 现象 | 解决方案 |
|---|---|---|
| 口语化 vs 标准化 | 用户说“这个咋弄啊”,知识库里写的是“操作步骤” | ① 用 LLM 做 query 改写,把口语标准化后再向量检索 ② 建立常见口语→标准词的映射词典,做第一层预处理 |
| 大模型幻觉 | 检索不到相关文档时,模型依然瞎编答案 | 相似度阈值兜底:精排后最高分 < 0.7 时,直接回复“请您稍等,我帮您转接人工客服”,绝不让模型自由发挥 |
| 高并发下向量检索慢 | 促销期间 QPS 暴涨,Milvus 查询延迟从 30ms 飙到 200ms+ | ① Milvus 索引从 IVF_FLAT 换为 HNSW,查询更快 ② Redis 热点缓存:top-100 高频问题的向量检索结果缓存在 Redis 里,命中率能到 60% |
| 敏感词漏网 | 用户输入或模型输出里出现违规内容 | 在 Spring AI 的 Advisor 链里加 SensitiveWordAdvisor,基于 DFA 算法 做输入输出的双向过滤,命中敏感词直接阻断 🛡️ |
📚 项目二:AI 内部 Wiki 知识库 —— 企业级 RAG 的深水区
和客服的本质区别
智能客服只查片段,Wiki 系统要支持长文档摘要、章节跳转、引用溯源,且对权限有强要求。
🏗️ 系统架构图
┌──────────────────────────────────────────────────────────┐
│ 前端 (Wiki 搜索页) │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Spring Gateway + 鉴权 │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ WikiSearchService │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 权限过滤 │ │ 混合检索 │ │ 引用溯源组装 │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└──────┬───────────────┬───────────────┬──────────────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Redis │ │Elasticsearch │ │ LLM API │
│(热点缓存) │ │(向量+倒排) │ │ (GPT-4o) │
└──────────┘ └──────┬───────┘ └──────────────┘
│
▼
┌────────────────┐
│ OSS (图片存储) │
└────────────────┘🔄 文档入库 & 检索流程图
文档增量入库流程:
MySQL (Wiki 编辑)
│
▼
┌─────────────┐
│ Canal 监听 │ Binlog 实时采集
└──────┬──────┘
│
▼
┌─────────────┐
│ Kafka 消息 │ 10s 窗口去重(同 docId 多次更新只保留最后一次)
└──────┬──────┘
│
▼
┌──────────────────────┐
│ 文档解析服务 │
│ ├─ 文本: Markdown切分 │
│ ├─ 表格: 结构化 JSON │
│ └─ 图片: OCR → 摘要 │
└──────┬───────────────┘
│
▼
┌──────────────┐
│ Embedding │ 调用 Embedding 模型向量化
└──────┬───────┘
│
▼
┌──────────────────┐
│ 写入 ES/Milvus │ metadata 带 deptId、roleLevel 等权限字段
└──────────────────┘用户检索流程:
用户输入查询
│
▼
┌──────────────┐
│ 获取用户权限 │ deptId, roleLevel
└──────┬───────┘
▼
┌──────────────────────────────┐
│ ES 权限前置过滤 │ filter context,不参与评分
│ (terms: deptId in [用户部门]) │
└──────┬───────────────────────┘
▼
┌──────────────────────┐
│ 混合检索 │
│ ├─ 向量检索 (70%) │
│ └─ BM25 关键词 (30%) │
└──────┬───────────────┘
▼
┌──────────────┐
│ Top-K 结果 │ 父文档上下文补充
└──────┬───────┘
▼
┌──────────────────────┐
│ Prompt 拼接 + 引用 │ 附 docTitle/sectionPath/链接
└──────┬───────────────┘
▼
┌──────────────┐
│ LLM 流式输出 │─── SSE ───▶ 前端(含可点击引用)
└──────────────┘🔍 项目细节
① 文档解析管线复杂得多
Wiki 里不只有纯文本,还有表格、代码块、图片。我们做了分层解析:
- 文本段落:
MarkdownHeaderTextSplitter按 # → ## → ### 层级切分,保留章节层级关系,检索时可以向上追溯章节上下文 - 表格:用
TableTransformer将表格转成结构化的 JSON,单独入 ES 的wiki_table索引,查询时走模板填充 - 图片:用 OCR 提取文字描述,生成图片摘要,跟所属文档一起做 embedding,这样搜索“架构图”也能命中
② 混合检索 + 权重融合
向量检索在长文档场景下容易漏掉精确的关键词匹配(比如搜一个具体的错误码)。我们采用 ES 同时存向量和倒排索引:
- 向量检索分:
cosineSimilarity(queryVector, docVector) - 关键词检索分:BM25 算法
- 最终分数:
score = 0.7 * 向量分 + 0.3 * BM25分
③ 引用溯源是“刚需”
用户不光要答案,还要知道“这话是从哪篇文档来的”。每个检索出来的 chunk 的 metadata 里存了 docTitle、sectionPath、pageNumber,最终回答里附上可点击的引用链接,点进去直接跳转到文档原文的对应段落。这个用户体验的提升非常明显 🔗
🚧 技术难点 & 解决方案
| 难点 | 现象 | 解决方案 |
|---|---|---|
| 长文档检索精度低 | 一段 2000 字的章节被切成 4 个 chunk,检索时只命中其中 1 个,上下文丢失 | 父文档索引:chunk 做向量化时,metadata 里存储所属父文档的完整内容。检索命中 chunk 后,把整个父文档的一段上下文一起喂给 LLM,而不是孤立的 chunk |
| 权限过滤性能差 | 每次检索都要过滤 deptId in [用户部门],文档多了延迟明显 | 在 ES 里为 metadata 字段建索引,用 filter context 做前置过滤,不参与评分计算,性能几乎无损。多部门用户用 terms 查询,比逐个 should 快很多 |
| 增量更新的“抖动” | Wiki 频繁编辑,同一篇文档短时间被更新多次,导致重复 embedding 浪费算力 | 用 Canal 监听 MySQL Binlog → Kafka,消费端做 10 秒窗口去重:同一 docId 在窗口内的多次更新只触发最后一次 embedding,减少无效计算 |
| 表格数据检索难 | 用户问“上个季度销售额”,数据在表格里,向量检索很难匹配 | 表格单独结构化存储,用 Text2SQL 的思路:LLM 把用户问题转成 SQL 查 ES,再把查到的数字拼进回答里 |
🌙 项目三:AI 睡眠讲故事系统 —— 生成式 AI 的创意玩法
场景特点
非严肃问答,需要可控的随机性和多模态输出(文本 + 语音)
🏗️ 系统架构图
┌──────────────────────────────────────────────────────────┐
│ 移动端 APP │
└──────────────┬───────────────────────┬───────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 用户画像服务 │ │ 故事播放服务 │
│ (MySQL + Redis) │ │ (OSS 音频流) │
└──────────┬───────────┘ └──────────────────────┘
│
▼
┌──────────────────────┐
│ 故事生成编排服务 │
│ (Spring AI) │
└──────┬───────────────┘
│
▼
┌──────────────────────┐ ┌──────────────────────┐
│ LLM 文本生成 │────▶│ RocketMQ │
│ (GPT-4o / 私有模型) │ │ (故事生成完毕事件) │
└──────────────────────┘ └──────────┬───────────┘
│
▼
┌──────────────────────┐
│ TTS 语音合成服务 │
│ (Azure Speech API) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ 推送服务 (FCM) │
│ + XXL-JOB 定时调度 │
└──────────────────────┘🔄 业务流程详图
┌─────────────────────────────────────────────────────┐
│ 用户设置偏好 │
│ 风格/主角/声音/时长 → 画像服务 (MySQL) │
└────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 用户预约播放时间 │
│ XXL-JOB 注册定时任务,key = userId+scheduleTime │
└────────────────────┬────────────────────────────────┘
│ 定时触发
▼
┌─────────────────────────────────────────────────────┐
│ Redis 分布式锁 (避免重复执行) │
│ SET NX EX 60s │
└──────┬──────────────────────────────────────────────┘
│ 获锁成功
▼
┌─────────────────────────────────────────────────────┐
│ 检查音频缓存 (OSS) │
│ 命中?────────────┐ │
└──────┬────────────────────┼─────────────────────────┘
│ 未命中 │ 命中
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Prompt 组装 │ │ 直接返回音频 │
│ + LLM 文本生成│ │ (秒级响应) │
└──────┬───────┘ └──────────────┘
│ 新故事生成
▼
┌──────────────┐
│ 内容安全审核 │ 关键词正则 + 阿里云绿网
└──────┬───────┘
│ 通过
▼
┌──────────────────────────┐
│ 发送 RocketMQ 消息 │
│ {storyId, textContent} │
└──────┬───────────────────┘
│
▼
┌──────────────┐
│ TTS 语音合成 │ Azure Speech → OSS 存储
└──────┬───────┘
│
▼
┌──────────────┐
│ FCM 推送通知 │ “今晚的故事已经准备好啦~”
└──────────────┘🔍 项目细节
① 用户画像构建
不是简单的“年龄+性别”,而是从历史交互中抽取偏好标签:
- 故事风格:
[温馨, 奇幻, 冒险, 科普] - 故事主角:
[小兔子, 小熊, 公主, 恐龙] - 声音偏好:
[温柔女声, 低沉男声] - 时长偏好:
[5分钟, 10分钟, 15分钟]
这些标签存在 MySQL 的用户画像表里,每次生成故事时作为 Prompt 的一部分注入。
② Prompt 设计 —— 可控的创意
PromptTemplate template = new PromptTemplate("""
你是一个儿童故事作家。请根据以下用户画像创作一个睡前故事:
- 年龄:{age}岁,性别:{gender}
- 喜欢的风格:{style}
- 喜欢的主角:{character}
- 故事时长要求:{duration}分钟(约{wordCount}字)
要求:
1. 语言温暖,适合睡前听,避免恐怖或刺激情节
2. 结尾要有“晚安”元素,帮助孩子入睡
3. 故事要包含一个正面教育意义
temperature 设 0.9,保证每次生成的故事不重复
""");③ 多模态生成和推送的全链路
文本生成和语音合成是两个独立耗时的步骤,我们用 RocketMQ 解耦:
- 订单服务发消息
{storyId, textContent} - TTS 服务消费消息,调用 Azure Speech API 合成语音,结果存 OSS
- 推送服务等到语音生成完毕,通过 FCM 给用户发通知
🚧 技术难点 & 解决方案
| 难点 | 现象 | 解决方案 |
|---|---|---|
| 创意 vs 可控性的平衡 | temperature 太高故事逻辑混乱,太低又千篇一律 | 用 temperature=0.9 保证创意,但同时在 Prompt 里加 结构化约束(“必须包含:开头-发展-高潮-结尾四段”),用格式限制抑制跑偏 |
| 语音合成延迟 | 10 分钟长的故事,TTS 耗时 30 秒+,用户等不及 | 流水线预生成:利用用户画像提前生成一批候选故事(深夜低峰期跑),存音频缓存。用户请求时 80% 命中缓存,秒级响应 |
| 分布式任务冲突 | 同一个用户预约了多个故事时间,调度任务可能并发执行 | xxl-job + Redis 分布式锁:以 userId + scheduleTime 为 key 加锁,保证同一时间点只执行一次,避免重复生成和推送 |
| 内容安全 | 生成的童话里可能出现不适合儿童的内容 | 输出层做二次审核:先用关键词正则过滤,再过一道内容安全 API(如阿里云绿网),双保险 |
🏥 项目四:AI 电子病历系统 —— 最考验严谨性的垂直领域
这块不能光靠通用大模型,必须上组合拳
🏗️ 系统架构图
┌──────────────────────────────────────────────────────────┐
│ 医生工作站 │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 病历预处理网关 │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 敏感词脱敏 │ │ 格式校验 │ │ 审计日志记录 │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ AI 病历结构化服务 (Spring AI) │
└──────┬───────────────┬───────────────┬───────────────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ LLM 提取 │ │ Drools 规则 │ │ 私有化模型 │
│ (Few-shot)│ │ 引擎 (200+条) │ │ Qwen2-7B 医疗 │
└──────┬───┘ └──────┬───────┘ └──────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────┐
│ 结构化结果 + 风险提示 + 解释链 │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ MySQL 病历库 (脱敏存储) │
└──────────────────────────────────────────────────────────┘🔄 病历处理流程图
原始病历文本 (医生输入)
│
▼
┌────────────────┐
│ 第一次脱敏 │ @SensitiveInfo 注解:姓名→[NAME_001]
│ (传 LLM 前) │ 身份证号保留前6后4,中间脱敏
└──────┬─────────┘
│
▼
┌──────────────────────┐
│ LLM 结构化提取 │ Few-shot Prompt
│ 输出 JSON │ 实体抽取 + 字段映射
└──────┬───────────────┘
│
▼
┌──────────────────────┐
│ 实体标准化 │ UMLS / ICD-10 映射表
│ “左上肺叶”→“左肺上叶” │ 不合法实体回退重试
└──────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ Drools 规则引擎校验 (200+条) │
│ ├─ 胸痛+年龄>40+高血压→心梗风险 │
│ ├─ 年龄<0 OR >150→数据异常 │
│ └─ 心梗诊断+无抗凝治疗→方案不全 │
└──────┬───────────────────────┘
│
▼
┌──────────────────────┐
│ 第二次脱敏 │ 向量化入库前去敏
│ 确保向量库无明文 │
└──────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 生成解释链 + 审计日志 │
│ 触发规则 → 数据来源 → 原文片段 │
└──────┬───────────────────────┘
│
▼
┌──────────────────────────────┐
│ 写入 MySQL + 向量库 (可选) │
│ 状态: 待医生确认 │
└──────────────────────────────┘
│
▼
┌──────────────────────────────┐
│ 医生复核 + 电子签名 │
│ “AI 提取结果可追溯”按钮 │
└──────────────────────────────┘🔍 项目细节
① 结构化提取的 Prompt 工程
不是简单一句“帮我提取”,而是精心设计的 Few-shot 模板:
你是一个医疗结构化提取工具。请从以下病历文本中提取关键信息,输出 JSON:
示例输入:
“患者男,45岁,主诉胸痛3天,有高血压史,否认糖尿病史”
示例输出:
{"性别":"男","年龄":45,"主诉":"胸痛","持续时间":"3天","既往史":["高血压"],"否认史":["糖尿病"]}
现在请提取:
“{病历文本}”② 规则引擎的校验链
提取结果不能直接入库,必须过 Drools 的规则校验。我们定义了约 200+ 条医学规则:
IF 主诉=="胸痛" AND 年龄>40 AND 既往史 contains "高血压" THEN 触发心梗风险提示 🚨IF 年龄<0 OR 年龄>150 THEN 标记为数据异常IF 诊断=="急性心肌梗死" AND 治疗 NOT contains "抗凝" THEN 提示治疗方案不完整
③ 脱敏的粒度控制
- 患者姓名 → 用
@SensitiveInfo(type="NAME")注解标记,序列化时自动替换为[NAME_001] - 身份证号 → 保留前 6 位和后 4 位,中间用
*替换 - 脱敏发生在两个环节:① 传给 LLM 之前做第一次脱敏 ② 向量化入库前做第二次脱敏,确保向量库里也不会有明文
④ 私有化模型部署
如果允许,用 Qwen2-7B 医疗版 做私有化部署,通过 Ollama 或 vLLM 暴露兼容 OpenAI 的 API,Java 侧零改动切换。数据不出院,合规无忧。
🚧 技术难点 & 解决方案
| 难点 | 现象 | 解决方案 |
|---|---|---|
| 医学实体识别不准 | LLM 把“左肺上叶”识别成了“左上肺叶”,字段名不规范 | 输出后做实体标准化:用 UMLS/ICD-10 的映射表把 LLM 输出的变体统一到标准术语,不合法的直接回退 |
| 幻觉导致误诊风险 | 模型有时会“编造”一个不存在的病史 | 人机协同:AI 只做“辅助提取+风险提示”,最终诊断必须由医生确认并签名。系统提供“AI 提取结果可追溯”按钮,能看到每一条数据的来源原文 |
| 隐私合规(HIPAA / 个保法) | 明文病历传给外部 LLM 是红线 | ① 私有化部署医疗模型,数据不出院 ② 必须调用外部模型时,先脱敏再传输,收到回复后再逆向还原(只还原非敏感实体) |
| 可解释性 | 医生质疑“为什么 AI 判断这个患者是高危”,系统必须能解释 | 每条 AI 输出附带 解释链:触发规则 → 数据来源 → 原文片段,同时记录到审计日志,支持事后追溯 |
📊 四个项目全景对比
| 维度 | 智能客服 🤖 | Wiki 知识库 📚 | 睡眠故事 🌙 | 电子病历 🏥 |
|---|---|---|---|---|
| RAG 依赖 | ✅ 强依赖 | ✅ 混合检索 | ❌ | ✅ 结构化提取 |
| Agent 调用 | ❌ | ❌ | ✅ 定时调度 | ✅ 规则引擎 |
| 流式输出 | ✅ SSE | ✅ SSE | ❌ | ❌ |
| 多模态 | ❌ | ✅ 表格+图片 | ✅ TTS | ❌ |
| 权限安全 | 低 | 高(部门级) | 低 | 极高(脱敏+合规) |
| 最难的点 | 口语理解+幻觉控制 | 长文档精度+增量同步 | 可控创意+预生成 | 实体标准化+隐私合规 |
💡 总结一句话
Java 在 AI 时代不是旁观者,而是把模型能力工程化、产品化的核心粘合剂。Spring 做编排、Kafka 做解耦、Redis 做缓存、Drools 做规则、ES/Milvus 做检索 —— 每一层都是企业级 AI 系统不可或缺的拼图 🧩。
