1. Java 开发者切入 AI 的真实处境与路线选择1.1 为什么 Java 程序员一碰 AI 就容易迷路我带过不少从 Java 后端转 AI 方向的朋友几乎所有人第一周都会陷入同一个困境打开搜索引擎满屏都是 Python 的教程、Notebook、PyTorch 示例自己写了七八年的 Spring Boot、MyBatis、JVM 调优经验好像一夜之间归零了。这种焦虑非常真实但方向其实错了。Java 开发者的优势从来不是从零训练一个大模型而是把 AI 能力工程化、产品化、稳定地跑在生产环境里。模型训练那部分确实 Python 生态更强但一个 AI 应用真正上线之后80% 的工作量是接口设计、并发控制、上下文管理、向量检索、限流降级、可观测性、成本核算——这些恰恰是 Java 工程师的主场。所以路线图的第一条原则就是不要试图变成算法工程师要成为 AI 应用工程师。我见过太多人一上来就啃《深度学习》花书啃了三个月连一个能跑的接口都没写出来最后放弃。正确的姿势是反过来先用 Spring AI 或 Spring AI Alibaba 跑通一个能对话的接口再逐步往里加 RAG、加工具调用、加 Agent 编排遇到不懂的原理再回头补。这种先跑通再深挖的路径对 Java 背景的人友好得多。1.2 一张适合 Java 人的四阶段路线图我把这条路线拆成四个阶段每个阶段都有明确的产出物避免学了很多但什么都没做出来的空转。阶段核心目标关键产出建议周期第一阶段打通调用让 Java 程序能稳定调用大模型一个封装好的 ChatClient 工具类1-2 周第二阶段上下文工程掌握 Prompt、记忆、结构化输出带多轮记忆的对话服务2-3 周第三阶段RAG 检索增强让模型回答私有知识一个可用的知识库问答系统3-4 周第四阶段Agent 与工程化工具调用、编排、可观测、成本控制一个能落地的业务 AI 模块4-6 周这个节奏不是拍脑袋定的。第一阶段之所以只要 1-2 周是因为 Spring AI 已经把底层 HTTP 调用、重试、流式响应都封装好了你只需要理解ChatClient和ChatModel两个抽象。第三阶段之所以要 3-4 周是因为 RAG 的坑几乎全在细节里——分块策略、Embedding 模型选型、向量库的相似度阈值、召回与重排的配合每一个都能让你调上一整天。提示不要跳过第一阶段直接上 RAG。我见过有人连流式响应和阻塞调用的区别都没搞清楚就去搞向量检索结果排查问题时完全不知道是模型层的问题还是检索层的问题。1.3 工具链选型Spring AI 还是 LangChain4j这是被问得最多的问题没有之一。我的结论很直接如果你的主战场是 Spring Boot 项目优先 Spring AI如果你需要更灵活的 Agent 编排且不介意多学一套 API可以看 LangChain4j。Spring AI 的最大价值是和 Spring 生态无缝。你的RestTemplate、ConfigurationProperties、Actuator、Micrometer 全都能直接复用依赖注入、配置外置、健康检查这些你熟得不能再熟的东西它都帮你接好了。Spring AI Alibaba 则是在此基础上补齐了国内模型的适配和 NL2SQL、RAG 等场景化能力做企业级应用时省事很多。LangChain4j 的优势在于抽象层次更丰富Chain、Agent、Tool 的组合方式更接近 Python 版 LangChain 的思路适合做复杂的多步推理流程。但它的生态和 Spring 的耦合度没那么高你需要自己处理一些配置和生命周期问题。至于现在到底用 Spring AI 还是 LangGraph4j这类问题我的看法是LangGraph4j 更适合做有状态的多 Agent 工作流比如一个需要反复循环、条件分支、人工介入的复杂流程。如果你只是做一个问答或者文档检索用 LangGraph4j 属于杀鸡用牛刀反而增加维护成本。2. 核心工具链拆解与关键细节2.1 Spring AI 的核心抽象到底抽象了什么很多人用 Spring AI 用了很久还是把它当成一个HTTP 客户端封装这就浪费了它真正的设计价值。它的核心抽象其实就三层理解这三层整个框架就通了。第一层是ChatModel负责和具体模型厂商打交道。OpenAI、通义千问、DeepSeek、Ollama 本地模型各自有各自的实现但对外暴露的接口是一致的。这一层解决的是换模型不改业务代码的问题。第二层是ChatClient这是你日常打交道最多的。它提供了 Fluent API把 Prompt 组装、消息角色、流式响应、结构化输出这些常用操作串起来。比如.prompt().user(...).call().content()这种链式写法读起来几乎就是自然语言。第三层是Advisor这是 Spring AI 里最容易被忽视但最有价值的部分。它本质是一个拦截器链可以在请求前后插入逻辑。RAG 的检索注入、对话记忆的读写、日志埋点、敏感词过滤全都可以用 Advisor 实现。理解了 Advisor你就理解了 Spring AI 的扩展哲学。Configuration public class AiConfig { Bean public ChatClient chatClient(ChatModel chatModel, VectorStore vectorStore) { return ChatClient.builder(chatModel) .defaultSystem(你是一个严谨的技术助手回答要给出依据) .defaultAdvisors( new MessageChatMemoryAdvisor(new InMemoryChatMemory()), new QuestionAnswerAdvisor(vectorStore) ) .build(); } }上面这段配置一次性把系统提示词、对话记忆、RAG 检索都挂上了。这就是 Spring AI 的威力——你不需要手写一堆 if-else 去拼上下文框架帮你编排好了。2.2 模型接入的三种方式与选型考量接入模型这件事看起来简单实际上选错了会直接影响成本和稳定性。我把它分成三类。第一类是云厂商 API比如通义千问、DeepSeek、智谱等。优点是开箱即用、模型能力强、无需运维缺点是按 token 计费、有网络延迟、数据要出内网。适合快速验证和中小规模应用。第二类是本地部署通过 Ollama 或 vLLM 跑开源模型。优点是数据不出内网、无调用费用、可离线缺点是需要 GPU 资源、模型能力通常弱于头部闭源模型、运维成本高。适合对数据敏感的场景。第三类是混合模式简单任务走本地小模型复杂任务走云端大模型。这是我最推荐的工程化方案但需要一套路由逻辑。选型时我一般看三个指标单次调用成本、首 token 延迟、任务准确率。这三个指标往往互相矛盾需要根据业务场景权衡。比如客服场景对延迟敏感可以接受稍弱的模型而合同审查场景对准确率要求极高延迟高一点无所谓。注意无论用哪种方式都要在配置里把 API Key、模型名、超时时间、最大重试次数外置化。我见过把 Key 硬编码在代码里然后提交到仓库的这种事故一旦发生损失的不只是钱。2.3 RAG 工具链向量库、Embedding 与重排RAG 是 Java 开发者最容易低估复杂度的部分。表面上看就是把文档切块、存进向量库、查询时召回但真正做起来每一步都有讲究。向量库选型上Spring AI 支持 Redis、PGVector、Milvus、Elasticsearch、Chroma 等多种实现。我的建议是如果项目里已经有 PostgreSQL直接用 PGVector省一套中间件如果数据量在千万级以上考虑 Milvus如果只是做原型Chroma 或内存向量库足够。Embedding 模型的选择直接影响召回质量。中文场景下通义千问的 text-embedding 系列、BGE 系列都是不错的选择。要注意的是Embedding 模型一旦选定索引和查询必须用同一个模型否则向量空间不一致召回结果会完全错乱。这个坑我踩过排查了半天才发现是索引时用了 A 模型、查询时用了 B 模型。重排Rerank是提升 RAG 质量的关键一步。向量检索是粗筛召回 Top-K 个候选重排模型再对这些候选做精排把真正相关的排到前面。加了重排之后回答准确率通常能提升 10-20 个百分点。代价是多一次模型调用延迟增加几百毫秒。环节常见方案关键参数踩坑点文档分块固定长度、按段落、按语义chunkSize、overlap分块太大召回不准太小丢上下文向量化通义、BGE、OpenAI维度、归一化索引与查询模型必须一致存储检索PGVector、Milvus、Redis相似度算法、Top-K余弦与内积结果不同重排BGE-Reranker、CohereTop-N重排后要重新截断上下文长度2.4 结构化输出与函数调用让 AI 真正接入业务大模型返回的是自然语言但业务系统需要的是结构化数据。Spring AI 提供了BeanOutputConverter这类工具可以把模型输出直接映射成 Java 对象。record OrderInfo(String productName, int quantity, String address) {} OrderInfo info chatClient.prompt() .user(从这句话里提取订单信息帮我订3台显示器送到杭州市西湖区) .call() .entity(OrderInfo.class);这段代码背后Spring AI 会自动生成 JSON Schema 塞进 Prompt要求模型按格式输出再把结果反序列化成对象。看起来魔法其实原理很朴素。函数调用Tool Calling则更进一步让模型能调用你定义的 Java 方法。比如你定义一个查询天气的方法模型判断需要天气信息时会返回一个调用请求框架执行后把结果回传给模型模型再生成最终回答。这是构建 Agent 的基础能力。提示结构化输出不是 100% 可靠的模型偶尔会返回格式错误的内容。生产环境一定要加校验和重试不能假设模型永远听话。3. 从零到一一个可落地的 RAG 问答模块实操3.1 项目初始化与依赖配置我以一个内部技术文档问答的场景来演示完整流程。这个场景很典型公司有一堆 Markdown 和 PDF 文档新同事查资料靠翻文件夹效率极低。先建一个 Spring Boot 3.x 项目JDK 用 17 或 21。依赖方面核心是 Spring AI 的 starter 和向量库的 starter。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-pgvector-store-spring-boot-starter/artifactId /dependency配置文件里把模型和向量库的地址、Key、库名都外置spring: ai: openai: api-key: ${AI_API_KEY} base-url: ${AI_BASE_URL} chat: options: model: qwen-plus temperature: 0.3 embedding: options: model: text-embedding-v3 datasource: url: jdbc:postgresql://localhost:5432/ai_demo这里temperature设成 0.3 而不是默认的 0.7是因为问答场景需要稳定、少发散。这个参数调过的人都知道温度越高回答越有创意但事实性任务上就越容易胡说。3.2 文档加载、分块与向量化入库文档入库是整个 RAG 的地基地基没打好后面怎么调都白搭。Spring AI 提供了DocumentReader和TextSplitter来简化这个过程。Component public class DocIngestService { private final VectorStore vectorStore; public DocIngestService(VectorStore vectorStore) { this.vectorStore vectorStore; } public void ingest(Resource resource) { TextSplitter splitter new TokenTextSplitter(500, 100, 5, 10000, true); ListDocument docs splitter.apply( List.of(new Document(resource)) ); vectorStore.add(docs); } }TokenTextSplitter的五个参数分别是目标块大小、最小块字符数、最小块行数、最大块 token 数、是否保留分隔符。我一般把目标块大小设在 400-600 token 之间overlap 留 10%-20%。为什么因为太小了单块信息不完整太大了检索精度下降而且塞进 Prompt 会挤占上下文窗口。分块策略上Markdown 文档最好按标题层级切代码文档按函数切PDF 按段落切。一刀切用固定长度效果通常不理想。我实测下来按语义边界分块比按固定长度分块召回准确率能高出一截。注意入库是幂等操作要小心。重复入库会导致同一内容有多份向量检索时重复召回。建议给每个文档算一个内容哈希入库前先查重。3.3 检索增强的问答链路实现入库完成后问答链路其实很短因为 Spring AI 的QuestionAnswerAdvisor已经帮你把检索和注入都做了。RestController public class QaController { private final ChatClient chatClient; public QaController(ChatClient chatClient) { this.chatClient chatClient; } GetMapping(/ask) public String ask(RequestParam String question) { return chatClient.prompt() .user(question) .call() .content(); } }但这段代码能跑通不代表能用。真正上线前我至少会做三件事。第一加相似度阈值过滤。默认情况下即使没有相关文档向量库也会返回 Top-K 个最不相关的结果模型拿着这些垃圾上下文照样能编出一本正经的答案。设置一个相似度阈值比如 0.7低于阈值的直接丢弃让模型老实说我不知道。第二加引用来源。让模型在回答时标注引用了哪段文档用户才能验证。这需要在 Prompt 里明确要求并在返回结果里带上元数据。第三加流式响应。问答场景用户等 5 秒和等 1 秒的体验天差地别。Spring AI 的.stream()方法返回FluxString配合 SSE 推给前端首字延迟能压到几百毫秒。3.4 效果调优从能答到答得准RAG 上线只是开始调优才是重头戏。我一般从四个维度入手。召回质量先看召回的内容对不对。如果召回的就是错的后面怎么调都没用。这时候要检查分块策略、Embedding 模型、相似度算法。上下文组织召回对了但塞进 Prompt 的顺序和格式也会影响效果。我习惯把最相关的放最前面并在每段前加来源标注让模型知道哪段来自哪里。Prompt 设计系统提示词里要明确只根据提供的上下文回答上下文没有就说不知道回答要简洁。这三句话能挡掉大部分幻觉。重排如果预算允许加一个重排模型把召回 Top-20 精排到 Top-5效果提升立竿见影。调优维度观察指标常用手段召回质量召回率、命中率调分块、换 Embedding、加混合检索上下文组织答案相关性排序、去重、截断Prompt幻觉率约束提示词、Few-shot 示例重排精排准确率引入 Rerank 模型4. 常见问题与排查技巧实录4.1 调用超时、限流与重试的正确姿势大模型调用超时是家常便饭尤其是流式响应网络抖动一下就可能断。我的经验是超时时间不要设太短重试要区分场景。普通问答超时设 30 秒比较稳妥流式响应可以设 60 秒。重试方面网络类错误连接超时、5xx可以重试 2-3 次但 4xx 错误参数错误、鉴权失败重试没意义只会浪费配额。重试要加指数退避避免瞬间打爆对方接口。限流方面云厂商通常有 QPS 和 TPM每分钟 token 数双重限制。生产环境一定要在应用层做令牌桶限流别等被厂商限了才反应过来。我一般用 Resilience4j 的RateLimiter配合Retry配置外置方便按环境调整。提示重试一定要幂等。如果一次调用其实成功了只是响应丢了重试会导致重复计费甚至重复业务操作。涉及写操作的场景务必用业务唯一 ID 去重。4.2 上下文超长与 token 成本控制上下文窗口是有上限的对话轮次一多历史消息就会撑爆窗口。常见做法有三种滑动窗口只保留最近 N 轮、摘要压缩把旧对话总结成一段、向量记忆把历史存进向量库按需召回。我一般用组合策略最近 5 轮原文保留更早的做摘要摘要再长就存向量库。这样既保证近期上下文完整又控制住 token 消耗。成本控制上几个实用技巧简单任务用小模型、缓存高频问题的答案、压缩 Prompt 里的冗余描述、限制最大输出 token。我算过一笔账一个日活几千的问答应用做好缓存和模型分级成本能降一半以上。4.3 结构化输出解析失败的排查entity()方法解析失败通常有三个原因模型没按格式输出、JSON 里有非法字符、字段类型不匹配。排查顺序是先把模型的原始返回打出来看确认是不是格式问题如果是检查 Prompt 里的 Schema 描述是否清晰如果 Schema 没问题但模型还是乱来考虑换一个指令遵循能力更强的模型或者降低 temperature。还有一种隐蔽的情况模型返回的 JSON 里带了 Markdown 代码块标记json ...解析器直接懵了。这时候要么在 Prompt 里明确要求不要用代码块包裹要么在解析前先做一次清洗。4.4 常见问题速查表现象可能原因排查方向回答与文档无关召回为空或阈值过低检查向量库是否有数据、相似度阈值回答重复啰嗦temperature 过高、Prompt 未约束降温度、加简洁回答约束调用频繁超时网络、模型负载、超时设置过短加超时、加重试、换时段成本异常高上下文过长、无缓存、模型选型过大查 token 用量、加缓存、分级模型结构化解析失败格式不符、非法字符打印原始返回、清洗、换模型多轮对话失忆记忆未配置或窗口过小检查 Advisor、调窗口大小4.5 几个我踩过的坑和独家心得第一个坑是向量库的相似度算法。PGVector 默认用余弦距离但如果你建索引时用了内积查询时又用余弦结果会完全不对。建表和查询必须用同一种算法这个一定要在文档里写清楚。第二个坑是流式响应的异常处理。Flux流中途出错如果不处理前端会一直转圈。要在流里加onErrorResume出错时推一条错误消息并正常关闭连接。第三个心得是给 AI 模块加可观测性。每次调用的输入、输出、耗时、token 用量、召回文档 ID全部打点到 Micrometer接 Grafana 看板。上线后你会发现很多模型不行的问题其实是某类问题召回一直为空或者某个 Prompt 触发了异常长的输出。没有可观测性你就是在盲调。第四个心得是别迷信大模型。有些任务用规则引擎、关键词匹配、传统机器学习反而更稳更便宜。AI 是工具箱里的一把锤子不是所有问题都是钉子。我见过把简单的字段抽取硬做成大模型调用的延迟高、成本高、还不稳定纯属自找麻烦。5. 工程化落地与后续演进方向5.1 把 AI 模块做成可维护的工程组件AI 模块最怕写成一坨 Prompt 字符串散落在各个 Service 里。我的做法是把它当成一个独立的领域来设计Prompt 模板集中管理、模型配置外置、调用链路统一封装、异常统一处理、指标统一打点。具体来说我会建一个ai包里面分config、client、prompt、advisor、service几个子包。Prompt 用模板文件管理支持变量替换和版本控制。所有对模型的调用都走统一的AiService业务代码不直接碰ChatClient。这样换模型、调参数、加拦截都只改一处。测试方面AI 模块的单元测试要 mock 掉模型调用用固定的响应验证业务逻辑。集成测试则用真实模型跑一批固定问题对比输出是否符合预期。这种回归测试集是保证 Prompt 改动不引入退化的关键。5.2 从 RAG 到 Agent 的平滑过渡RAG 做熟了之后往 Agent 演进是自然的下一步。Agent 的核心是让模型自己决定调用哪些工具、按什么顺序调用。Spring AI 的 Tool Calling 就是入口。比如一个运维助手可以给它挂上查日志重启服务查监控三个工具用户问服务为什么挂了模型会自己决定先查监控、再查日志、最后给出结论。这种能力比单纯的问答强太多但复杂度也上了一个台阶——工具调用的循环控制、失败处理、权限校验每一个都要仔细设计。我的建议是先把单工具调用跑通再上多工具编排。一上来就搞复杂 Agent调试会让你怀疑人生。5.3 给 Java 开发者的几句实在话这条路线走下来我最大的体会是Java 开发者做 AI拼的不是算法功底而是工程判断力。什么时候该用大模型、什么时候该用规则、上下文怎么组织、成本怎么控制、失败怎么兜底这些才是决定一个 AI 应用能不能上生产的关键。工具链会变Spring AI 会升级模型会迭代但把不确定的能力包装成稳定的服务这个工程内核不会变。你过去写高并发、做容错、搞监控的那些经验在 AI 时代一样值钱甚至更值钱。最后分享一个小技巧给自己建一个Prompt 实验记录本每次调 Prompt 都记下改了什么、效果如何。坚持一个月你会对什么样的表达能触发什么样的输出形成肌肉记忆。这比看十篇教程都管用。
阅读完成 · 觉得有帮助?