Java开发者AI应用入门:Spring AI与LangChain4j实战RAG知识库
1. Java 开发者切入 AI 的真实路径长什么样
这两年身边不少写 Java 的朋友都在问同一个问题:后端业务写了五六年,CRUD 闭着眼睛都能敲,现在想往 AI 方向靠,到底从哪下手?网上教程一搜,清一色 Python、PyTorch、Jupyter Notebook,看完更焦虑了——难道要把 Java 全扔了重学一遍?
先给结论:不用。Java 开发者的 AI 入门路径,和算法工程师那条路完全是两码事。算法工程师关心的是模型结构、训练策略、损失函数怎么调;而 Java 开发者真正该切入的是AI 应用层——把大模型当成一个能力组件,嵌进你熟悉的 Spring Boot 体系里,做 RAG 知识库、做 Agent 工具调用、做企业级 AI 中台。这条路上,你的 Spring 生态经验、工程化能力、并发和事务处理功底,全是加分项,不是包袱。
我自己是从一个内部知识问答需求开始动手的,当时团队有几千份产品文档、历史工单、FAQ,客服每天重复回答同样的问题。用传统关键词检索效果很差,用户问"订单超时了钱扣了没发货怎么办",关键词匹配根本命中不了。后来用Spring AI加LangChain4j搭了一套 RAG 检索增强生成的知识库,两周上线,命中率从原来的四成多提到八成以上。整个过程没写一行 Python。
这篇就把我踩过的路完整梳理一遍:路线图怎么排、工具链怎么选、RAG 到底怎么落地、哪些坑必须提前知道。适合有 Java 基础、想往 AI 应用方向转的开发者,也适合正在评估技术选型的团队负责人。不吹概念,只讲能跑起来的东西。
2. 路线图拆解:Java 开发者该补什么、不该补什么
2.1 先划清边界:你要做的是应用,不是训练
很多人一上来就去啃 Transformer 论文、看反向传播推导,结果两周劝退。这是方向错了。AI 领域粗略分三层:
| 层级 | 典型工作 | 主要语言 | Java 开发者是否需要 |
|---|---|---|---|
| 模型训练层 | 预训练、微调、对齐 | Python | 基本不需要 |
| 模型服务层 | 推理部署、量化、加速 | Python/C++ | 了解即可 |
| 应用编排层 | RAG、Agent、工作流、业务集成 | Java 完全够用 | 核心战场 |
你要抢的是第三层。这一层的核心能力是:提示词工程、检索增强、工具调用编排、上下文管理、工程化落地。这些全是软件工程问题,Java 的强项。
提示:不要因为"AI"两个字就自我怀疑。应用层 80% 的工作量是数据清洗、接口编排、异常处理、性能优化,这些恰恰是 Java 后端最擅长的。
2.2 三阶段路线图,每阶段都有可交付物
我把入门拆成三个阶段,每个阶段都要求有能跑的东西,避免"学了一堆概念但啥也做不出来"。
第一阶段:打通调用链路(1-2 周)
目标是让 Java 程序能稳定调用大模型接口。核心知识点:
- HTTP 客户端调用大模型 API,理解请求体结构(messages 数组、role 角色、temperature 等参数)
- 流式响应处理(SSE),这是打字机效果的底层
- Token 概念与计费逻辑,知道钱花在哪
- 引入Spring AI或LangChain4j,用框架替代手写 HTTP
这个阶段的交付物:一个能对话的 Spring Boot 接口,支持流式输出。
第二阶段:RAG 检索增强(3-4 周)
目标是让模型能回答"它本来不知道"的私有知识。核心知识点:
- 文档解析(PDF、Word、Markdown、HTML)
- 文本分块策略(chunk size、overlap 怎么定)
- 向量化 Embedding 与向量数据库(Milvus、PgVector、Redis 等)
- 相似度检索与重排序
- 提示词模板拼接上下文
交付物:一个能基于本地文档回答问题的知识库服务。
第三阶段:Agent 与工程化(持续)
目标是让 AI 能"动手做事",而不只是"动嘴回答"。核心知识点:
- 工具调用(Function Calling),让模型决定调哪个接口
- 多轮对话状态管理
- 可观测性(日志、链路追踪、Token 统计)
- 缓存、限流、降级
交付物:一个能查订单、能建工单、能调内部系统的 AI 助手。
2.3 不该补的坑:这些时间可以省下来
- 不要系统学 Python 数据科学栈。除非你要转算法岗,否则 NumPy、Pandas 学到够看懂示例就行。
- 不要死磕数学推导。知道注意力机制大概在干嘛即可,推导过程交给专业的人。
- 不要一上来就搞微调。90% 的企业场景,RAG 加好的提示词就能解决,微调是最后手段。
- 不要迷信某个框架。Spring AI 和 LangChain4j 都在快速迭代,学的是思路不是 API。
3. 工具链选型:Spring AI 与 LangChain4j 到底怎么选
3.1 两个框架的定位差异
这是被问最多的问题。我的判断是:它们不是二选一,而是各有主场。
Spring AI的定位是"Spring 生态原生的 AI 抽象层"。它的设计哲学和 Spring Data、Spring Cache 一脉相承——用统一的抽象屏蔽底层差异。你换模型厂商,改配置就行;你用不同的向量库,换 starter 就行。对已经在 Spring Boot 体系里的团队,接入成本极低。
LangChain4j的定位更接近"Java 版的 LangChain",功能覆盖面更广,尤其在 Agent、工具调用、RAG 高级特性(如查询转换、多路召回)上更激进。它的 API 设计更贴近 AI 应用的思维方式,声明式注解用起来很顺手。
我实际项目的做法是:基础对话和模型抽象用 Spring AI,复杂的 RAG 编排和 Agent 用 LangChain4j。两者可以在同一个 Spring Boot 工程里共存,不冲突。
3.2 关键能力对照表
| 能力维度 | Spring AI | LangChain4j | 我的建议 |
|---|---|---|---|
| 模型接入 | 官方 starter 丰富 | 支持广泛 | 平手 |
| 流式输出 | 支持,与 WebFlux 集成好 | 支持 | Spring AI 略优 |
| RAG 基础 | 有,够用 | 更完整 | LangChain4j |
| 向量库支持 | 主流都支持 | 主流都支持 | 平手 |
| Agent/工具调用 | 支持 | 更成熟 | LangChain4j |
| 与 Spring 集成 | 原生 | 需手动配置 | Spring AI |
| 学习曲线 | 低(Spring 开发者) | 中 | 看背景 |
3.3 Maven 依赖怎么引
以 LangChain4j 为例,核心依赖长这样:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.35.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>0.35.0</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-pgvector</artifactId> <version>0.35.0</version> </dependency>Spring AI 则是走 starter 路线:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency>注意:版本号一定要对齐。LangChain4j 的各个模块版本必须一致,否则会出现 NoSuchMethodError 这种运行时才暴露的问题,排查起来很痛苦。建议用 BOM 统一管理。
3.4 选型决策的实操建议
如果你所在团队是标准的 Spring Boot 技术栈,且需求以"对话 + 基础 RAG"为主,直接上 Spring AI,接入快、维护省心。
如果需求里有复杂的 Agent 编排、多工具协同、高级检索策略,LangChain4j 更合适,它的抽象层次更贴合这类场景。
如果两个都想要,就共存。我现在的项目就是这么干的,Spring AI 管模型调用和配置,LangChain4j 管 RAG 链路,各司其职。
4. RAG 落地实战:从文档到可用的知识库
4.1 RAG 到底解决了什么问题
先讲清楚原理,不然后面全是黑盒。大模型的知识来自训练数据,有两个硬伤:一是时效性,训练截止之后的事它不知道;二是私有性,你公司的内部文档它没见过。
RAG(Retrieval-Augmented Generation,检索增强生成)的思路很朴素:既然模型不知道,那我在提问的时候把相关资料一起塞给它。流程是:
- 用户提问
- 把问题向量化,去向量库检索最相关的几段资料
- 把资料和问题拼成一个提示词,发给模型
- 模型基于资料回答
打个比方:模型是个博学但不了解你公司的专家,RAG 就是每次提问前先给他递一份相关资料,让他看着资料回答。这样既不用重新训练模型,又能保证答案基于事实。
4.2 文档处理:最脏最累但最关键的一步
我踩过最大的坑就在这。很多人以为 RAG 效果差是模型问题,其实八成是文档处理没做好。
第一步是解析。不同格式要用不同工具:
- PDF:推荐 Apache PDFBox,但要注意扫描件需要 OCR
- Word:Apache POI,注意表格和图片的处理
- Markdown/HTML:Jsoup 或直接文本处理
- 数据库/API:直接查询后拼接
第二步是清洗。原始文档里全是页眉页脚、页码、乱码、多余空行。这些噪声会严重干扰检索。我一般会做:
- 去掉连续空行和特殊字符
- 合并被换行切断的句子
- 过滤掉过短的段落(比如少于 10 个字的)
第三步是分块(Chunking)。这是最讲究的地方。块太大,检索出来的内容冗余,浪费 Token;块太小,语义不完整,检索不准。
我的经验参数:
| 文档类型 | chunk size | overlap | 说明 |
|---|---|---|---|
| 技术文档 | 500-800 字 | 100 字 | 段落语义完整 |
| FAQ | 按问答对切 | 0 | 一问一答天然分块 |
| 长篇文章 | 800-1000 字 | 150 字 | 保留上下文 |
| 代码文档 | 按函数/类切 | 50 字 | 保持代码完整 |
overlap 的作用是防止关键信息正好被切在边界上。比如一句话被切成两半,两半都不完整,加了重叠就能保证至少有一块是完整的。
4.3 向量化与向量库选择
分块之后,每块文本要通过 Embedding 模型转成向量(一串浮点数)。这个向量就代表了文本的语义,语义相近的文本,向量距离也近。
Embedding 模型的选择:
- 通用场景:OpenAI 的 text-embedding-3-small,性价比高
- 中文场景:BGE 系列(如 bge-large-zh)效果不错,可本地部署
- 预算敏感:本地部署开源模型,省 API 费用
向量库的选择,我列个实际对比:
| 向量库 | 部署难度 | 性能 | 适用场景 |
|---|---|---|---|
| PgVector | 低(复用 PG) | 中 | 已有 PG,数据量中等 |
| Milvus | 中 | 高 | 大规模、专业向量检索 |
| Redis | 低 | 中 | 已有 Redis,追求简单 |
| Elasticsearch | 中 | 中高 | 已有 ES,需混合检索 |
我的建议:如果团队已经有 PostgreSQL,直接用 PgVector,省一套中间件,运维成本最低。数据量上千万级别再考虑 Milvus。
4.4 检索质量优化:从能用到好用
基础 RAG 跑通后,你会发现效果时好时坏。这时候要做优化,几个关键手段:
混合检索。纯向量检索对精确匹配不敏感,比如用户问"错误码 E1024",向量检索可能召回一堆语义相近但错误码不对的内容。解决办法是向量检索 + 关键词检索(BM25)结合,各取所长。
重排序(Rerank)。初次检索召回 Top 20,再用重排序模型精排出 Top 5。重排序模型更慢但更准,用它来提纯。这一步对效果提升很明显,我实测命中率能再涨 10 个点。
查询改写。用户的问题往往口语化、有指代。比如"它怎么用","它"指什么?可以用模型先把问题改写成完整清晰的查询,再检索。
元数据过滤。给每个块打上标签(来源、部门、时间),检索时先按标签过滤,再算相似度。这样能大幅缩小范围,提升准确率。
4.5 一个最小可用的 RAG 代码骨架
用 LangChain4j 搭一个最简 RAG,核心代码大概这样:
// 1. 加载并切分文档 DocumentSplitter splitter = DocumentSplitters.recursive(500, 100); List<Document> documents = FileSystemDocumentLoader.loadDocuments("/docs"); List<TextSegment> segments = splitter.splitAll(documents); // 2. 向量化并存入向量库 EmbeddingModel embeddingModel = new OpenAiEmbeddingModel("your-api-key"); EmbeddingStore<TextSegment> store = new PgVectorEmbeddingStore( dataSource, "embeddings", 1536 ); EmbeddingStoreIngestor.builder() .embeddingModel(embeddingModel) .embeddingStore(store) .build() .ingest(segments); // 3. 构建检索增强的对话链 ChatLanguageModel chatModel = OpenAiChatModel.builder() .apiKey("your-api-key") .modelName("gpt-4o-mini") .build(); RetrievalAugmentor augmentor = DefaultRetrievalAugmentor.builder() .contentRetriever(EmbeddingStoreContentRetriever.builder() .embeddingStore(store) .embeddingModel(embeddingModel) .maxResults(5) .minScore(0.7) .build()) .build(); Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .retrievalAugmentor(augmentor) .build(); // 4. 提问 String answer = assistant.chat("订单超时未发货怎么处理?");这段代码里,maxResults是召回数量,minScore是相似度阈值。这两个参数要反复调,没有万能值。
提示:
minScore设太高会漏召回,设太低会引入噪声。建议先用 0.6-0.7 起步,根据实际效果微调。不同 Embedding 模型的分数分布不一样,换模型后要重新校准。
5. 常见问题与排查技巧实录
5.1 效果类问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 答非所问 | 检索没召回相关内容 | 检查分块、Embedding、相似度阈值 |
| 答案不完整 | 召回块太少或太小 | 增大 maxResults 或 chunk size |
| 胡编乱造 | 模型没基于资料回答 | 强化提示词约束,要求"仅根据资料回答" |
| 检索到无关内容 | 噪声数据太多 | 清洗文档,加元数据过滤 |
| 相同问题答案不稳定 | temperature 太高 | 降到 0.1-0.3 |
| 响应太慢 | 召回太多或模型太大 | 减少召回数,换小模型 |
5.2 几个我踩过的具体坑
坑一:分块把表格切碎了。产品文档里有很多参数表格,按固定字数切会把表格拦腰截断,检索出来全是残缺信息。解决办法是按结构切分,识别到表格就整块保留,或者用 Markdown 的标题层级来切。
坑二:Embedding 模型和检索模型不匹配。有次我入库用的是 A 模型,检索时配置写成了 B 模型,结果检索出来的东西驴唇不对马嘴。入库和检索必须用同一个 Embedding 模型,这是铁律。
坑三:提示词没约束,模型自由发挥。早期提示词写得太宽松,模型经常"我觉得..."然后开始编。后来改成明确指令:"你是知识库助手,只能根据以下资料回答,资料中没有的信息请回答'暂无相关信息'。"效果立刻稳定。
坑四:忽略 Token 成本。召回 10 个块,每块 500 字,加上系统提示词,一次请求轻松上万 Token。高频调用下成本很吓人。后来我做了两件事:一是召回数降到 5,二是加了缓存,相同问题直接返回缓存结果。
5.3 性能与成本优化技巧
缓存策略。问题级别的缓存最有效,相同问题直接命中。可以用 Redis 存"问题哈希 -> 答案",设置合理过期时间。注意要归一化问题(去空格、转小写)再算哈希。
异步与流式。用户对首字延迟很敏感,但对完整回答的等待容忍度高。用流式输出,首字 1 秒内出来,体验就好很多。Spring AI 和 LangChain4j 都支持流式。
批量 Embedding。入库时不要一条一条调 Embedding 接口,要批量提交,能省大量网络往返时间。
模型分级。简单问题用小模型(便宜快),复杂问题用大模型(贵但准)。可以先用小模型判断问题复杂度,再路由。
5.4 上线前必须检查的清单
- 提示词是否明确约束了"仅基于资料回答"
- 检索阈值是否经过实际数据校准
- 是否有兜底回答(检索不到时怎么回)
- 是否有敏感内容过滤
- Token 消耗是否有监控和告警
- 是否有降级方案(模型服务挂了怎么办)
- 日志是否记录了完整的请求链路,方便排查
6. 从 RAG 到 Agent:下一步怎么走
RAG 解决的是"知道"的问题,Agent 解决的是"做到"的问题。当你的知识库跑顺了,下一步自然是让 AI 能调用工具、执行动作。
工具调用(Function Calling)是核心机制。你定义好工具(比如"查询订单状态"、"创建工单"),模型根据用户意图决定调哪个、传什么参数。Java 里用 LangChain4j 的@Tool注解就能声明:
public class OrderTools { @Tool("根据订单号查询订单状态") public String queryOrder(@P("订单号") String orderId) { // 调用内部订单服务 return orderService.getStatus(orderId); } }然后在构建 Assistant 时注册这些工具,模型就能自动调用了。
Agentic RAG是更进一步的形态:让 Agent 自己决定要不要检索、检索几次、用什么查询。比如用户问一个复杂问题,Agent 可以先拆解成子问题,分别检索,再综合回答。这比固定流程的 RAG 灵活得多,但也更难控制,容易陷入循环或跑偏。
我的建议是循序渐进:先把基础 RAG 做扎实,再上工具调用,最后考虑 Agentic 编排。每一步都要有明确的业务价值,不要为了技术而技术。
可观测性这块千万别省。AI 应用的调试比传统应用难得多,因为输出是不确定的。我一般会记录:完整的提示词、召回的文档块及分数、模型的原始输出、Token 消耗、耗时。出问题时能快速定位是哪一环的问题。
最后分享一个我自己的体会:Java 开发者做 AI,最大的优势不是算法,而是工程化能力。模型能力大家都能用,但谁能把 RAG 的召回率调上去、把响应延迟压下来、把成本控制住、把系统做得稳定可维护,这才是真正的壁垒。我见过太多 demo 很惊艳但一上生产就崩的项目,问题全出在工程细节上。所以别焦虑自己不懂算法,把你后端那套本事用足,AI 应用这条路你走得比想象中顺。