1. 为什么 Java 工程师做 AI,真正的机会在「落地」而非「训练」
1.1 训练这件事,跟大多数 Java 工程师其实没太大关系
先把一个现实摆在桌面上:大模型的预训练和微调,本质上是一个算力密集、数据密集、资金密集的活儿。一次像样的全量微调动辄需要几十张甚至上百张高端显卡跑上几天,数据集动辄几十 GB 到 TB 级别,这背后是实打实的硬件成本和工程团队规模。绝大多数公司的 Java 工程师,日常接触的是业务系统、中间件、数据库、接口联调,跟这套训练流水线基本是两条平行线。
我身边不少同行一开始听到「AI 转型」四个字,第一反应是去啃 Transformer 论文、去学 PyTorch、去研究 LoRA 微调。啃了两周发现,学是学了点皮毛,但公司里根本没有场景让你去训模型,学完就荒废了。这不是能力问题,是定位问题——你把自己放到了一个供给过剩、门槛又极高的赛道上。
真正稀缺的是什么?是把已经训练好的模型,接进真实业务系统里跑起来、跑稳、跑出效果的人。这件事需要的能力,恰好是 Java 工程师最擅长的:工程化、稳定性、并发处理、系统集成、权限控制、可观测性。模型训练是「造发动机」,落地是「把发动机装进车里,还要让车能上路、能加油、能刹车、能过审」。后者才是绝大多数企业真正缺人的地方。
1.2 「落地」到底包含哪些具体活儿
我把「落地」拆成几个层次,你可以对照看看自己现在处在哪一层:
- 第一层:模型接入。把大模型 API 或者本地部署的模型服务,通过 HTTP、SDK 等方式接进现有 Java 系统。这一层看着简单,但涉及超时控制、重试策略、流式响应、错误码映射,坑不少。
- 第二层:RAG 检索增强。让模型能回答「公司内部知识」相关的问题,需要做文档切分、向量化、向量库检索、上下文拼装、Prompt 组装。这是目前企业落地最主流的形态。
- 第三层:Agent 与工具调用。让模型不只是聊天,还能调用你的业务接口、查数据库、执行操作。这一层开始涉及权限、审计、幂等、事务。
- 第四层:工程化与治理。多模型路由、成本控制、限流熔断、对话历史管理、效果评估、灰度发布。这一层是纯 Java 工程师的主场。
你会发现,越往上走,越不依赖「懂模型原理」,越依赖「懂系统」。这就是 Java 工程师的核心机会所在。
1.3 一个真实的判断:RAG 是当前性价比最高的切入点
在所有这些落地形态里,RAG(检索增强生成)是当前投入产出比最高的。原因很直接:它不需要训练,不需要标注大量数据,不需要 GPU 集群,只需要你有一套文档、一个向量库、一个能调用的模型,就能在几天内做出一个可演示、可交付的东西。
我做过一个内部知识问答的原型,从零到能回答「报销流程怎么走」「某个接口的参数含义」这类问题,前后不到一周。核心代码量其实不大,难的是把文档切分策略、检索召回率、Prompt 模板这几块调顺。这个过程中,Java 工程师的工程直觉——比如怎么设计缓存、怎么处理并发、怎么做降级——反而比算法知识更管用。
所以我的建议很明确:别一上来就想着训模型,先把 RAG 这条链路用 Spring Boot 跑通一遍。跑通之后你对整个 AI 落地的体感会完全不一样。
2. RAG 落地的核心技术点拆解与选型考量
2.1 RAG 的本质:给模型配一个「开卷考试」的参考书
用生活化的类比解释 RAG:大模型本身像一个博学但记性有点模糊的人,你问它公司内部的事,它要么不知道,要么一本正经地胡说。RAG 的做法是,在它回答之前,先帮它从你的资料库里翻出几页最相关的材料,塞到它手里,然后说「你照着这几页回答」。
这个「翻资料」的过程就是检索(Retrieval),「照着回答」的过程就是生成(Generation)。整个链路拆开看是这么几步:
- 文档入库:把 PDF、Word、Markdown、网页等原始资料,切成一段段合适大小的文本块(chunk)。
- 向量化:用 Embedding 模型把每个文本块转成一串数字向量,存进向量数据库。
- 查询检索:用户提问时,把问题也转成向量,去向量库里找最相似的几个文本块。
- 上下文拼装:把检索到的文本块和用户问题拼成一个 Prompt。
- 生成回答:把 Prompt 发给大模型,拿到回答返回给用户。
这五步里,Java 工程师要写代码的主要是第 1、3、4、5 步的编排逻辑,第 2 步的向量化通常调 API 或本地模型,向量库有现成的。
2.2 技术选型:为什么是 Spring Boot + WebFlux
选型这件事,我的原则是「用团队最熟的技术栈,减少认知负担」。Java 团队做 AI 落地,Spring Boot 几乎是默认选项,但这里有个关键点容易被忽略:流式响应。
大模型生成回答是逐字吐出来的,如果后端用传统的阻塞式接口,用户要等整段回答生成完才能看到,体验很差。这时候WebFlux就派上用场了。WebFlux 基于 Reactor 的响应式模型,天然适合处理流式数据,可以把模型吐出来的 token 实时推给前端。
我对比过两种方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Spring MVC 阻塞式 | 写法简单,团队熟悉 | 流式体验差,线程占用高 | 内部工具、非实时场景 |
| Spring Boot + WebFlux | 流式体验好,资源利用率高 | 学习曲线陡,调试稍麻烦 | 面向用户的对话产品 |
如果你的场景是面向 C 端或需要实时打字机效果,WebFlux 基本是必选项。如果只是内部批处理或者后台任务,MVC 也够用,别为了技术而技术。
2.3 向量库怎么选:从零基础到生产级
向量库的选择直接决定了你的检索效果和运维成本。我按使用门槛从低到高列一下:
- 内存向量库(如 SimpleVectorStore):适合原型验证,数据量小的时候够用,重启就丢,不能上生产。
- 本地持久化向量库(如 Chroma、Milvus Lite):适合单机部署、中小规模知识库,运维简单。
- 分布式向量库(如 Milvus、Qdrant 集群版):适合大规模、高并发场景,但运维复杂度上来了。
我的经验是:先用最简单的方案把链路跑通,等真的遇到性能瓶颈再换。很多人一上来就搭 Milvus 集群,结果数据量才几千条,纯属浪费。我自己的原型用的是本地持久化的方案,几千个 chunk 检索延迟在几十毫秒,完全够用。
2.4 框架选择:LangChain4j 还是自己撸
Java 生态里做 RAG,LangChain4j是目前比较成熟的选择。它把文档加载、切分、向量化、检索、Prompt 组装这些环节都封装好了,能省不少事。但它也不是银弹,封装太厚的地方你不好控制细节,比如切分策略、检索后的重排序。
我的建议是:第一版用 LangChain4j 快速跑通,理解每个环节在干什么;等要优化效果的时候,把关键环节换成自己写的代码。这样既享受了框架的便利,又保留了优化的空间。纯自己撸也不是不行,但文档加载器、各种格式解析这些轮子,没必要重复造。
3. 用 Spring Boot 搭一套 RAG 服务的完整实操
3.1 环境准备与依赖引入
先说环境。JDK 17 起步,Spring Boot 3.x,这是当前的主流组合。如果你还在用 Spring Boot 2.3.x 或 2.6.x,做 AI 落地问题不大,但 WebFlux 的一些新特性用不上,建议新项目直接上 3.x。
Maven 依赖核心是这几块:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.35.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> <version>0.35.0</version> </dependency>向量库和 Embedding 模型的依赖按你选的方案加。如果本地跑模型,还需要引入对应的推理依赖;如果调云端 API,加对应的 HTTP 客户端即可。
注意:LangChain4j 版本迭代很快,不同版本 API 有差异,引入前先看官方文档对应版本的用法,别直接抄网上的老代码。
3.2 文档入库:切分策略是效果的第一道关
文档切分看着简单,其实是 RAG 效果的分水岭。切太大,检索出来的块包含太多无关信息,模型容易被干扰;切太小,语义不完整,检索到的片段答非所问。
我踩过的坑:一开始按固定 500 字符切,结果把一段完整的操作步骤从中间截断,模型拿到半截话,回答得驴唇不对马嘴。后来改成按语义边界切分,优先在段落、标题、句子结束符处断开,效果明显好转。
一个可参考的切分参数:
- chunk size:500 到 800 字符,中文场景可以适当小一点。
- chunk overlap:50 到 100 字符,保证相邻块之间有重叠,避免边界信息丢失。
- 分隔符优先级:先按标题(
#、##)切,再按段落(\n\n)切,最后按句子(。、!、?)切。
代码上,LangChain4j 提供了DocumentSplitter,可以配置这些参数。但我的建议是,针对你的文档类型写一个自定义切分器,因为通用切分器很难照顾到所有格式。比如技术文档里的代码块,就不应该被从中间切开。
3.3 向量化与存储:Embedding 模型的选择
Embedding 模型负责把文本转成向量。选择上分两类:
- 云端 API:调用方便,效果好,但按量计费,数据要出你的服务器。
- 本地模型:数据不出内网,无调用成本,但需要一定的机器资源,效果参差。
如果公司对数据安全要求高,本地模型是唯一选择。我试过几个开源的中文 Embedding 模型,在通用语义检索上表现都不错,具体选哪个要看你的领域。技术文档、法律文书、医疗记录,不同领域的最优模型可能不一样,建议拿你自己的数据做个小规模评测再定。
向量入库这块,注意批量写入而不是一条条写,几千条数据一条条写会慢到怀疑人生。批量大小控制在 100 到 500 之间,太大容易超时。
3.4 检索与重排序:召回率不够怎么办
检索环节最常见的抱怨是「明明文档里有,就是检索不出来」。这通常是几个原因:
- 切分不合理:关键信息被切散了。
- Embedding 模型不匹配:模型没理解你的领域术语。
- 纯向量检索的局限:向量检索擅长语义相似,但对精确的关键词匹配不敏感。
针对第三点,混合检索是个有效的解法:向量检索 + 关键词检索(如 BM25),两路结果合并后再重排序。这样既能抓住语义相近的内容,又能命中精确的关键词。
重排序(Rerank)是另一个提升点。初次检索召回 Top 20,然后用一个重排序模型对这 20 条精排,取 Top 3 到 5 条给大模型。这一步能显著提升上下文的相关性,代价是多一次模型调用。我的实测是,加了重排序之后,回答准确率有明显提升,值得加。
3.5 流式响应:WebFlux 怎么把 token 推给前端
这是 Java 工程师做 AI 落地最有技术含量的部分之一。大模型返回的是 SSE(Server-Sent Events)流,后端要把它转成前端能消费的流。
用 WebFlux 的写法大致是这样:
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> chatStream(@RequestParam String question) { return ragService.streamAnswer(question) .map(token -> "data: " + token + "\n\n"); }关键点在于Flux这个响应式类型,它代表一个可以持续发射数据的流。模型每吐一个 token,就通过这个流推给前端。前端用 EventSource 接收,就能实现打字机效果。
注意:流式接口的超时设置要特别小心。默认的超时可能几十秒就断了,而大模型生成一段长回答可能要一两分钟。要把超时调大,同时做好客户端断开连接时的资源释放,否则会泄漏连接。
3.6 对话历史管理:多轮对话怎么存
单轮问答简单,多轮对话就涉及历史管理。核心问题是:历史消息存哪、存多少、怎么拼进 Prompt。
- 存储:可以用 Redis,key 是会话 ID,value 是消息列表。设置合理的过期时间,避免无限增长。
- 长度控制:不能把所有历史都塞进 Prompt,会超上下文窗口。常见做法是保留最近 N 轮,或者对历史做摘要压缩。
- 拼装顺序:一般是「系统提示词 + 历史消息 + 检索到的上下文 + 当前问题」。
我踩过的坑:历史消息没做长度控制,聊了十几轮之后请求直接超限报错。后来加了滑动窗口,只保留最近 10 轮,问题解决。
4. 落地过程中那些文档里不会写的坑
4.1 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决思路 |
|---|---|---|---|
| 检索不到相关内容 | 切分不合理 / Embedding 不匹配 | 打印检索结果看召回内容 | 调整切分策略,换 Embedding 模型 |
| 回答胡编乱造 | 上下文没拼对 / Prompt 没约束 | 检查最终发给模型的 Prompt | 加强 Prompt 约束,要求「仅根据上下文回答」 |
| 流式响应卡顿 | 线程阻塞 / 超时设置不当 | 看日志和线程状态 | 检查是否有阻塞调用,调大超时 |
| 并发一高就崩 | 模型调用没做限流 | 压测观察 | 加限流、熔断、降级 |
| 成本失控 | 没有 token 统计和预算控制 | 统计每次调用 token 数 | 加预算告警,做模型路由 |
| 回答太慢 | 检索链路太长 / 模型太大 | 分段计时 | 优化检索,小模型处理简单问题 |
4.2 独家避坑经验
第一,Prompt 里一定要加「不知道就说不知道」的约束。不加的话,模型在检索不到相关内容时,会用自己的知识硬编,编出来的东西看着像模像样,实际是错的。这在企业场景里是致命的。
第二,检索结果要打印出来看。很多人调 RAG 只盯着最终回答,其实问题往往出在检索环节。把每次检索到的 chunk 打印出来,你一眼就能看出是切分问题还是模型问题。
第三,别忽视 token 成本。RAG 每次请求都要把检索到的上下文塞进 Prompt,token 消耗比普通对话大得多。如果不做统计和控制,月底账单会让你怀疑人生。建议在网关层做 token 统计,设置每日预算。
第四,模型调用一定要有超时和重试。模型服务不是 100% 可用的,网络抖动、服务过载都会导致失败。超时设置要合理,重试要有退避策略,别傻乎乎地立即重试把对方打挂。
第五,灰度发布很重要。RAG 的效果不是非黑即白的,新版本可能在某些问题上更好,在另一些上更差。上线前一定要做 A/B 对比,别直接全量。
4.3 关于「RAG 瓶颈」的一些思考
网上经常讨论 RAG 的瓶颈,我自己的体会是,瓶颈主要在三处:
- 检索质量:这是最核心的瓶颈。检索不准,后面全白搭。混合检索 + 重排序是目前比较有效的缓解手段。
- 上下文窗口:检索到的内容多了,Prompt 就长,成本和延迟都上去了。怎么在有限窗口里塞进最有用的信息,是个持续优化的活儿。
- 多跳推理:有些问题需要综合多个文档的信息才能回答,单次检索搞不定。这时候需要 Agent 式的多轮检索,复杂度上来了。
这些瓶颈不是靠换个模型就能解决的,更多是工程问题。而工程问题,恰好是 Java 工程师的舒适区。
5. 从 RAG 到 Agent:Java 工程师的进阶路线
5.1 Agent 是什么,为什么它更需要工程能力
Agent 简单说就是「让模型能自己决定调用什么工具、执行什么操作」。比如用户问「帮我查一下上个月的订单」,Agent 会自己判断需要调用订单查询接口,拿到数据后再组织语言回答。
这件事对工程能力的要求比 RAG 高一个量级。因为模型要调用你的真实业务接口,就必须处理:
- 权限:模型能调哪些接口,不能调哪些,要有行级权限控制。
- 幂等:模型可能重复调用同一个接口,要保证重复调用不出问题。
- 审计:模型调了什么接口、传了什么参数、返回了什么,都要留痕。
- 事务:涉及写操作的,要考虑事务边界和回滚。
这些全是 Java 工程师天天打交道的东西。算法工程师可能能把 Agent 的推理逻辑写出来,但要把权限、审计、事务这套做扎实,还是得靠后端工程师。
5.2 多模型路由:不是所有问题都值得用大模型
一个实用的优化:简单问题用小模型,复杂问题用大模型。比如「今天天气怎么样」这种,小模型完全够用,没必要上最贵的。路由策略可以基于问题长度、关键词、或者一个小分类模型来判断。
这个路由层用 Java 写非常自然,就是一个策略模式。成本能降下来不少,响应速度也更快。
5.3 可观测性:AI 服务更需要监控
传统服务的监控看 QPS、延迟、错误率就够了。AI 服务还要看:
- token 消耗:按模型、按接口、按用户统计。
- 检索命中率:检索到的内容有多少被模型实际用上了。
- 回答质量:这个比较难量化,可以用人工抽检 + 用户反馈。
- Prompt 版本:不同版本的 Prompt 效果对比。
Spring Boot 生态里的 Micrometer、Actuator 这些,稍微改造一下就能用上。别等出了问题才想起来加监控。
5.4 给 Java 工程师的几点实在建议
最后说几句掏心窝的话。
别焦虑,你的工程能力就是最大的护城河。AI 落地这件事,算法只是其中一环,大量的工作是系统集成、稳定性保障、性能优化,这些恰恰是 Java 工程师的强项。我见过太多算法很强但工程一塌糊涂的 AI 项目,最后卡在并发、卡在部署、卡在权限上。
动手比看论文重要。与其花两周啃 Transformer,不如花两天用 Spring Boot 搭一个能跑的 RAG demo。跑通之后你对整个链路的理解会深刻得多,遇到问题也知道从哪查。
保持对业务的理解。AI 落地最终是要解决业务问题的,脱离业务谈技术都是耍流氓。多跟业务方聊,搞清楚他们真正需要什么,比闷头调模型参数有价值得多。
别排斥新工具,但也别盲目追新。LangChain4j、Spring AI 这些框架更新很快,保持关注,但选型时以稳定和团队熟悉度为先。生产环境不是试验田。
我自己从纯后端转到做 AI 落地,最大的感受是:这不是转行,是延伸。你原来会的那些东西一样没浪费,只是多了一个新的应用场景。把 RAG 这条链路跑通,把 Agent 的权限和审计做扎实,你就已经比大多数只会调 API 的人走得远了。