尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java开发者AI入门实战:Spring AI与RAG工程化落地指南

Java开发者AI入门实战:Spring AI与RAG工程化落地指南 1. Java 开发者切入 AI 的真实路径与全局思路1.1 为什么 Java 开发者不需要从零学 Python我做了十多年 Java 后端这两年身边问得最多的问题就是“要不要转 Python 才能搞 AI”。说实话这个判断本身就是个误区。AI 工程化落地从来不是只有“训练模型”这一条路真实的企业场景里80% 的 AI 需求是推理调用、流程编排、RAG 检索增强、Agent 工具链集成这些活儿恰恰是 Java 的主场。你想想一个电商系统要做智能客服核心难点是模型训练吗不是。难点在于怎么把商品库、订单库、售后工单这些已有 Java 服务跟大模型串起来怎么保证并发下的响应延迟怎么做权限隔离和审计日志。这些全是 Java 工程师每天在干的事。模型本身用现成的 API 或者本地部署的开源模型就够了你不需要会调参你需要会调接口。所以我的结论很直接Java 开发者入门 AI路线应该是“先会用再懂原理最后按需深入”而不是先去啃线性代数和反向传播。这条路线的好处是反馈快、能落地、跟现有职业积累不冲突。你白天写业务代码晚上花一小时把 Spring AI 接进项目里跑通一个问答接口这种成就感比看三周数学课强得多。1.2 三条主流路线的取舍分析市面上 Java 接 AI 大致有三条路我按上手难度和适用场景给你捋一遍。第一条是直接调大模型 HTTP API。用HttpClient或者OkHttp发 POST 请求解析 JSON 返回。优点是零依赖、可控性强缺点是每个厂商的请求格式、鉴权方式、流式返回协议都不一样你要写一堆重复的适配代码。适合做技术验证或者极简场景。第二条是用 Spring AI 这类框架做统一抽象。Spring AI 把 Chat、Embedding、VectorStore、Tool Calling 这些概念抽象成了统一的接口底层可以切换不同的模型提供方。你写一套代码改个配置就能从一家模型换到另一家。这是目前 Java 生态里最省心的方案也是我推荐新手优先掌握的。第三条是本地部署模型 Java 调用。比如用 Ollama 在本地跑一个开源模型Java 通过 HTTP 或者专用 SDK 调用。好处是数据不出内网、没有调用费用坏处是对机器配置有要求而且模型能力通常不如云端旗舰模型。适合对数据隐私敏感的场景。路线上手难度灵活性适用场景推荐指数直接调 HTTP API低高技术验证、简单集成三颗星Spring AI 框架中中高企业级应用、多模型切换五颗星本地部署模型中高中数据敏感、离线环境四颗星我个人的建议是先用 Spring AI 跑通一个最小闭环再回头理解底层 HTTP 调用最后根据项目需要决定要不要本地部署。这个顺序能让你最快建立全局认知不至于一上来就陷进细节里出不来。1.3 学习路线图的分阶段拆解我把整个入门过程分成四个阶段每个阶段大概两到四周具体看你能投入的时间。第一阶段建立体感。目标是不看教程也能写出一个能对话的接口。核心任务是理解 ChatClient 的基本用法、消息角色System/User/Assistant的区别、以及流式返回怎么处理。这个阶段不要碰 RAG不要碰 Agent就老老实实把一次问答跑通。第二阶段掌握 RAG。这是企业落地最常用的模式。核心是理解 Embedding 模型把文本转成向量、VectorStore 做相似度检索、然后把检索结果拼进 Prompt 里。你需要动手搭一个本地向量库把一批文档灌进去验证问答效果。第三阶段工具调用与 Agent。让模型能调用你写的 Java 方法比如查数据库、调天气接口、发邮件。这是从“聊天机器人”到“能干活的助手”的关键一步。Spring AI 的 Tool Calling 机制就是干这个的。第四阶段工程化与调优。包括 Prompt 模板管理、多轮对话记忆、超时重试、成本控制、可观测性。这些是让 AI 功能真正上生产必须解决的问题也是区分玩具项目和商业项目的分水岭。提示每个阶段都要有可运行的代码产出不要只看不写。AI 这东西看十遍不如跑一遍。2. Spring AI 核心概念与最小可运行示例2.1 环境准备与依赖引入先说版本。Spring AI 目前迭代很快我写这篇的时候稳定版是 1.0.x 系列。你新建项目的时候Spring Initializr 里已经能直接勾选 Spring AI 相关的 starter 了。如果手动加依赖Maven 里大概是这样dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0/version /dependencyJDK 版本建议 17 以上Spring Boot 用 3.2 以上。这两个是硬性要求别想着用 JDK 8 硬扛Spring AI 底层用了不少新特性版本对不上会报一堆莫名其妙的错。配置文件里至少要填两个东西API Key 和 base URL。如果你用的是兼容 OpenAI 协议的国内模型服务base URL 改成对应的地址就行。这里我不具体点名哪家你根据自己手头的资源选。spring: ai: openai: api-key: ${AI_API_KEY} base-url: https://your-endpoint/v1 chat: options: model: gpt-4o-mini temperature: 0.7temperature这个参数值得说一下。它控制输出的随机性范围一般是 0 到 2。做事实性问答的时候调到 0.2 左右让回答稳定做创意文案的时候可以调到 1.0 以上。我见过有人做客服机器人用默认值 1.0结果同一个问题每次回答都不一样被业务方投诉“不稳定”。这个坑你提前避开。2.2 ChatClient 的三种调用姿势Spring AI 里最核心的入口就是ChatClient。它有三种用法我按从简到繁排一下。最简单的是一次性调用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(); } }这段代码跑起来浏览器访问/chat?message你好就能看到模型回复。注意ChatClient.Builder是自动注入的你不需要自己 new。第二种是带 System Prompt 的调用。System Prompt 用来设定模型的角色和行为边界比如“你是一个只回答 Java 相关问题的助手其他问题一律拒绝”。这个在业务里非常有用能大幅减少模型胡说八道。String answer chatClient.prompt() .system(你是一个资深的 Java 技术顾问回答要简洁给出代码示例。) .user(解释一下 Java 的 AQS 原理) .call() .content();第三种是流式返回。做聊天界面必须用流式否则用户要盯着空白屏幕等好几秒。Spring AI 支持返回FluxString配合 SSE 推给前端。GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); }注意流式接口的produces必须写成TEXT_EVENT_STREAM_VALUE否则前端收不到分块数据。这个细节我第一次做的时候卡了半小时。2.3 Prompt 模板与参数化设计硬编码 Prompt 字符串在 demo 里没问题但生产环境必须用模板。原因很简单业务方要改话术你不能每次都改代码重新发版。Spring AI 提供了PromptTemplate用法跟字符串格式化类似但更安全能防止用户输入里的特殊字符破坏模板结构。PromptTemplate template new PromptTemplate( 你是一个{role}请用{style}的风格回答以下问题 问题{question} ); Prompt prompt template.create(Map.of( role, Java 面试官, style, 严谨, question, 说说 HashMap 的扩容机制 ));模板文件建议放在resources/prompts/目录下用.st后缀Spring AI 能自动加载。这样做的好处是 Prompt 和代码分离产品经理也能参与调整。我踩过的一个坑是模板里的占位符如果用户输入了{}这种字符会跟模板语法冲突。解决办法是在拼接前对用户输入做转义或者干脆把用户输入放在模板的最后减少冲突概率。2.4 多轮对话与记忆管理单次问答很简单但真实场景往往是多轮对话。用户说“帮我写个排序”你写完用户又说“改成倒序”这时候模型必须记得上一轮的内容。Spring AI 提供了ChatMemory接口内置了InMemoryChatMemory和基于 JDBC、Redis 的实现。开发阶段用内存版就够了生产环境建议用 Redis否则服务重启对话就丢了。ChatMemory memory new InMemoryChatMemory(); ChatClient client ChatClient.builder(chatModel) .defaultAdvisors(new MessageChatMemoryAdvisor(memory)) .build();这里有个关键参数叫maxMessages控制保留多少轮历史。设太大 token 消耗高设太小模型会“失忆”。我的经验值是保留最近 10 轮超过的部分做摘要压缩。摘要压缩的意思是让模型把前面的对话总结成一段话既保留了关键信息又省了 token。实操心得多轮对话里最容易出问题的是 System Prompt 被历史消息淹没。解决办法是每轮都把 System Prompt 重新放在最前面Spring AI 的 Advisor 机制会自动处理这个顺序。3. RAG 检索增强的完整落地流程3.1 RAG 到底解决了什么问题模型本身的知识是有截止日期的而且它不知道你公司内部的文档。你问它“我们产品的退款政策是什么”它要么瞎编要么说不知道。RAG 的思路很朴素先去你的知识库里搜相关内容把搜到的内容塞进 Prompt 里让模型基于这些内容回答。打个比方模型是一个博学但没看过你公司文件的顾问。RAG 就是每次提问前你先从档案室找出相关文件递给他他看完再回答。这样答案既准确又有依据。RAG 的核心流程分三步索引、检索、生成。索引是把文档切块、转向量、存进向量库检索是把用户问题也转向量在库里找最相似的几块生成是把检索结果和问题一起发给模型。3.2 文档切分与向量化实操文档切分看着简单其实最影响效果。切太大检索出来的内容冗余浪费 token切太小语义不完整模型看不懂。我的经验参数是每块 500 到 800 个字符块之间重叠 100 到 200 个字符。重叠是为了防止一句话被从中间切断导致两边都读不通。Spring AI 提供了TokenTextSplitter按 token 数切分比按字符数更准。TokenTextSplitter splitter new TokenTextSplitter(800, 100, 5, 10000, true); ListDocument documents splitter.apply(List.of(new Document(rawText)));向量化就是把文本块转成一串浮点数。这个过程用的是 Embedding 模型跟聊天模型是两回事。Embedding 模型专门负责把文本映射到高维空间语义相近的文本在空间里距离也近。EmbeddingModel embeddingModel ...; VectorStore vectorStore new SimpleVectorStore(embeddingModel); vectorStore.add(documents);开发阶段用SimpleVectorStore就行它存在内存里。生产环境要换成 Redis、PGVector、Milvus 这些持久化方案。选型的时候重点看两点检索延迟和过滤能力。过滤能力指的是能不能按元数据筛选比如只搜某个部门、某个时间段的文档。3.3 相似度检索与 Prompt 拼装检索的时候用户问题先被转向量然后在向量库里找距离最近的 K 个块。K 一般设 3 到 5太多会稀释重点太少可能漏掉关键信息。SearchRequest request SearchRequest.query(userQuestion).withTopK(4); ListDocument docs vectorStore.similaritySearch(request); String context docs.stream() .map(Document::getContent) .collect(Collectors.joining(\n---\n)); String prompt 请基于以下资料回答问题如果资料中没有相关信息请明确说不知道。 资料 %s 问题%s .formatted(context, userQuestion);这里有个细节一定要在 Prompt 里明确告诉模型“资料里没有就说不知道”。否则模型会强行用自己训练时的知识编答案RAG 就白做了。3.4 效果调优的三个关键抓手RAG 做出来容易做好难。我总结了三个最有效的调优点。第一是切分策略。技术文档按标题层级切合同按条款切聊天记录按时间窗口切。不要一刀切用固定长度。Spring AI 支持自定义DocumentSplitter你可以根据文档结构写自己的切分逻辑。第二是检索后的重排序。初次检索出来的 TopK 不一定按相关性排好序可以用一个重排序模型再过一遍。这个模型比 Embedding 模型小但专门判断“问题和文档的相关程度”效果提升明显。第三是混合检索。纯向量检索对关键词不敏感比如搜“AQS”可能搜不到包含“AbstractQueuedSynchronizer”的文档。解决办法是向量检索和关键词检索各跑一遍结果合并去重。这个在 Spring AI 里需要自己写一点胶水代码但值得投入。调优手段实现难度效果提升适用阶段优化切分策略低中初期重排序中高中期混合检索中高高成熟期4. Tool Calling 与 Agent 的工程化实践4.1 让模型调用 Java 方法的原理Tool Calling 的本质是你把几个 Java 方法的描述告诉模型模型判断用户问题需要调哪个方法返回一个结构化的调用请求你的代码执行完再把结果喂回给模型模型生成最终回答。整个过程模型不直接执行你的代码它只是“说”要调哪个方法、传什么参数。真正的执行权在你手里这就保证了安全可控。Spring AI 里定义一个工具非常简单用Tool注解就行Component public class WeatherTools { Tool(description 查询指定城市的当前天气) public String getWeather(ToolParam(description 城市名称) String city) { // 实际调用天气 API return city 今天晴25度; } }description写得好不好直接决定模型能不能正确调用。我见过有人写“查询天气”模型不知道要传什么参数调用就失败了。要写成“查询指定城市的当前天气参数是城市中文名”。4.2 工具注册与调用链路追踪定义好工具后注册到 ChatClient 上ChatClient client ChatClient.builder(chatModel) .defaultTools(new WeatherTools()) .build();然后正常提问“北京今天天气怎么样”模型会自动触发工具调用。你可以在日志里看到完整的调用链路模型返回工具调用请求、你的代码执行、结果回传、模型生成最终回答。注意工具方法的返回值不要太大。如果返回一个几万字的 JSONtoken 会爆掉。建议在工具方法内部做裁剪只返回模型需要的关键字段。4.3 Agent 循环与多步推理单个工具调用只是开始真正的 Agent 需要能连续调用多个工具、根据中间结果决定下一步。比如用户问“帮我查一下北京天气如果下雨就提醒我带伞”这需要先调天气工具再根据结果决定要不要调提醒工具。Spring AI 目前对多步 Agent 的支持还在演进中但你可以自己写循环把工具调用结果追加到消息历史里再次调用模型直到模型不再请求工具调用为止。ListMessage messages new ArrayList(); messages.add(new UserMessage(userInput)); while (true) { ChatResponse response chatModel.call(new Prompt(messages)); if (response.hasToolCalls()) { // 执行工具把结果加入 messages messages.add(response.getResult().getOutput()); messages.addAll(executeToolCalls(response)); } else { return response.getResult().getOutput().getContent(); } }这个循环要设最大轮次限制比如 10 轮防止模型陷入死循环烧钱。我见过一个没设限制的 demo模型在两个工具之间来回调用了几十次账单直接爆炸。4.4 安全边界与权限控制工具调用最大的风险是模型被诱导调用不该调的方法。比如你有一个“删除用户”的工具用户通过精心构造的 Prompt 让模型去调它。防护措施有三层。第一层是工具粒度控制危险操作不要做成工具或者做成需要二次确认的工具。第二层是参数校验工具方法内部要校验参数合法性不能信任模型传过来的值。第三层是审计日志每次工具调用都记录谁在什么时间调了什么出了问题能追溯。实操心得我习惯给每个工具方法加一个PreAuthorize注解复用 Spring Security 的权限体系。这样模型调用工具的时候权限校验跟普通接口一样生效不用另写一套。5. 常见问题排查与避坑经验5.1 连接超时与重试策略调模型 API 最常见的错误就是超时。模型推理本身就要几秒到几十秒网络再抖一下很容易触发默认超时。Spring Boot 里要显式配置超时时间spring: ai: openai: chat: options: timeout: 60s重试策略建议用指数退避第一次等 1 秒第二次 2 秒第三次 4 秒。但要注意流式请求不要自动重试因为流已经推给前端了重试会导致内容重复。5.2 Token 超限与上下文裁剪每个模型都有上下文窗口限制比如 128K token。你的 Prompt 加上历史对话加上检索结果很容易超。解决办法是分层裁剪System Prompt 永远保留最近的对话优先保留检索结果按相关性截断最老的历史对话做摘要。Spring AI 的ChatMemory可以配置maxMessages但更精细的控制需要自己实现。我一般会在调用前估算 token 数用一个简单的规则中文一个字约 1.5 token英文一个词约 1.3 token。估算值超过窗口的 80% 就触发裁剪。5.3 模型输出格式不稳定的处理让模型返回 JSON 的时候它有时候会加一堆解释文字有时候 JSON 格式还写错了。这在需要程序解析结果的场景里很致命。三个应对手段。第一是在 Prompt 里给示例明确说“只返回 JSON不要任何其他文字”。第二是用模型的 JSON 模式很多模型提供方支持强制 JSON 输出。第三是解析失败时做修复用正则提取 JSON 部分或者再调一次模型让它修正格式。try { return objectMapper.readValue(content, Result.class); } catch (JsonProcessingException e) { // 尝试提取 JSON 片段 String json extractJson(content); return objectMapper.readValue(json, Result.class); }5.4 成本控制与缓存策略模型调用是按 token 计费的量大了很烧钱。控制成本最有效的手段是缓存。完全相同的请求直接返回缓存结果这个用 Redis 做 key-value 缓存就行。相似但不相同的请求可以用语义缓存把请求转向量在缓存库里找相似度超过阈值的直接返回。语义缓存能命中更多但实现复杂一些。另外简单任务用小模型复杂任务用大模型。比如意图识别、文本分类这种小模型完全够用成本只有大模型的几十分之一。Spring AI 支持在运行时动态切换模型你可以根据任务类型路由到不同的模型。问题类型排查方向快速解决超时网络、模型负载加大 timeout加重试Token 超限上下文长度裁剪历史截断检索结果格式错误Prompt 不明确加示例用 JSON 模式成本过高调用量、模型选择加缓存小模型分流回答不准检索质量、Prompt调切分加重排序5.5 日志与可观测性建设AI 功能上生产没有可观测性就是裸奔。至少要记录四样东西请求内容、响应内容、token 消耗、耗时。Spring AI 提供了ChatClient的 Advisor 机制你可以写一个自定义 Advisor 在请求前后打日志。public class LoggingAdvisor implements CallAroundAdvisor { Override public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) { long start System.currentTimeMillis(); AdvisedResponse response chain.nextAroundCall(request); log.info(耗时{}ms, 请求{}, 响应{}, System.currentTimeMillis() - start, request.userText(), response.response().getResult().getOutput().getContent()); return response; } }这些日志积累起来你就能分析出哪些问题问得最多、哪些回答质量差、成本花在哪里。没有这些数据优化就是拍脑袋。6. 从能跑到好用生产化改造要点6.1 异步化与并发处理模型调用是 IO 密集型操作用同步阻塞的方式会浪费线程。Spring AI 底层基于 Reactor天然支持异步。你的 Controller 返回Mono或FluxTomcat 线程就能立刻释放去处理其他请求。GetMapping(/chat/async) public MonoString chatAsync(RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); }但要注意异步链路里的异常处理跟同步不一样。要用onErrorResume而不是 try-catch否则异常会静默丢失。6.2 限流与降级方案模型服务不是无限的并发太高会被限流。你需要在应用层做限流用 Resilience4j 或者 Sentinel 都行。限流之后要有降级方案比如返回缓存的通用回答或者提示用户“当前咨询人数较多请稍后再试”。降级不是失败是保护系统不被打垮。我见过一个没做限流的系统高峰期把模型配额打满了导致所有用户都收到错误还不如一开始就限流让一部分用户正常使用。6.3 多模型路由与故障转移不要把所有鸡蛋放在一个篮子里。生产环境建议配置至少两个模型提供方主模型挂了自动切到备用模型。Spring AI 的ChatModel接口是统一的你可以写一个路由实现根据配置或者健康检查结果决定用哪个。public class RoutingChatModel implements ChatModel { private final ChatModel primary; private final ChatModel fallback; Override public ChatResponse call(Prompt prompt) { try { return primary.call(prompt); } catch (Exception e) { log.warn(主模型调用失败切换备用模型, e); return fallback.call(prompt); } } }这个模式在真实生产里救过我好几次。某次主模型服务商突发故障备用模型自动接管业务方完全无感知。6.4 版本升级与兼容性管理Spring AI 还在快速迭代API 变动比较频繁。我的建议是锁定版本不要用 SNAPSHOT升级前先在测试环境跑全量回归。升级的时候重点关注三块依赖坐标有没有变、配置项有没有重命名、核心接口签名有没有调整。Spring AI 的官方文档有迁移指南每次大版本升级前花半小时读一遍能省掉很多调试时间。另外把模型相关的配置全部外置到配置中心不要硬编码在代码里。这样换模型、调参数不用重新发版运维效率高很多。最后分享一个我自己的习惯每次接入一个新的 AI 能力我都会先写一个最小的验证项目跑通之后再往主项目里集成。这样出了问题能快速定位是框架的问题还是业务代码的问题排查效率高很多。AI 这块变化太快保持一个随时能验证新东西的沙盒环境比什么都重要。
返回列表