Spring AI 2.0 + DeepSeek:纯Java实现Agent代码生成助手

Spring AI 2.0 + DeepSeek:纯Java实现Agent代码生成助手 Claude Code 这类终端式 Agent 最近确实火但很多 Java 后端团队的疑问很现实我们不在 Python/Node 生态里能不能用纯 Java 做一个自己的代码生成助手答案是可以而且核心链路不用自己造轮子Spring AI 2.0 已经把模型接入、工具调用、多轮记忆这些事做完了。这篇文章要做一个可落地的项目用 Spring AI 2.0 DeepSeek手写一个类似 Claude Code 的代码生成助手。它不是只调 API 出文本而是让模型能主动读取工作区文件、创建代码文件、执行只读命令像真正的 Agent 一样完成从需求到代码落盘的过程。同时我会把接口封装、批量任务、超时排查一起讲清楚。如果你正在评估 Spring AI 版本升级、想在 Java 后端内置一个 Agent 能力或者想给团队做内部编码助手这篇文章可以直接收藏。先给结论硬件上没有门槛不需要独立显卡核心前置是 Java 17 和一个大模型 API Key建议使用 DeepSeek 这类 OpenAI 兼容接口国内可以直接访问。1. 核心能力速览先说结论方便快速判断这个方案适不适合你。能力项说明项目类型Java 后端 Agent 应用基于 Spring AI 2.0 手写代码生成助手核心依赖Spring Boot 3.x Spring AI 2.0 OpenAI 兼容接口主要功能代码生成、代码修改、文件读写、只读命令执行、多轮对话、REST 接口封装、批量任务硬件门槛无 GPU 要求普通开发机即可模型要求需要支持 Function Calling 的大模型示例使用 DeepSeek支持平台Windows / macOS / Linux依赖 JDK 17启动方式Maven 或 Gradle 启动 Spring Boot 应用是否支持 API支持本文会封装 REST 接口是否支持批量任务支持线程池 任务队列适合场景企业内部编码助手、智能运维、代码审查、RAG 应用扩展需要说明的是Spring AI 2.0 不是一套全新的编程模型而是把 1.x 时代分散的 API 收拢到了ChatClient这个统一入口上。对 Java 后端来说最大的价值是不用学 Python 的 LangChain也不需要在项目里引一堆 AI 框架Spring 原生的依赖注入、配置中心、监控体系都能直接复用。2. 适用场景与使用边界2.1 适合谁这个方案最适合三类人Java 后端工程师想在自己的业务系统里接入大模型能力既要快点出效果又要能交给 Spring 管理生命周期。内部工具链负责人需要给团队做一个统一入口的代码助手可以读项目、改代码、执行命令但又不想把代码库整个交给公网 SaaS 工具。Spring AI 学习者已经学过 ChatClient 基础调用想进一步理解 Agent、Tool Calling、多轮记忆和接口封装。2.2 不适合什么场景如果你的目标只是生成一次性代码片段不涉及文件系统那直接用 OpenAI 兼容接口写一个 HTTP 客户端就够了不需要引入 Spring AI。如果团队已经有成熟的 Claude Code 工作流并且已经在终端环境里跑得很顺不一定要换成 Java 实现。Spring AI 的好处是能嵌进 Web 服务但终端交互体验需要自己补齐。如果业务要求大模型必须离线部署、断网运行这个方案并不适合Spring AI 2.0 默认还是面向云端模型接口设计的。2.3 使用边界与合规提醒代码生成助手具备文件读写和命令执行能力这是它效率高的原因也是最大的风险点。任何一个允许模型执行命令的 Agent都必须做命令白名单不能让模型随意执行rm -rf、删除数据库、修改生产配置这类操作。涉及公司代码、客户资料、内部接口文档时要确认你的模型服务是否会把请求内容用于训练。对敏感项目建议用私有化部署模型或者明确签署数据协议的服务。生成代码的质量由模型决定接入生产环境前必须有人工 Code Review不要自动合并生成结果。如果后续做声音、图像、人脸相关功能一定要确认素材授权但本文的代码生成助手不涉及这些能力重点约束就在命令执行和文件写入边界上。3. 环境准备与项目初始化3.1 运行环境检查清单先确认本机环境缺哪个补哪个。环境项建议配置检查方式JDK17 或更高版本java -versionMaven3.8 或 Gradle 8mvn -vSpring Boot3.3通过父 POM 管理Spring AI2.0 当前版本在 Maven 中央仓库确认API KeyDeepSeek 或其他兼容接口在对应平台申请3.2 创建 Spring Boot 工程建议直接通过 Spring Initializr 创建工程也可以手动创建 Maven 项目。下面是完整的 Maven 依赖配置parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.4.1/version relativePath/ /parent properties java.version17/java.version spring-ai.version2.0.0-M1/spring-ai.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI 官方 OpenAI 兼容模块 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version${spring-ai.version}/version /dependency !-- Tool Calling 支持 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-tool-calling/artifactId version${spring-ai.version}/version /dependency !-- 可选内存记忆用于多轮对话上下文管理 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-memory/artifactId version${spring-ai.version}/version /dependency /dependencies注意一点Spring AI 的 Maven 依赖坐标在不同小版本之间可能会有微调。网上搜到的很多帖子写的是 1.0.0 的旧坐标如果你用的是 2.0一定以 Maven 中央仓库里实际发布的 artifactId 为准。版本号也不要写死先跑通再说。3.3 准备模型服务本文示例使用 DeepSeek因为它是 OpenAI 兼容接口可以直接通过配置 base-url 接入也适合国内网络环境。你需要在 DeepSeek 开放平台申请 API Key并确保账户有足够余额。申请完成之后把 Key 保存到环境变量里不要直接写死在代码和配置文件里。如果你是公司内部已经部署了其他 OpenAI 兼容模型服务也可以替换只需要把 base-url 和模型名改掉。4. 接入 DeepSeek 的两种配置方式Spring AI 接入 DeepSeek 有两种常见方式二选一即可。4.1 方式一官方 DeepSeek Starter如果当前 Spring AI 2.0 版本已经提供 DeepSeek 模块直接使用官方配置最省事spring: ai: deepseek: api-key: ${DEEPSEEK_API_KEY} chat: options: model: deepseek-chat temperature: 0.2这种方式的好处是代码里不需要手动指定 base-url模型客户端由框架自动装配。4.2 方式二OpenAI 兼容模式如果你更熟悉 OpenAI 的接入方式或者需要兼容多个模型服务商推荐使用 OpenAI 兼容模式。DeepSeek 的 API 兼容 OpenAI 协议配置如下spring: ai: openai: base-url: https://api.deepseek.com api-key: ${DEEPSEEK_API_KEY} chat: options: model: deepseek-chat temperature: 0.2两种方式的核心参数是model: deepseek-chat。如果你在开发者平台看到的是其他模型名以平台文档为准。这里要提醒一个高频坑很多人把模型名写成了deepseek-coder或者其他历史名称导致请求返回 400 Invalid Model。deepseek-chat是当前最常见的对话模型名称。4.3 验证模型连通性配置写完之后先写一个最简单的 Controller 或 CommandLineRunner 验证连通性不做任何 Agent 逻辑。package com.example.aiagent; import org.springframework.ai.chat.client.ChatClient; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; Component public class PingRunner implements CommandLineRunner { private final ChatClient chatClient; public PingRunner(ChatClient.Builder builder) { this.chatClient builder.build(); } Override public void run(String... args) { String reply chatClient.prompt(用一句话介绍你自己) .call() .content(); System.out.println(模型回复: reply); } }如果这一步能正常打印出模型回复说明 API Key、模型名、网络链路全部没问题。如果这里就报错不要继续往下做 Agent先把基础链路排查清楚方法和下文第 10 节一致。5. 手写 Agent从 ChatClient 到 Tool Calling5.1 Agent 的核心链路一个类 Claude Code 的 Agent 通常包含四层能力模型层负责理解用户意图、生成代码、决定下一步动作。工具层提供文件读取、文件写入、命令执行等能力模型通过 Function Calling 决定何时调用。记忆层保存多轮对话上下文避免每次都丢失前面的任务状态。执行层把模型生成的代码、修改后的文件落盘并返回执行结果继续给模型判断。Spring AI 2.0 的ChatClient是这一切的主入口。你需要做的不是自己写 Agent 编排框架而是把工具类注册给模型让模型在回答过程中按需调用。5.2 定义代码工具集先创建一个工具类里面定义模型可以调用的方法。Spring AI 2.0 使用Tool注解标记工具方法方法的参数和描述会被框架转换成模型可识别的 Function Calling 结构。package com.example.aiagent.tools; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; Component public class CodeWorkspaceTools { // 工作区根目录所有文件操作都被限制在这个目录内 private static final Path ROOT Paths.get(System.getProperty(user.home), agent-workspace); Tool(description 读取工作区中指定路径的文本文件) public String readFile(String path) { try { Path target resolvePath(path); return Files.readString(target, StandardCharsets.UTF_8); } catch (IOException e) { return 读取失败: e.getMessage(); } } Tool(description 将文本内容写入工作区中指定路径如果父目录不存在则自动创建) public String writeFile(String path, String content) { try { Path target resolvePath(path); Files.createDirectories(target.getParent()); Files.writeString(target, content, StandardCharsets.UTF_8); return 写入成功: target; } catch (IOException e) { return 写入失败: e.getMessage(); } } Tool(description 列出工作区指定目录下的文件列表) public String listFiles(String path) { try { Path target resolvePath(path); StringBuilder sb new StringBuilder(); try (var stream Files.list(target)) { stream.forEach(p - sb.append(p.getFileName()).append(\n)); } return sb.toString(); } catch (IOException e) { return 列目录失败: e.getMessage(); } } private Path resolvePath(String path) { Path target ROOT.resolve(path).normalize(); if (!target.startsWith(ROOT)) { throw new IllegalArgumentException(路径越界不允许访问工作区之外的文件); } return target; } }这里最核心的是resolvePath方法。它把所有路径先 normalize 再判断是否以工作区根目录开头防止模型通过../../路径跳出工作区读写系统文件。Agent 工具越权是排第一位的安全问题代码必须提前兜住。5.3 配置 ChatClient 和系统提示词系统提示词决定了 Agent 的行为模式。这里我们把它定义成一个“运行在终端环境中的代码生成助手”并明确告知模型哪些工具可用、什么情况下使用工具。package com.example.aiagent.config; import com.example.aiagent.tools.CodeWorkspaceTools; import org.springframework.ai.chat.client.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class AgentConfig { Bean ChatClient codingAgentChatClient( ChatClient.Builder builder, CodeWorkspaceTools tools) { return builder .defaultSystem( 你是一个运行在终端环境中的代码生成助手类似 Claude Code。 你的职责是帮助用户完成 Java 项目中的代码生成和代码修改任务。 可用工具 1. readFile(path) - 查看工作区文件内容 2. writeFile(path, content) - 创建或覆盖文件 3. listFiles(path) - 查看工作区目录结构 工作规则 - 在生成代码之前先查看目标目录的现有文件避免重复创建。 - 修改已有代码时先读取文件内容再决定修改方案。 - 不要编造文件内容如果文件读取失败主动告诉用户原因。 - 生成代码时给出必要的注释但不要过度装饰。 - 输出结果时用中文代码本身保持 Java 语法。 ) .defaultTools(tools) .build(); } }defaultTools这一步是关键。模型能不能真正调用工具取决于这里是否把工具类传给了 ChatClient。很多新手做完工具类发现模型不调用大概率就是忘了注册工具。5.4 创建 Agent 服务层把 ChatClient 封装到一个 Service 里方便后续给 Controller 和批量任务复用。package com.example.aiagent.service; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; Service public class CodingAgentService { private final ChatClient chatClient; public CodingAgentService(ChatClient chatClient) { this.chatClient chatClient; } public String run(String userPrompt) { return chatClient.prompt() .user(userPrompt) .call() .content(); } }到这里你已经拥有了一个最基础的代码生成 Agent。接下来做功能测试。6. 功能测试与效果验证6.1 测试一基础代码生成先测试基础能力让 Agent 直接生成一个 Java 工具类。操作步骤启动 Spring Boot 应用。调用CodingAgentService.run输入提示词请在工作区生成一个 StringUtils.java包含一个判断字符串是否为空的方法。观察模型是否先调用listFiles查看目录再调用writeFile写入文件。判断成功的标准工作区agent-workspace目录下出现StringUtils.java文件。文件内容包含完整的方法签名和实现。常见失败模型只输出代码文本但没有写入文件说明模型没有触发工具调用。写入路径不对因为提示词里没有指定包名模型可能直接写了根目录文件。建议在测试时把需求描述得更精确例如“在src/main/java/com/example/demo/utils目录下生成”。6.2 测试二读取并修改已有代码这个测试更接近真实 Agent 场景。先手动在工作区放一个Calculator.java内容故意只有加法然后让 Agent 增加一个减法方法。工作区里有一个 Calculator.java请先读取它然后增加一个减法方法并保存。判断成功的标准模型先调用readFile拿到原始代码。模型再调用writeFile写入包含减法方法的完整文件。Calculator.java中同时存在加法和减法方法。如果模型没有先读文件就直接写说明系统提示词约束不够强可以把“修改代码前必须先读取文件”进一步强调或者改成多轮对话强制确认。6.3 测试三多轮对话上下文一个合格的 Agent 需要记住用户在前几轮提到的信息。加入 Spring AI 的内存机制后模型可以在同一会话内记住之前的任务。在使用ChatClient时启用ChatMemorypackage com.example.aiagent.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ChatMemoryConfig { Bean ChatMemory chatMemory() { return new InMemoryChatMemory(); } Bean ChatClient memoryChatClient( ChatClient.Builder builder, ChatMemory chatMemory) { return builder .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } }测试方法先问“我们项目的包名是 com.example.demo”再问“请在这个包名下生成一个 User.java”。如果第二轮的生成结果自动带上com.example.demo包名说明上下记忆生效。6.4 判断 Agent 是否在正常工作在开发阶段强烈建议开启 Spring AI 日志观察模型和工具之间的调用过程。logging: level: org.springframework.ai: DEBUG日志里会看到模型返回的 tool calls、工具执行结果和最终回复。Debug 日志看着会有点多但排查 Agent 问题非常有效。7. 接口 API 与批量任务7.1 封装 REST 接口Agent 跑通之后下一步就是对外提供接口能力。这个方案是 Web 服务天然适合封装成 REST API供前端、命令行工具或其他服务调用。package com.example.aiagent.controller; import com.example.aiagent.service.CodingAgentService; import org.springframework.web.bind.annotation.*; import java.util.Map; RestController RequestMapping(/api/agent) public class AgentController { private final CodingAgentService codingAgentService; public AgentController(CodingAgentService codingAgentService) { this.codingAgentService codingAgentService; } PostMapping(/run) public MapString, String run(RequestBody MapString, String request) { String prompt request.get(prompt); if (prompt null || prompt.isBlank()) { return Map.of(error, prompt 不能为空); } String result codingAgentService.run(prompt); return Map.of(result, result); } }启动服务后用 curl 验证curl -X POST http://127.0.0.1:8080/api/agent/run \ -H Content-Type: application/json \ -d {prompt: 请生成一个 HelloController默认返回 hello}预期返回 JSON{ result: 已生成 HelloController.java内容如下... }7.2 批量任务设计代码生成助手常用于批量生成工具类、批量补测试、批量修复警告。批量任务不能直接在 HTTP 请求里同步执行否则一个任务跑 1 分钟接口就很容易超时。正确做法是把任务提交到线程池异步执行用一个任务 ID 去查结果。这里给一个通用模板package com.example.aiagent.service; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; Service public class BatchAgentService { private final CodingAgentService codingAgentService; private final ExecutorService executor Executors.newFixedThreadPool(4); private final ConcurrentHashMapString, CompletableFutureString tasks new ConcurrentHashMap(); private final AtomicLong idGenerator new AtomicLong(0); public BatchAgentService(CodingAgentService codingAgentService) { this.codingAgentService codingAgentService; } public String submit(String prompt) { String taskId task- idGenerator.incrementAndGet(); CompletableFutureString future CompletableFuture .supplyAsync(() - codingAgentService.run(prompt), executor) .exceptionally(ex - 任务执行失败: ex.getMessage()); tasks.put(taskId, future); return taskId; } public MapString, Object query(String taskId) { CompletableFutureString future tasks.get(taskId); if (future null) { return Map.of(error, 任务不存在); } if (future.isDone()) { return Map.of(status, done, result, future.join()); } return Map.of(status, running); } public void submitBatch(ListString prompts) { prompts.forEach(this::submit); } }使用方式# 提交任务 curl -X POST http://127.0.0.1:8080/api/agent/batch/submit \ -H Content-Type: application/json \ -d {prompt: 批量生成 10 个工具方法备注} # 查询任务 curl http://127.0.0.1:8080/api/agent/batch/query/task-1批量任务要注意几个点线程池大小要根据模型接口的 QPS 限制来定