Java 圈子这两年有个挺有意思的现象面试造火箭的那批人突然开始集体焦虑 AI。倒不是怕被 AI 取代而是发现身边做 Python 的同事三行代码就能调个大模型跑通一个 RAG 问答自己还在那儿纠结 Maven 依赖冲突。更扎心的是公司新立项的智能客服项目技术选型会上有人直接甩出一句“用 Spring AI 吧Java 也能做”然后全场安静——因为没人真正跑通过。这篇东西就是写给这批人的。我做了十多年 Java 后端从 SSH 时代一路写到 Spring Boot 微服务去年开始系统性地把 AI 能力往 Java 技术栈里搬。踩过的坑不算少从“以为要转 Python”到“发现 Spring AI 真能打”中间隔了大概三个月的试错。下面这份路线图和工具链概览不是官方文档的复述是我自己走通之后回头整理的实战路径。适合有 Java 基础、想切入 AI 应用层开发但不知道从哪下手的同学也适合已经在用 Spring Boot 做业务、想把大模型能力集成进来的团队参考。1. 先搞清楚 Java 做 AI 到底做的是哪一层很多人一上来就问“Java 能不能训练大模型”这个问题本身就问偏了。训练大模型是另一条赛道涉及 CUDA、分布式训练框架、海量算力调度跟 Java 的关系确实不大。但 AI 应用开发不等于训练模型绝大多数企业真正需要的是把已有的大模型能力接进业务系统这一层 Java 不仅能做而且在工程化、稳定性、生态成熟度上还有明显优势。1.1 三层分工训练层、推理层、应用层把 AI 技术栈拆开看大致分三层。训练层是造模型的地方PyTorch、JAX 这些框架主导Java 基本不参与。推理层是模型跑起来提供接口的地方可能是本地部署的推理服务也可能是云厂商的 API这一层 Java 通过 HTTP 或 SDK 调用就行。应用层才是 Java 的主战场——把模型能力编排进业务流程做 RAG 检索增强、做 Agent 工具调用、做多轮对话管理、做输出结果的校验和落库。我见过不少团队一开始就想“全栈自研”结果卡在模型微调上三个月出不来。后来调整策略直接用现成的模型 APIJava 侧专注做业务编排和工程保障两周就上线了第一个可用版本。这个取舍很关键你的核心竞争力在业务理解和工程能力不在模型本身。1.2 为什么 Spring AI 成了 Java 侧的事实入口Spring 生态的统治力在这里体现得很明显。Spring AI 做的事情本质上是把大模型调用抽象成了一套跟 JdbcTemplate 风格一致的 API——统一的 ChatClient、统一的 EmbeddingClient、统一的向量库接口。你换模型供应商业务代码基本不用动改配置就行。这对 Java 开发者来说太友好了学习成本几乎为零。我实测下来Spring AI 目前对主流模型平台的支持已经比较完整OpenAI 风格的接口、国内几家大厂的模型服务都能接。更关键的是它跟 Spring Boot 的自动装配无缝集成你原来怎么写 Service现在还怎么写只是多注入一个 ChatClient。这种“无感接入”的体验是 Python 侧那些零散库给不了的。1.3 别被“AI 无禁词聊天”这类热词带偏搜索热词里混进来一些奇怪的东西比如“ai无禁词聊天网页版不用登录”“无限制无审核生成式ai”之类的。这些跟正经的 AI 应用开发没有半点关系纯粹是流量词。做企业级 AI 集成你要关心的是内容安全过滤、输出合规校验、调用链路可观测而不是怎么绕过限制。方向搞错了技术选型就会跟着歪。2. 从 Java 基础到 AI 应用的最小知识补齐有 Java 基础的同学切入 AI不需要从头学一门新语言但有几个知识盲区必须补上。我按优先级排了个顺序都是实际写代码时会卡住的地方。2.1 向量与相似度RAG 的地基RAG 是当前 Java 做 AI 最主流的场景而 RAG 的核心是向量检索。你得理解什么是 Embedding——把一段文本映射成一个高维浮点数数组语义相近的文本在向量空间里距离更近。然后要理解余弦相似度怎么算为什么它比欧氏距离更适合文本匹配。这些概念听起来数学味很重但实际写代码时你不需要手推公式。Spring AI 的 VectorStore 接口已经封装好了你调similaritySearch方法传查询文本和 topK 就行。但理解原理能帮你排查问题——比如为什么检索出来的结果不相关可能是 Embedding 模型选得不对也可能是分块策略有问题。2.2 提示词工程不是玄学是结构化输入提示词写得好不好直接决定输出质量。我刚开始也是随便写几句后来发现同样的模型结构化提示词能把准确率拉高一大截。核心就几条角色设定要明确、任务描述要具体、输出格式要约束、few-shot 示例要给够。举个实际例子做合同信息抽取时我一开始的提示词是“从下面文本中提取甲方乙方和金额”结果模型经常把甲乙方搞反。后来改成“你是一名法务助理请从合同文本中提取以下字段甲方名称、乙方名称、合同金额单位元。如果某字段未出现返回空字符串。输出为 JSON 格式不要额外解释。”准确率立刻上来了。提示词的本质是给模型划定输出空间空间越小结果越稳。2.3 函数调用与 Agent让模型能干活大模型本身只能生成文本但通过函数调用Function Calling你可以让模型决定什么时候调用你预先注册好的 Java 方法。比如用户问“帮我查一下订单 12345 的状态”模型识别出需要调用订单查询接口返回一个结构化的调用请求你的 Java 代码执行后把结果再喂回模型模型生成自然语言回复。Spring AI 对函数调用的支持是通过Bean注册Function实现的跟 Spring 的依赖注入风格一致。这块是 Agent 的基础理解了函数调用再去看 ReAct 模式、多步推理这些概念就顺了。3. 工具链选型每个环节用什么为什么工具链这块我按开发流程拆成几段来说每段给出我的实际选择和理由。不是唯一答案但都是跑通过的组合。3.1 项目骨架与依赖管理基础还是 Spring Boot 3.xJDK 至少 17。Spring AI 对 JDK 版本有要求17 是底线21 更好。构建工具用 Maven 或 Gradle 都行我习惯 Maven依赖声明直观。核心依赖就一个spring-ai-spring-boot-starter。但要注意版本对应关系Spring AI 的版本迭代比较快跟 Spring Boot 的版本有绑定。我一般去官方仓库看最新的兼容矩阵别自己瞎猜版本号容易出莫名其妙的 NoSuchMethodError。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-spring-boot-starter/artifactId version1.0.0-M6/version /dependency提示Spring AI 在里程碑版本阶段 API 变动较频繁生产项目建议锁定版本升级前先跑通集成测试。3.2 模型接入层统一抽象还是直连Spring AI 提供的是统一抽象好处是换模型不改代码。但有些场景下你需要用到特定平台的独有参数统一抽象可能覆盖不到。我的做法是主流程走 Spring AI 的 ChatClient特殊需求通过自定义 Model 实现或直接走 HTTP 客户端兜底。配置上模型平台的 API Key 和 Base URL 放在application.yml里通过环境变量注入别硬编码。这块跟普通 Spring Boot 配置没区别。spring: ai: openai: api-key: ${AI_API_KEY} base-url: ${AI_BASE_URL} chat: options: model: gpt-4o-mini temperature: 0.73.3 向量库从内存到生产开发阶段用SimpleVectorStore就够了数据放内存里重启就没了但调试方便。生产环境得换真正的向量数据库。我评估过几个Milvus 功能全但运维重Redis 的向量检索能力够用且团队熟悉PgVector 适合已经在用 PostgreSQL 的团队。选型逻辑很简单看你现有的基础设施。如果已经在用 Redis直接上 Redis Stack 的向量功能省一套运维。如果数据量大、检索要求高再考虑 Milvus 这类专用向量库。别为了用新技术而引入不必要的复杂度。3.4 文档处理与分块RAG 的上游是文档处理。PDF、Word、Markdown 都要能读进来然后切分成合适大小的块。Spring AI 提供了DocumentReader和TextSplitter的抽象但实际用的时候你会发现分块策略比读取本身重要得多。我踩过的坑按固定字符数切分结果把一句话从中间截断了检索出来的片段语义不完整。后来改成按段落切分再对超长段落做二次切分效果明显好转。分块大小一般 500 到 1000 字符比较合适重叠部分留 10% 到 20%保证上下文连贯。4. 一条能跑通的 RAG 最小闭环光说概念没意思我把一个最小可用的 RAG 流程完整走一遍。这个例子是给内部知识库做问答代码量不大但覆盖了核心环节。4.1 文档入库从文件到向量第一步是把文档读进来、切块、向量化、存库。Spring AI 的VectorStore.add()方法接收Document列表内部会自动调 Embedding 模型做向量化。RestController public class IngestController { private final VectorStore vectorStore; public IngestController(VectorStore vectorStore) { this.vectorStore vectorStore; } PostMapping(/ingest) public String ingest(RequestParam(file) MultipartFile file) throws IOException { String content new String(file.getBytes(), StandardCharsets.UTF_8); Document doc new Document(content, Map.of(source, file.getOriginalFilename())); ListDocument chunks new TokenTextSplitter().split(doc); vectorStore.add(chunks); return 已入库 chunks.size() 个片段; } }这里TokenTextSplitter是按 token 数切分的比按字符数切分更合理因为不同模型的 token 边界不一样。实际项目中我会根据文档类型选不同的 Splitter技术文档按标题层级切聊天记录按对话轮次切。4.2 检索与生成问答主流程用户提问时先把问题向量化去向量库检索最相似的几个片段拼进提示词再调模型生成回答。RestController public class QaController { private final ChatClient chatClient; private final VectorStore vectorStore; public QaController(ChatClient.Builder builder, VectorStore vectorStore) { this.chatClient builder.build(); this.vectorStore vectorStore; } GetMapping(/ask) public String ask(RequestParam String question) { ListDocument docs vectorStore.similaritySearch( SearchRequest.query(question).withTopK(5) ); String context docs.stream() .map(Document::getContent) .collect(Collectors.joining(\n---\n)); return chatClient.prompt() .system(你是一个知识库助手请根据提供的上下文回答问题。如果上下文中没有相关信息直接说不知道不要编造。) .user(u - u.text(上下文\n{context}\n\n问题{question}) .param(context, context) .param(question, question)) .call() .content(); } }这段代码跑通你就有了一个能用的 RAG 问答。但注意 system prompt 里那句“不知道就说不知道”这是防止模型幻觉的关键。我测试过不加这句的情况模型会一本正经地编造答案非常危险。4.3 效果调优检索不准怎么办跑通之后大概率会遇到检索不准的问题。排查顺序我总结成三步先看分块是否合理再看 Embedding 模型是否匹配语种最后看 topK 和相似度阈值设置。中文场景下Embedding 模型的选择很关键。有些模型对中文语义的捕捉明显更好换模型之后检索准确率能差出百分之二三十。这个没有捷径得实际测。我一般准备一组标准问答对换模型后跑一遍看命中率。5. 那些文档里不会写的踩坑记录这部分是我觉得最有价值的内容都是实际调试时撞出来的。5.1 超时与重试模型调用不是本地方法模型 API 调用是网络请求延迟波动很大。我遇到过高峰期单次调用超过 30 秒的情况如果不设超时线程池很快就被打满。Spring AI 的底层 HTTP 客户端可以配置超时和重试但重试要小心——对于生成类请求重试可能导致重复计费。我的做法是连接超时设 5 秒读取超时设 60 秒重试只针对连接失败不针对读取超时。同时用 Resilience4j 做熔断连续失败达到阈值就快速失败给上游返回降级结果。5.2 Token 消耗看不见的成本黑洞RAG 场景下每次问答都要把检索到的上下文拼进提示词token 消耗比想象中大得多。我算过一笔账topK 设 5每个片段 500 token光上下文就 2500 token加上系统提示词和用户问题单次请求轻松超过 3000 token。如果日活一千一天就是三百万 token。优化手段有几个压缩上下文只保留最相关的片段用更小的模型做初步筛选对高频问题做缓存。缓存这块特别有效很多内部知识库的问题重复率很高缓存命中率能到 40% 以上。5.3 流式输出与前端配合聊天场景需要流式输出不然用户等十几秒才看到回复体验很差。Spring AI 支持返回FluxString配合 SSE 推给前端。但这里有个坑流式输出时如果发生异常HTTP 状态码已经发出去了没法再改。所以错误处理要在流内部做把错误信息也作为一种“输出”推给前端。GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content() .onErrorResume(e - Flux.just([错误] e.getMessage())); }5.4 多轮对话的上下文管理多轮对话不是简单地把历史消息全塞进去那样 token 会爆炸。我的策略是滑动窗口加摘要保留最近 N 轮完整对话更早的对话用模型生成摘要摘要作为系统提示的一部分。这样既保留了长期记忆又控制了 token 消耗。Spring AI 的ChatMemory接口提供了基础能力但默认实现比较简单。生产环境我建议自己实现把对话历史存 Redis按会话 ID 隔离设置合理的过期时间。6. 从能跑到好用工程化补课Demo 跑通只是起点要上线还得补不少工程化的东西。6.1 可观测性调用链路要能追踪AI 应用的调试比普通接口麻烦因为输出不确定。我接入了 Micrometer 做指标采集记录每次调用的耗时、token 消耗、模型名称。再配合日志把完整的提示词和响应打出来出问题时能复现。关键指标我盯这几个P99 延迟、token 消耗趋势、检索命中率、用户反馈率。特别是用户反馈做个简单的点赞点踩积累一段时间就能看出哪些问题回答得不好针对性优化。6.2 内容安全输出必须过一遍企业场景下模型输出不能直接返回给用户。我加了一层输出校验用规则引擎过滤敏感词再用一个小模型做意图分类判断输出是否偏离主题。这层校验会增加延迟但安全底线不能破。输入侧也要过滤防止提示词注入。用户输入里如果包含“忽略之前的指令”这类模式直接拦截。这块没有银弹只能持续对抗。6.3 测试策略断言不能写死AI 应用的测试跟传统接口测试不一样输出是自然语言没法用 assertEquals。我的做法是分层测试单元测试 mock 掉模型调用验证业务逻辑集成测试用真实模型但只断言关键信息是否包含比如问“合同金额是多少”断言回答里包含正确的数字。再往上做评估集测试准备一批标准问答对定期跑一遍看准确率变化。模型升级、提示词调整之后必须跑评估集防止效果回退。7. 关于 Spring AI 和 LangChain4j 的选择这个问题被问得最多。我的看法是如果你团队是 Spring 技术栈无脑选 Spring AI。集成成本最低学习曲线最平缓社区活跃度也够。LangChain4j 的功能更丰富一些特别是在 Agent 编排和工具调用方面但它的 API 风格跟 Spring 不太一致团队接受度可能是个问题。实际项目中我也见过混用的主流程用 Spring AI某些复杂 Agent 场景用 LangChain4j 单独实现一个服务。技术选型没有绝对的对错关键是团队能 hold 住。别为了追新而引入不熟悉的技术栈维护成本会教你做人。8. 学习路径三个月从入门到能交付最后给一个我验证过的学习节奏按周拆解。第一个月打基础第一周把 Spring AI 的官方示例跑通理解 ChatClient 和 VectorStore 的基本用法第二周补向量检索和提示词工程的知识动手做一个简单的文档问答第三周学习函数调用做一个能查数据库的 Agent第四周把 RAG 流程完整走一遍加上文档入库和检索优化。第二个月做项目找一个真实的业务场景比如内部知识库或者客服辅助完整实现一遍。重点不是功能多复杂而是把工程化的东西都加上——超时重试、缓存、可观测性、内容安全。第三个月深入研究多轮对话管理、Agent 编排、评估体系。这时候你已经能独立负责 AI 应用模块了。这个节奏的前提是每天能投入两小时以上。如果只能碎片时间学周期拉长到半年也正常。关键是别停在看文档的阶段一定要动手写。我见过太多人收藏了一堆教程最后连一个完整的 RAG 都没跑通。我在实际带团队的过程中发现Java 开发者切入 AI 最大的障碍不是技术难度而是心理上的“这不是我的领域”。一旦跨过那道坎你会发现之前积累的工程能力——依赖管理、异常处理、并发控制、可观测性——在 AI 应用开发里全都是加分项。模型会换、框架会变但这些底层能力不会过时。