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 工具调用、做多轮对话管理、做输出结果的校验和落库。
我见过不少团队一开始就想“全栈自研”,结果卡在模型微调上三个月出不来。后来调整策略,直接用现成的模型 API,Java 侧专注做业务编排和工程保障,两周就上线了第一个可用版本。这个取舍很关键:你的核心竞争力在业务理解和工程能力,不在模型本身。
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.x,JDK 至少 17。Spring AI 对 JDK 版本有要求,17 是底线,21 更好。构建工具用 Maven 或 Gradle 都行,我习惯 Maven,依赖声明直观。
核心依赖就一个:spring-ai-spring-boot-starter。但要注意版本对应关系,Spring AI 的版本迭代比较快,跟 Spring Boot 的版本有绑定。我一般去官方仓库看最新的兼容矩阵,别自己瞎猜版本号,容易出莫名其妙的 NoSuchMethodError。
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-spring-boot-starter</artifactId> <version>1.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())); List<Document> 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) { List<Document> 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 支持返回Flux<String>,配合 SSE 推给前端。但这里有个坑:流式输出时如果发生异常,HTTP 状态码已经发出去了,没法再改。所以错误处理要在流内部做,把错误信息也作为一种“输出”推给前端。
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> 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 应用开发里全都是加分项。模型会换、框架会变,但这些底层能力不会过时。