最近总有人在 B 站刷到“Spring AI 2.0 手写 Claude Code 代码生成助手”这类视频标题点进去却发现要么只给了概念没有完整实现要么代码片段东拼西凑没法直接跑。尤其是 Java 后端同学大多在 Spring Boot 项目里已经积累了很成熟的工程体系不缺写代码的能力缺的是一套能把大模型 Agent 真正落进后端服务的思路。所以这篇文章不聊虚的直接围绕一个核心目标展开用 Spring AI 在 Java 后端实现一个类似 Claude Code 的代码生成助手 Agent它可以读取项目文件理解用户需求调用工具生成代码并写入磁盘。整个实现过程会覆盖环境搭建、核心概念、代码编写、运行验证、常见踩坑和工程化建议新手能跟着一步步搭起来有经验的开发者也可以直接复用其中的设计思路。考虑到 Spring AI 版本迭代较快文章示例会以可稳定运行的核心 API 为主同时说明 2.0 演进方向让你在新旧版本之间切换时不至于迷茫。1. 从 Claude Code 到 Java Agent为什么后端开发也需要自己的 AI Agent1.1 Claude Code 到底解决什么问题Claude Code 是 Anthropic 推出的一款终端 AI 编程助手用户可以在命令行里用自然语言描述修改需求它自动读取项目源码、定位问题文件、生成补丁并执行命令。核心体验是你不用在 IDE、文档和终端之间反复横跳只要把意图说清楚AI 会代替你完成上下文检索、方案设计和代码修改。这个产品带火了一个概念Agent智能体。传统大模型 Prompt 是“你问我答”而 Agent 是“你指派任务它调用工具、观察结果、继续决策直到任务完成”。对 Java 后端开发者来说Claude Code 这类工具很好用但有一个天然限制它是独立 CLI 工具很难深度嵌进你自己的业务系统。比如电商中台想让商品运营用自然语言生成数据报表代码或者 DevOps 平台想接入一个能自动补全流水线脚本的 AI 助手这些场景都需要在服务端实现 Agent而不是让用户去装一个终端工具。1.2 为什么选择 Spring AI 而不是直接调模型 API直接使用大模型 HTTP API 也能写 Agent但你会遇到下面这些问题不同大模型厂商的 API 格式不同切换模型要改很多代码。Prompt 拼接、历史消息管理、结构化输出解析都要自己处理。工具调用Function Call的协议层很繁琐需要手动解析模型返回的工具参数。没有统一的重试、超时、流式输出抽象。Spring AI 的价值在于把这些问题都封装成了和 Spring 生态一致的编程模型。你可以像写RestTemplate一样使用ChatClient像写 Spring MVC 接口一样声明工具方法配置层面通过application.yml切换模型厂商。对于已经重度使用 Spring Boot 的团队接入成本非常低。1.3 这篇文章的实战目标为了让你学完就能用我把目标定成一个可运行的最小 Agent 项目用户输入一段自然语言例如“生成一个读取 CSV 文件并打印统计信息的 Java 工具类”。Agent 解析需求调用文件读写工具将生成的代码写入D:/agent-output/目录。后端返回结构化结果包含文件名、编程语言、代码内容、说明。全程在 Spring Boot 服务中完成不依赖任何外部 CLI 工具。这就构成了一个“Java 后端手写 Claude Code 式代码生成助手”的最小闭环。2. Spring AI 2.0 与 Agent 核心概念拆解2.1 Spring AI 是什么Spring AI 是 Spring 官方推出的 AI 应用开发框架目标是为 Java 生态提供一套标准化的 AI 集成方式。它支持主流的模型厂商例如 OpenAI、Azure OpenAI、Anthropic、DeepSeek、通义千问等也支持 Ollama 本地模型。它的核心模块包括模块作用ChatClient统一 Prompt 对话入口类似 RestClientChatModel大模型客户端抽象屏蔽厂商 API 差异Tool / Tool定义 Agent 可调用的本地工具方法Structured Output让模型按指定实体类结构返回 JSONDocument / VectorStore为 RAG 提供文档加载、分割、向量化简单理解Spring AI 就是 Java 后端接入大模型的“Spring 官方封装层”。2.2 关于 Spring AI 2.0 和版本兼容这里需要说一个客观事实Spring AI 的版本迭代非常快社区现在已经大量使用1.0.x稳定版本而 2.0 方向的讨论主要集中在 Agent 编排、图任务模型、多工具协同等能力上。比如 Spring AI Alibaba 的 Graph 项目就是在拥抱 Agent 工作流编排。这篇文章里我采用“以稳定 API 为准、兼容新版本思路”的策略示例代码中的ChatClient、Tool、Structured Output在 1.x 中完全可运行如果后续你切换到 2.0依赖坐标和少数 API 做迁移即可整体设计思想不变。2.3 Agent 的工作原理LLM 工具 循环Agent 可以拆成三个要素LLM 作为决策大脑负责理解用户意图、拆分任务、决定下一步调用哪个工具。工具作为行动手脚例如读文件、写文件、执行命令、调用第三方接口。循环作为执行框架模型调用工具后系统把工具结果返回给模型模型继续判断是否需要调用下一个工具直到认为任务完成。用一句话概括普通对话是“会说”Agent 是“会做”。2.4 工具调用与普通 Prompt 的区别普通 Prompt 只能输出文本模型不能影响外部世界。工具调用则允许模型在生成答案的过程中输出一个结构化的“工具调用请求”由 Spring AI 拦截这个请求在本地执行对应方法再把执行结果追加给模型。例如模型可能输出{ name: writeFile, arguments: { filePath: D:/agent-output/StatisticsTool.java, content: public class StatisticsTool { ... } } }Spring AI 会自动把它映射到你的Tool方法上完成真正的文件写入。3. 环境准备与项目初始化3.1 运行环境为了减少环境干扰本文的示例环境如下JDK 17 或更高版本。Maven 3.8。Spring Boot 3.x。任意一款主流 IDE推荐 IntelliJ IDEA。一个可访问的大模型 API Key。大模型部分示例以 DeepSeek 的 OpenAI 兼容接口为例因为接入成本低、国内网络环境友好。如果你使用的是通义千问、Kimi、OpenAI只需修改base-url、api-key、model三个配置项。Version 说明以下版本号只是示例请以你实际创建项目时从 Spring Initializr 获取到的版本为准不要盲目照搬。3.2 创建 Maven 项目结构我们先创建一个空白 Maven 项目包结构如下spring-ai-code-agent ├── pom.xml └── src/main/java/com/example/agent ├── SpringAiCodeAgentApplication.java ├── config/ChatModelConfig.java ├── controller/AgentController.java ├── service/CodeAgentService.java └── tool/CodeTools.java3.3 添加依赖在pom.xml中引入 Spring Web 和 Spring AI OpenAI Starterparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这里需要特别注意Spring AI 的依赖管理是通过独立 BOM 引入的不是直接由 Spring Boot 父 POM 管理因此必须配置spring-ai-bom。3.4 配置文件在src/main/resources/application.yml写入模型接入配置spring: application: name: spring-ai-code-agent ai: openai: base-url: https://api.deepseek.com api-key: ${AI_API_KEY} chat: options: model: deepseek-chat temperature: 0.1 agent: output-dir: D:/agent-output解释一下核心配置项base-url模型厂商的接口地址DeepSeek 的 OpenAI 兼容地址是https://api.deepseek.com。api-key通过环境变量AI_API_KEY注入不要把真实密钥写在代码里。temperature代码生成场景建议设置为较低值例如 0.1减少随机性。agent.output-dir自定义配置用于限制 Agent 写入文件的根目录。4. 核心模块实现从 ChatClient 到工具调用4.1 启动类启动类就是标准的 Spring Boot 入口package com.example.agent; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class SpringAiCodeAgentApplication { public static void main(String[] args) { SpringApplication.run(SpringAiCodeAgentApplication.class, args); } }4.2 配置 ChatClientSpring AI 自动装配了ChatClient.Builder我们没有必要手动创建模型客户端只需要在配置类里声明一个基础 ChatClient Bean并设置全局系统提示词package com.example.agent.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ChatModelConfig { Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultSystem( 你是一名资深的 Java 后端工程师擅长编写高质量、可维护的代码。 你的任务是根据用户需求生成代码并调用工具将代码写入指定目录。 代码风格要求规范、注释完整、包含必要的主方法或单元测试。 ) .build(); } }为什么要设置defaultSystem因为每次调用时如果用户请求里没有更具体的系统提示词Spring AI 会自动携带这段默认指令。这样工具调用、代码风格、输出格式这些约束只需要配置一次。4.3 编写 Agent 工具类工具方法是 Agent 能力的核心。这里我们实现两个最简单的工具读取文件和写入文件。package com.example.agent.tool; import org.springframework.beans.factory.annotation.Value; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; Component public class CodeTools { private final Path outputRoot; public CodeTools(Value(${agent.output-dir}) String outputDir) { this.outputRoot Path.of(outputDir); } Tool(description 读取指定文件的内容。入参为文件的绝对路径或相对于输出目录的路径。) public String readFile(String filePath) { try { Path path normalizePath(filePath); if (!Files.exists(path)) { return 文件不存在: path; } return Files.readString(path); } catch (IOException e) { return 读取文件失败: e.getMessage(); } } Tool(description 将内容写入指定文件。入参为相对输出目录的文件路径和完整的文件内容。) public String writeFile(String filePath, String content) { try { Path path normalizePath(filePath); Files.createDirectories(path.getParent()); Files.writeString(path, content); return 写入成功: path.toAbsolutePath(); } catch (IOException e) { return 写入文件失败: e.getMessage(); } } private Path normalizePath(String filePath) { Path raw Path.of(filePath); if (raw.isAbsolute()) { return outputRoot.resolve(raw.getFileName()); } return outputRoot.resolve(raw).normalize(); } }这里有两个重要的工程细节第一Tool注解是 Spring AI 识别工具方法的标记注解里的description会被作为工具描述发送给模型。描述越清晰模型越能正确选择工具。第二normalizePath做了一件事把用户传入的绝对路径强制收敛到输出目录下避免 Agent 把文件写到项目外的任意目录。这是 Agent 工具必须有的安全边界。4.4 实现 Agent 服务层服务层负责将 ChatClient、工具、结构化输出组合在一起。package com.example.agent.service; import com.example.agent.tool.CodeTools; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; Service public class CodeAgentService { private final ChatClient chatClient; public CodeAgentService(ChatClient chatClient) { this.chatClient chatClient; } public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .call() .content(); } public String runAgent(String userMessage) { return chatClient.prompt() .user(userMessage) .tools(new CodeTools()) .call() .content(); } }tools(new CodeTools())表示本次请求允许模型调用CodeTools中的工具方法。Spring AI 会自动完成协议转换模型所需的工具描述被发送给模型模型返回的调用请求被调度到对应 Java 方法。4.5 定义结构化输出实体在代码生成助手中我们不仅希望获得文本还希望得到“文件名、代码内容、说明”这类结构化字段方便前端直接渲染。Spring AI 的结构化输出支持把结果映射到 Java 实体。package com.example.agent.dto; public record CodeGenerationResult( String fileName, String language, String code, String description ) { }这里使用 Java 16 的record简洁且适合不可变 DTO。4.6 带结构化输出的 Agent 方法在CodeAgentService中增加一个方法要求模型始终按实体结构返回public CodeGenerationResult generateCodeWithStructure(String userMessage) { return chatClient.prompt() .user(userMessage) .tools(new CodeTools()) .user(请完成上述需求并在返回前调用 writeFile 工具写入最终文件。) .call() .entity(CodeGenerationResult.class); }entity(CodeGenerationResult.class)是关键Spring AI 会提示模型按照目标结构生成 JSON并自动反序列化成 record 实例。5. 完整实战让 Agent 自动生成并写入 Java 代码5.1 编写控制器增加一个 HTTP 接口方便我们用 Postman 或浏览器验证package com.example.agent.controller; import com.example.agent.dto.CodeGenerationResult; import com.example.agent.service.CodeAgentService; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/agent) public class AgentController { private final CodeAgentService codeAgentService; public AgentController(CodeAgentService codeAgentService) { this.codeAgentService codeAgentService; } GetMapping(/chat) public String chat(RequestParam String message) { return codeAgentService.chat(message); } PostMapping(/generate) public CodeGenerationResult generate(RequestBody String userMessage) { return codeAgentService.generateCodeWithStructure(userMessage); } }这里chat接口用于验证基础对话连通性generate接口用于验证完整的 Agent 工具调用链路。5.2 启动项目配置好AI_API_KEY环境变量后启动方法有两种在 IDEA 中直接运行SpringAiCodeAgentApplication。在命令行执行mvn spring-boot:run。如果启动成功控制台会出现 Spring Boot 的启动日志默认端口是 8080。5.3 测试基础对话先测试最简单的基础对话确认模型连接正常curl http://localhost:8080/api/agent/chat?message用Java写一个冒泡排序预期返回一段包含冒泡排序实现的文本内容。如果这个接口报错说明模型配置有问题应该先排查网络和 API Key。5.4 测试 Agent 工具调用与代码生成接下来测试完整的 Agent 能力请求内容是一个完整的代码生成任务curl -X POST http://localhost:8080/api/agent/generate \ -H Content-Type: text/plain \ -d 生成一个名为 CsvStatisticsTool 的 Java 工具类功能是读取指定 CSV 文件并输出总行数、列数、每列非空个数。请将文件写入 statistics 目录并包含 main 方法示例。预期结果分为两部分服务端日志中能看到模型调用writeFile工具的记录并返回 “写入成功: ...”。接口返回的 JSON 中包含fileName、language、code、description字段。D:/agent-output/statistics/CsvStatisticsTool.java目录下出现生成好的代码文件。5.5 完整代码示例这里给出一份工具类生成结果的简化版示例帮你理解模型应该生成的代码长什么样import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class CsvStatisticsTool { public static void main(String[] args) { String filePath data.csv; try { ListString[] rows readCsv(filePath); System.out.println(总行数: rows.size()); if (!rows.isEmpty()) { System.out.println(列数: rows.get(0).length); } } catch (IOException e) { e.printStackTrace(); } } public static ListString[] readCsv(String filePath) throws IOException { ListString[] rows new ArrayList(); try (BufferedReader reader new BufferedReader(new FileReader(filePath))) { String line; while ((line reader.readLine()) ! null) { rows.add(line.split(,)); } } return rows; } }实际生成结果会因模型、Prompt 详细程度不同而有差异这不重要关键是你要确认 Agent 是否真的把代码写到了磁盘。5.6 验证文件是否写入到配置的输出目录检查D:/agent-output/statistics/CsvStatisticsTool.java如果文件存在且内容完整说明你的 Java 后端 Agent 已经具备了 Claude Code 的核心能力之一根据自然语言生成代码并作用于文件系统。6. 常见问题与排查清单6.1 高频问题对照表问题现象常见原因解决思路请求模型超时日志出现did not respond in time网络不稳定或模型响应慢增加超时时间配置重试策略报错模型名称不存在例如deepseek-v4-pro is not a model配置的model与厂商实际支持不符登录模型厂商后台确认模型名不要用旧版本猜测名称Lombok 相关编译警告或报错JDK 版本与 Lombok 版本不兼容升级 Lombok 到与 JDK 17/21 兼容的版本或改用record编译或启动时内存不足insufficient memoryMaven/JVM 堆内存不够设置MAVEN_OPTS调整 IDEA 的 VM 参数结构化输出解析失败模型返回了额外文本或 target 类型不匹配在系统提示词中强制“仅返回 JSON”检查 record 字段名是否与模型输出一致Agent 没有调用工具直接返回文本工具描述不清晰或工具类没有注册检查Tool注解和tools()传入方式写入文件时路径越权用户或模型传入绝对路径在工具层做路径归一化限制到固定目录6.2 排查 Agent 未调用工具的步骤如果模型没有调用工具按下面顺序排查确认tools(new CodeTools())是否传入CodeTools是否被 Spring 扫描。检查Tool注解的描述是否足够清晰。描述含糊会让模型不知道该在什么时候调用。在 Prompt 里明确要求“请调用 writeFile 工具写入最终文件”。查看日志中是否有Tool call或Function call相关输出。6.3 网络与 API 地址注意事项如果你使用的是 OpenAI 官方接口需要确保服务器网络可访问相关域名如果是在国内环境建议优先使用国内模型厂商的 OpenAI 兼容接口例如 DeepSeek、通义千问、Moonshot 等避免因网络问题影响线上稳定性。7. 工程化最佳实践7.1 工具权限最小化Agent 能调用工具就意味着它能影响外部系统。文件写入类工具必须做路径白名单校验只允许写入指定目录数据库操作类工具必须限制为只读或经过审批的 SQL命令执行类工具在生产环境要谨慎启用最好使用独立的低权限账号运行 Agent 服务。7.2 超时、重试与降级大模型接口调用具有不确定性和延迟抖动风险生产环境必须配置超时和重试。Spring AI 支持在配置文件中设置spring: ai: openai: chat: options: temperature: 0.1 connect-timeout: 30s read-timeout: 120s这里的超时时间要根据实际场景调整简单对话通常 30 秒内返回复杂代码生成可能需要 60 秒以上。同时要设计降级方案例如模型服务不可用时返回缓存结果或错误提示而不是让接口直接 5xx。7.3 上下文与 Token 控制Agent 每轮工具调用都会把工具结果追加到上下文中Token 消耗会快速膨胀。常用手段包括限制用户输入长度。在系统提示词中要求模型只保留必要上下文。定期清理历史消息或使用 Token 计数工具控制总长度。7.4 日志与审计Agent 的每一次工具调用都应当被完整记录包括用户输入、模型决策、工具入参、工具返回结果。这样一旦出现问题才能回溯模型到底做了什么。建议使用 SLF4J 记录这类关键链路日志并将审计日志写入独立的日志文件或持久化存储。7.5 结构化输出的稳定性结构化输出是最容易出现解析问题的环节。一个比较有效的做法是在系统提示词中明确指出输出格式你返回的结果必须是一个 JSON 对象包含 fileName、language、code、description 四个字段不要包含任何额外文本。如果模型仍然偶发解析失败可以增加解析兜底逻辑例如捕获异常后要求模型重新生成或者使用正则提取 JSON 片段。7.6 流式输出的体验优化代码生成通常需要较长时间如果始终是同步等待前端体验会很差。Spring AI 支持流式调用方法签名返回FluxString可以将模型的输出按 token 推送给前端。流式输出加上 SSE 协议是生产级代码生成助手的标配能力。8. 总结与下一步学习路线到这里你已经从零实现了一个具备基础能力的 Java Agent 代码生成助手它能理解自然语言、调用文件读写工具、按结构化格式返回结果并把生成的代码写入磁盘。虽然和 Claude Code 相比还缺少终端交互、命令执行、多文件差异合并等高级能力但最核心的“模型 工具 循环”骨架已经完整。如果要把这个 Demo 继续往生产级方向推进可以按下面几个方向学习扩展更多工具Git 操作、Maven 编译、代码格式化、单元测试执行。引入 RAG让 Agent 读取你的团队私有编码规范、历史代码库生成更符合落地要求的代码。使用 Spring AI Alibaba 的 Graph 能力把复杂任务拆分为多个 Agent 节点形成工作流编排。研究 Agent 评估建立一组评测用例量化代码生成的正确率、工具调用成功率避免“感觉效果好”这种主观判断。项目中还有很多细节值得打磨但最重要的是先动手跑通这个闭环把代码拿过去改一改、跑一跑看看 Agent 在真实文件系统上的表现。祝你在 Java 后端 AI Agent 这条路上走得更远。