用Spring AI在Java后端实现Claude Code式代码生成Agent
2026/8/30 2:07:33 网站建设 项目流程

最近总有人在 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 Agent

1.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 对话入口,类似 RestClient
ChatModel大模型客户端抽象,屏蔽厂商 API 差异
Tool / @Tool定义 Agent 可调用的本地工具方法
Structured Output让模型按指定实体类结构返回 JSON
Document / 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@ToolStructured Output在 1.x 中完全可运行;如果后续你切换到 2.0,依赖坐标和少数 API 做迁移即可,整体设计思想不变。

2.3 Agent 的工作原理:LLM + 工具 + 循环

Agent 可以拆成三个要素:

  1. LLM 作为决策大脑:负责理解用户意图、拆分任务、决定下一步调用哪个工具。
  2. 工具作为行动手脚:例如读文件、写文件、执行命令、调用第三方接口。
  3. 循环作为执行框架:模型调用工具后,系统把工具结果返回给模型,模型继续判断是否需要调用下一个工具,直到认为任务完成。

用一句话概括:普通对话是“会说”,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-urlapi-keymodel三个配置项。

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.java

3.3 添加依赖

pom.xml中引入 Spring Web 和 Spring AI OpenAI Starter:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.5</version> <relativePath/> </parent> <properties> <java.version>17</java.version> <spring-ai.version>1.0.0</spring-ai.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</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 配置 ChatClient

Spring 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 中包含fileNamelanguagecodedescription字段。
  • 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 { List<String[]> 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 List<String[]> readCsv(String filePath) throws IOException { List<String[]> 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 未调用工具的步骤

如果模型没有调用工具,按下面顺序排查:

  1. 确认tools(new CodeTools())是否传入,CodeTools是否被 Spring 扫描。
  2. 检查@Tool注解的描述是否足够清晰。描述含糊会让模型不知道该在什么时候调用。
  3. 在 Prompt 里明确要求“请调用 writeFile 工具写入最终文件”。
  4. 查看日志中是否有Tool callFunction 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 支持流式调用,方法签名返回Flux<String>,可以将模型的输出按 token 推送给前端。流式输出加上 SSE 协议,是生产级代码生成助手的标配能力。

8. 总结与下一步学习路线

到这里,你已经从零实现了一个具备基础能力的 Java Agent 代码生成助手:它能理解自然语言、调用文件读写工具、按结构化格式返回结果,并把生成的代码写入磁盘。虽然和 Claude Code 相比还缺少终端交互、命令执行、多文件差异合并等高级能力,但最核心的“模型 + 工具 + 循环”骨架已经完整。

如果要把这个 Demo 继续往生产级方向推进,可以按下面几个方向学习:

  • 扩展更多工具:Git 操作、Maven 编译、代码格式化、单元测试执行。
  • 引入 RAG:让 Agent 读取你的团队私有编码规范、历史代码库,生成更符合落地要求的代码。
  • 使用 Spring AI Alibaba 的 Graph 能力:把复杂任务拆分为多个 Agent 节点,形成工作流编排。
  • 研究 Agent 评估:建立一组评测用例,量化代码生成的正确率、工具调用成功率,避免“感觉效果好”这种主观判断。

项目中还有很多细节值得打磨,但最重要的是先动手跑通这个闭环,把代码拿过去改一改、跑一跑,看看 Agent 在真实文件系统上的表现。祝你在 Java 后端 AI Agent 这条路上走得更远。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询