Java开发者AI入门实战:Spring AI与LangChain4j构建RAG应用
1. Java 开发者切入 AI 的真实路径拆解1.1 为什么 Java 开发者做 AI 总觉得“隔了一层”我做了十多年 Java 后端从 SSH 时代一路写到 Spring Boot 微服务中间也带过不少从传统业务转 AI 方向的同事。一个特别普遍的现象是Java 开发者第一次接触 AI几乎都会经历一段“工具错位期”。网上搜到的教程清一色是 PythonLangChain、LlamaIndex、Transformers 全是 Python 生态连跑个本地模型都要先装 conda、配 CUDA写惯了强类型、依赖注入、分层架构的人突然被丢进一堆 notebook 和脚本里很容易产生一种“我是不是选错语言了”的自我怀疑。但实际情况恰恰相反。AI 应用落地到企业里最终要解决的不是“模型能不能跑”而是“模型怎么稳定地嵌进现有业务系统”。而现有业务系统里Java 占了半壁江山——订单、库存、权限、审批、报表、对账这些全是 Java 的地盘。你让一个 Python 脚本去对接 Spring Security 的权限体系、去连 MyBatis 的数据源、去走公司统一的网关和日志链路运维第一个不同意。所以 Java 开发者的真正优势不是去训练大模型而是把大模型能力工程化地接进企业级系统。这个定位想清楚了路线就清晰了。我个人的判断是Java 开发者入门 AI核心不是学算法而是学“如何调用、编排、约束、观测”模型。你要掌握的是 AI 应用的工程骨架而不是模型内部的数学推导。这条路线图我后面会拆成几个阶段每个阶段配对应的工具链全部是我自己踩过或者带人踩过的真实路径。1.2 一张适合 Java 人的 AI 入门路线图我把这条路线分成四个阶段从“能跑起来”到“能上生产”每一步都有明确的产出物避免学了一堆概念却做不出东西。阶段目标核心工具产出物第一阶段打通调用让 Java 程序能稳定调用大模型Spring AI、LangChain4j、HTTP 客户端一个能对话的 REST 接口第二阶段接入知识让模型基于私有数据回答RAG、向量库、Embedding一个可检索的知识库问答第三阶段编排智能让模型能调用工具、多步推理Function Calling、Agent一个能查库、下单的 Agent第四阶段工程化可观测、可评测、可扩展链路追踪、评测集、缓存一套可上线的 AI 服务这个路线的好处是每一阶段都能独立交付价值不需要等全部学完才动手。我见过太多人卡在“先把机器学习原理学透再动手”结果半年过去还在看梯度下降一个能用的接口都没写出来。对 Java 开发者来说先跑通再深入是更务实的策略。1.3 Spring AI 和 LangChain4j 到底怎么选这是被问得最多的问题没有之一。我的结论是两个都要了解但起步选哪个取决于你的项目形态。Spring AI 的优势在于它和 Spring Boot 生态是无缝的。你的项目本来就用 Spring Boot加一个 starter 依赖配一下 API KeyChatClient直接注入就能用配置走application.yml和现有的Service、Configuration风格完全一致。它背后是 Spring 官方团队在推版本迭代虽然快但方向明确适合“我就是要在一个标准 Spring Boot 项目里加 AI 能力”的场景。LangChain4j 的优势在于它的抽象层次更丰富尤其是 RAG、Agent、工具调用这些偏“编排”的能力封装得更细。它的AiServices可以把一个 Java 接口直接变成 AI 代理声明式地定义工具和记忆写起来很优雅。如果你要做的是复杂的多步推理、多工具协同LangChain4j 的表达力会更强一些。我实际的做法是简单对话和基础 RAG 用 Spring AI复杂 Agent 编排用 LangChain4j两者在同一个项目里共存也没问题因为它们底层都是 HTTP 调用不冲突。至于网上热词里提到的 LangGraph4j那是更偏状态机式编排的库适合流程特别复杂的场景入门阶段可以先放一放等你有明确的“多节点、带条件分支”的需求再上。2. 核心工具链与关键概念解析2.1 大模型调用从 HTTP 到声明式客户端最开始我建议你手写一次 HTTP 调用哪怕只用RestTemplate或WebClient。目的是搞清楚一件事大模型 API 本质上就是一个 HTTP 接口你发一段 JSON 过去它回一段 JSON。这个认知很重要因为它能破除“AI 很神秘”的心理障碍。一个典型的对话请求体大概长这样{ model: qwen-plus, messages: [ {role: system, content: 你是一个严谨的技术助手}, {role: user, content: 解释一下什么是 RAG} ], temperature: 0.7 }messages是对话历史role分 system、user、assistant 三种temperature控制随机性。你把这个请求发出去拿回来的就是模型生成的文本。手写一遍之后你再看 Spring AI 的ChatClient就会明白它只是帮你把这层 HTTP 封装成了 Java 对象本质没变。Spring AI 的调用代码大概是这样RestController public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }配置在application.yml里spring: ai: openai: api-key: ${AI_API_KEY} base-url: https://your-endpoint/v1 chat: options: model: qwen-plus temperature: 0.7这里有个坑我要提前说base-url 一定要确认清楚很多兼容 OpenAI 协议的服务商路径是/v1结尾少写或多写都会 404。我第一次配的时候就是因为多写了一个斜杠排查了半小时。2.2 RAG让模型回答私有知识的核心机制RAG 是热词里出现频率最高的全称是 Retrieval-Augmented Generation检索增强生成。用大白话讲模型本身不知道你公司的内部文档但你可以先把文档切成小块存进向量库用户提问时先检索出最相关的几块连同问题一起塞给模型让它“看着资料回答”。这样既不用重新训练模型又能保证答案基于你的私有数据。RAG 的完整链路分两大段离线索引和在线检索。离线索引阶段文档加载 → 文本切分 → 向量化Embedding→ 存入向量库。在线检索阶段用户提问 → 问题向量化 → 向量库相似度检索 → 取 Top-K 片段 → 拼进 Prompt → 调用模型生成答案。这里面每个环节都有讲究。文本切分不是随便切的切太大检索不精准切太小语义不完整。我一般用 500 到 800 字符一块重叠 100 字符左右保证跨块的语义不断裂。Embedding 模型的选择也很关键中文场景下用专门优化过中文的模型检索命中率会明显高一截。LangChain4j 里做 RAG 的代码结构很清晰EmbeddingStoreTextSegment embeddingStore new InMemoryEmbeddingStore(); EmbeddingModel embeddingModel new AllMiniLmL6V2EmbeddingModel(); // 索引 Document document Document.from(你的私有文档内容); DocumentSplitter splitter DocumentSplitters.recursive(800, 100); ListTextSegment segments splitter.split(document); ListEmbedding embeddings embeddingModel.embedAll(segments).content(); embeddingStore.addAll(embeddings, segments); // 检索 Embedding queryEmbedding embeddingModel.embed(用户的问题).content(); ListEmbeddingMatchTextSegment matches embeddingStore.findRelevant(queryEmbedding, 5);findRelevant的第二个参数是 Top-K取多少块合适我的经验是 3 到 5 块取太多会稀释关键信息还会撑爆上下文窗口取太少又可能漏掉答案。这个值需要根据你的文档密度实测调整。2.3 向量库选型从内存到生产级入门阶段用InMemoryEmbeddingStore就够了重启数据就没了但胜在零依赖、跑得快适合验证流程。一旦要持久化就得选真正的向量库。向量库特点适用场景Redis Stack已有 Redis 可直接用支持向量检索中小规模、追求部署简单PostgreSQL pgvector关系库扩展事务友好已有 PG、数据量中等Milvus专业向量库性能强大规模、高并发检索Elasticsearch全文向量混合检索已有 ES、需要混合搜索Chroma轻量、Python 生态友好快速原型我个人的偏好是如果项目已经有 PostgreSQL直接上 pgvector省一套中间件运维成本最低。数据量上到千万级向量再考虑 Milvus 这类专业库。别一上来就追求“最强向量库”大部分业务场景几百万向量用 pgvector 完全扛得住。2.4 Function Calling 与 Agent让模型动手做事光会聊天不够真正的价值在于让模型能调用你的业务接口。比如用户问“帮我查一下订单 12345 的状态”模型需要调用你的订单查询接口拿到结果再组织语言回答。这就是 Function Calling。Spring AI 里定义工具很直接public class OrderTools { Tool(description 根据订单号查询订单状态) public String queryOrderStatus(String orderId) { // 调用真实业务逻辑 return orderService.getStatus(orderId); } }然后在调用时注册工具String response chatClient.prompt() .user(订单 12345 现在什么状态) .tools(new OrderTools()) .call() .content();模型会自己判断要不要调用这个工具、传什么参数。这里的关键是description要写清楚模型靠它来决定何时调用。描述写得含糊模型就可能该调不调或者乱调。Agent 则是在 Function Calling 基础上更进一步让模型能多步推理、连续调用多个工具、根据中间结果决定下一步。LangChain4j 的AiServices做这件事很顺手interface Assistant { SystemMessage(你是一个订单助手可以查询和修改订单) String chat(String userMessage); } Assistant assistant AiServices.builder(Assistant.class) .chatLanguageModel(model) .tools(new OrderTools()) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .build();chatMemory负责记住对话历史tools负责提供能力接口方法就是入口。这种声明式写法对 Java 开发者来说非常友好几乎不需要学新范式。3. 从零搭建一个可用的 RAG 问答服务3.1 环境准备与依赖配置我以一个真实的 Spring Boot 项目为例带你走一遍完整流程。假设你已经有一个标准的 Spring Boot 3.x 项目JDK 17 以上。第一步加依赖。用 Spring AI 做基础对话用 LangChain4j 做 RAG 编排两个都加dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-M4/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.35.0/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-easy-rag/artifactId version0.35.0/version /dependency版本号要注意Spring AI 在 1.0 之前迭代很快M 版本之间 API 有变动建议锁定一个版本别频繁升级。LangChain4j 相对稳定0.3x 系列 API 变化不大。第二步配置模型接入。application.yml里配好 API Key 和模型名。我强烈建议把 Key 放环境变量别硬编码进配置文件更别提交到代码仓库。这个是最基本的安全习惯我见过不止一次有人把 Key 提交上去第二天就被刷爆额度。3.2 文档索引的完整实现假设你要把一批 Markdown 格式的产品文档做成知识库。核心代码如下Service public class KnowledgeIndexService { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; public KnowledgeIndexService(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { this.embeddingModel embeddingModel; this.embeddingStore embeddingStore; } public void indexDirectory(Path dir) throws IOException { DocumentSplitter splitter DocumentSplitters.recursive(800, 100); try (StreamPath files Files.walk(dir)) { files.filter(p - p.toString().endsWith(.md)) .forEach(p - indexFile(p, splitter)); } } private void indexFile(Path path, DocumentSplitter splitter) { try { String content Files.readString(path); Document doc Document.from(content, Metadata.from(source, path.toString())); ListTextSegment segments splitter.split(doc); ListEmbedding embeddings embeddingModel.embedAll(segments).content(); embeddingStore.addAll(embeddings, segments); } catch (IOException e) { throw new RuntimeException(索引失败: path, e); } } }这里有几个实操细节值得说。Metadata里存源文件路径是为了后面回答时能给出引用来源用户看到“根据 XX 文档”会更信任答案。切分参数 800/100 是我实测下来对中文技术文档比较均衡的值你可以根据文档特点微调。索引过程如果文档多建议异步执行别阻塞启动流程。3.3 检索与生成的组装逻辑检索到相关片段后怎么拼进 Prompt 直接决定回答质量。我常用的模板是这样的public String answer(String question) { Embedding queryEmbedding embeddingModel.embed(question).content(); ListEmbeddingMatchTextSegment matches embeddingStore.findRelevant(queryEmbedding, 5); String context matches.stream() .map(m - m.embedded().text()) .collect(Collectors.joining(\n\n---\n\n)); String prompt 请严格根据以下资料回答问题。如果资料中没有相关信息请直接说资料中未提及不要编造。 资料 %s 问题%s .formatted(context, question); return chatClient.prompt().user(prompt).call().content(); }这个 Prompt 里最关键的一句是“如果资料中没有相关信息请直接说资料中未提及不要编造”。不加这句模型很容易在检索不到内容时自己编答案这在企业场景里是致命的。我踩过这个坑用户问了一个知识库里根本没有的问题模型一本正经地编了一段差点造成误导。3.4 检索命中率的调优实战RAG 做出来容易做好难。热词里提到的“rag hit rate”就是核心痛点。我总结几个提升命中率的实操手段。第一问题改写。用户的问题往往口语化、信息不全直接拿去检索效果差。可以先让模型把问题改写成更适合检索的形式再拿去向量库查。比如“那个订单的事咋样了”改写成“订单状态查询”命中率立刻不一样。第二混合检索。纯向量检索对关键词不敏感比如产品型号、专有名词向量可能匹配不准。加上 BM25 这类关键词检索两路结果融合效果通常比单路好。Elasticsearch 天然支持这种混合模式。第三重排序。先粗召回 20 条再用一个重排序模型精排出 Top 5精度提升明显。LangChain4j 支持接入重排序模型多一步但值得。第四切分策略。技术文档按标题层级切比按固定长度切效果好得多。Markdown 的##、###天然就是语义边界顺着它切每块语义完整检索自然准。调优手段提升幅度我的实测实现成本问题改写命中率 15% 左右低混合检索命中率 20% 左右中重排序精度 10% 左右中语义切分命中率 10% 左右低这些数字不是绝对的不同数据集差异很大但方向是可靠的。建议你先做语义切分和问题改写这两个成本最低、见效最快。4. 常见问题与排查技巧实录4.1 调用超时与限流怎么处理大模型调用最大的不确定性就是延迟。正常情况几秒返回高峰期可能几十秒甚至超时。我的处理原则是设置合理超时 重试 降级。超时时间别设太短我一般设 60 秒因为长文本生成确实慢。重试用指数退避第一次等 1 秒第二次 2 秒第三次 4 秒最多重试 3 次。但要注意不是所有错误都该重试401、403 这种鉴权错误重试多少次都没用只有 429 限流和 5xx 服务端错误才值得重试。限流方面很多服务商有 QPS 限制超过就返回 429。生产环境一定要做客户端限流用 Guava 的RateLimiter或者 Resilience4j 都行把请求速率控制在配额以内比被服务端拒绝再重试要优雅得多。4.2 回答不稳定、胡编乱造怎么办这是 RAG 场景最头疼的问题。模型明明有资料却答非所问或者资料里没有的内容也敢编。我的排查顺序是这样的。先看检索结果。把召回的片段打印出来如果片段本身就不相关那是检索的问题回去调切分和检索策略。如果片段相关但模型没用上那是 Prompt 的问题加强约束语句明确要求“只根据资料回答”。再看温度参数。temperature设太高模型发挥空间大容易跑偏。RAG 场景我一般设 0.1 到 0.3要的是稳定复现不是创意。创意写作才需要高温度。最后看模型本身。有些模型指令遵循能力弱你怎么约束它都爱自由发挥。这种时候换个指令遵循强的模型问题可能直接消失。选模型不能只看价格和速度指令遵循能力在 RAG 场景里权重很高。4.3 上下文窗口超限的应对模型有上下文长度限制检索片段加对话历史加问题很容易超。应对手段有几个。一是控制检索片段数量Top-K 别贪多3 到 5 块通常够用。二是压缩对话历史只保留最近几轮或者对历史做摘要。三是用支持长上下文的模型现在很多模型支持 128K 甚至更长但要注意上下文越长成本和延迟越高而且中间部分的信息容易被忽略不是越长越好。我一般会做一个 token 预算给检索片段留 60%给对话历史留 20%给问题和回答留 20%。超出预算就裁剪优先裁历史保留检索片段因为检索片段是回答的依据。4.4 常见问题速查表现象可能原因排查方向401 鉴权失败Key 错误或过期检查环境变量、Key 有效性404 路径错误base-url 配置不对确认是否带 /v1、有无多余斜杠429 限流请求频率超配额加客户端限流、错峰调用回答与资料无关检索不准打印召回片段、调切分和检索模型编造答案Prompt 约束不足加强“无资料则拒答”指令响应特别慢模型负载高或文本过长缩短输入、换时段、加超时中文乱码编码不一致统一 UTF-8向量检索结果差Embedding 模型不适配换中文优化模型这张表是我自己遇到问题时的第一反应清单基本能覆盖八成常见故障。遇到问题先对照查一遍比盲目试错效率高得多。4.5 几个我踩过的坑和独家心得第一个坑别在循环里逐条调用 Embedding。我一开始索引文档时一个片段一个片段地调 Embedding 接口几百个片段跑了几分钟。后来改成embedAll批量调用一次传一批速度快了十几倍。批量接口能显著降低网络往返开销这个优化立竿见影。第二个坑向量库的维度必须和 Embedding 模型一致。换 Embedding 模型时如果维度变了旧数据全部作废必须重新索引。我换过一次模型忘了重建索引检索结果全是乱的排查了半天才想起来。所以换模型前一定要确认维度并且准备好重建索引的脚本。第三个坑对话记忆别无限增长。MessageWindowChatMemory要设上限不然聊得越久每次请求带的上下文越大成本和延迟都飙升。我一般设 10 到 20 条消息超出就丢弃最早的。重要信息靠 RAG 检索补不靠对话历史硬记。第四个心得给 AI 服务加可观测性。每次调用记录输入、输出、耗时、token 消耗、检索到的片段。这些数据在排查问题和优化成本时价值极高。我一开始没记录出了问题两眼一抹黑后来加了日志和指标定位问题快了很多。用 Micrometer 打点接 Prometheus 和 Grafana一套下来并不复杂。第五个心得评测集要早建。准备几十个典型问题和标准答案每次调整检索策略或 Prompt都跑一遍评测集看命中率和准确率的变化。没有评测集优化全靠感觉很容易改了这个坏了那个。这个投入前期看着麻烦后期回报巨大。5. 工程化落地与后续扩展方向5.1 把 AI 能力做成标准服务入门阶段代码写在 Controller 里没问题但要上生产必须做分层。我的做法是抽一个AiService层把模型调用、检索、Prompt 组装都封装进去Controller 只负责参数校验和响应包装。这样业务逻辑和 AI 逻辑解耦测试和替换都方便。再往上把 AI 能力做成独立的微服务通过内部接口暴露给其他系统。好处是模型配置、限流、缓存、监控都集中管理其他业务方不用关心 AI 细节只管调用。我们内部就是这么做的订单系统、客服系统、运营后台都通过统一接口调 AI 能力维护成本低很多。缓存也值得做。相同的问题反复问没必要每次都调模型。用问题文本的哈希做 Key答案做 Value存 Redis设个合理过期时间。对于高频重复问题缓存能省下大量调用成本。但要注意涉及实时数据的问答不能缓存否则答案会过期。5.2 从 RAG 到 Agent 的演进思路RAG 解决的是“问答”Agent 解决的是“办事”。当你的知识库问答跑顺了下一步自然是让 AI 能执行操作。比如客服场景用户说“帮我退掉订单 12345”Agent 需要先查订单、判断是否符合退款条件、调用退款接口、返回结果。这是一条多步链路每一步都可能需要模型决策。演进路径我建议是先做单工具调用验证模型能正确选工具、传参数再做多工具串联验证多步推理最后做带条件分支的复杂流程。每一步都加评测确保稳定性。别一上来就搞复杂 Agent链路越长出错概率越高排查越难。LangChain4j 的AiServices配合tools和chatMemory能覆盖大部分 Agent 场景。真到了需要状态机式编排的复杂流程再考虑 LangGraph4j 这类工具。工具是跟着需求走的别为了用而用。5.3 成本控制的几个实用手段AI 调用是要花钱的token 消耗直接对应成本。控制成本我有几个习惯。一是按需选择模型。简单任务用便宜的小模型复杂任务才上大模型。很多分类、抽取任务小模型完全够用成本差好几倍。可以在服务里做路由根据任务类型选模型。二是精简 Prompt。System Prompt 别写太长检索片段别塞太多历史别留太久。每一部分都在消耗 token。我见过有人 System Prompt 写了上千字每次调用都带着成本白白翻倍。三是缓存高频结果。前面说过了这里再强调一次对成本敏感的场景缓存是性价比最高的优化。四是监控 token 消耗。每次调用记录 input token 和 output token按天统计找出消耗大户。有时候某个功能设计不合理token 消耗异常高监控能帮你发现。5.4 后续可以深入的方向如果你已经把 RAG 问答跑通接下来有几个方向值得深入。多模态让模型能处理图片、表格、PDF 里的图表这在文档问答场景里需求很大。GraphRAG用知识图谱增强检索处理实体关系复杂的场景比纯向量检索强。评测体系建立自动化的 RAG 评测流水线每次改动自动跑分这是从“能用”到“可靠”的关键一步。我个人最看好的是评测体系这块。很多团队 RAG 做出来了但不敢上线因为不知道效果到底怎么样。有了评测集和自动化评测心里就有底了迭代也敢放手做。这个投入越早越好。最后分享一个我自己的体会Java 开发者做 AI最大的障碍从来不是技术而是心态。总觉得自己不是科班出身总想先把理论补全再动手。但 AI 应用的工程化恰恰是 Java 开发者最擅长的领域——分层、解耦、可观测、可测试这些我们做了十几年的东西放到 AI 场景里一样适用。把模型当成一个不太稳定、需要约束和监控的外部依赖用你熟悉的工程手段去治理它这条路就走通了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →