Java Agent实战:用Spring AI 2.0打造仿ClaudeCode项目助手
最近在业务里做 Java 侧的大模型 Agent 应用时一个很明显的感受是Java 生态里能直接落地的 Agent 实战资料比 Python 少了很多。很多教程停留在“用 Spring Boot 调一次大模型 API返回一段文本”的阶段但真正想实现类似 ClaudeCode 这种能浏览项目文件、根据用户指令自动分析代码、再总结回答的 Agent 时你会发现要补的细节非常多工具调用怎么注册、多轮工具结果怎么回传、怎么防止 Agent 越权读取目录等等。本文会围绕 Spring AI 2.0 生态结合 Agent Utils 和 Spring AI Alibaba 写一套从零到可运行的 Java Agent 实战流程。核心目标是做一个“仿 ClaudeCode”的 Java 项目助手用户用自然语言提问Agent 自己决定要不要查看项目文件、要不要读取某个文件内容最后给出分析结论。适合正在学习 Spring AI、想从“调 API”走向“写 Agent”的 Java 开发者也适合准备把 AI 能力集成进内部工具链的后端团队。1. 为什么用 Java 开发大模型 Agent1.1 从“调用大模型”到“构建 Agent”早期 Java 后端接大模型最常见的写法是封装一个 HttpClient把用户问题拼进 Prompt请求大模型接口拿到回复后回显给前端。这是“单轮问答”本质上是一个远程函数调用不存在智能决策。Agent 的差别在于模型不再只生成文本它可以根据用户目标决定“是否需要调用工具”“调用哪个工具”“拿到工具结果之后下一步怎么走”。这个过程通常叫 Agent 循环Agent Loop或者工具调用循环Tool Calling Loop。用大白话解释普通问答是“用户问一句模型答一句”Agent 是“用户提一个目标模型自己拆步骤、调工具、看结果、再继续做直到任务完成”。例如用户说“看看这个项目有哪些文件顺便读一下 README 总结项目用途”。如果只是普通问答模型只能瞎编。但在 Agent 场景下模型会先请求调用listFiles工具拿到文件列表再调用readFile(README.md)拿到内容最后结合工具结果生成总结。这就是仿 ClaudeCode 的核心体验。1.2 Spring AI 2.0 生态Agent Utils 与 Spring AI AlibabaSpring AI 是 Spring 官方推出的 AI 应用开发框架目标是让 Java 开发者用一套统一的 API 接入不同大模型。它把 ChatModel、EmbeddingModel、Tool Calling、结构化输出等能力抽象成 Spring 风格接口开发者不需要关心底层 HTTP 协议和各家 API 差异。Spring AI 2.0 相比早期版本在模型接入、工具调用链、可观测性上有明显演进。围绕 Agent 场景Spring AI 生态里有几个重要模块ChatModel / ChatClient统一对话模型入口ChatClient 是推荐的高层 API支持链式调用、工具注册和流式返回。Agent Utils面向 Agent 开发的基础模块提供 Tool Callback 定义、方法工具适配、模型上下文管理等能力。平时写的工具类通过Tool标注后可以方便地被 Agent 使用。Spring AI Alibaba阿里巴巴开源的 Spring AI 适配组件提供阿里云百炼DashScope接入、通义千问系列模型的 starter以及在 Java 侧使用大模型时常见的企业级扩展。这三个组件组合起来就能搭建一个类似 ClaudeCode 的 Java Agent 底座。需要说明的是Agent Utils 的具体模块名在不同版本可能调整实际开发时以当前 Spring AI 官方 Release 的文档为准。1.3 仿 ClaudeCode 项目的核心思路ClaudeCode 给人的体感不是简单的聊天窗口而是“一个能动手操作代码库的助手”。它能看到项目结构、读取文件、甚至执行命令然后基于真实信息回答。在 Java 里仿这种形态不需要照搬它的前端交互关键是实现“模型 工具 循环”这套内核。我们用 Spring AI 2.0 做一次最小实现包含三个部分把“查看文件列表”“读取文件内容”封装成工具方法。把工具注册给 ChatClient让大模型知道自己能调用哪些能力。调用 ChatClient 时框架自动处理多轮工具调用循环。后面的实战章节会逐步展开这三个点。先看环境和项目怎么搭。2. 环境准备与项目初始化2.1 版本与依赖说明本文示例以 Spring Boot 3.x JDK 17 为基准。当前 Spring AI 2.0 对 JDK 版本有要求建议使用 JDK 17 或更高版本如果条件允许可以用 JDK 21。Maven 建议 3.8 以上。需要特别提醒Spring AI 版本迭代很快不要直接照抄网上任意一个版本的依赖坐标。优先去 Spring AI 官方 Release 页面和 Spring AI Alibaba 官方仓库查看当前稳定版本。以下代码是为了展示依赖结构版本号需要按你自己的环境调整。组件说明JDK17 或 21Maven3.8Spring Boot3.4 及以上具体看 Spring AI 2.0 兼容矩阵Spring AI BOM以官方 Release 版本为准Spring AI Alibaba Starter以官方 Release 版本为准2.2 创建 Spring Boot 项目推荐直接使用 Spring Initializr 生成基础项目选择 Spring Web 依赖。项目结构如下java-agent-demo/ ├── pom.xml └── src/main/ ├── java/com/example/agent/ │ ├── AgentApplication.java │ ├── controller/ │ │ └── AgentChatController.java │ └── tool/ │ └── ProjectTools.java └── resources/ └── application.yml在pom.xml中先引入 Spring AI BOM再添加相关依赖。关键片段如下dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId !-- 请使用官方发布的 2.0 版本号 -- version2.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI Alibaba用于接入阿里云百炼/DashScope -- dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId !-- 版本请查看 Spring AI Alibaba 官方 Release -- /dependency !-- Agent UtilsAgent 工具调用相关基础能力 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-agent-utils/artifactId /dependency /dependencies如果实际项目中依赖解析不到spring-ai-agent-utils需要去官方文档确认当前 2.0 版本的模块命名不同版本存在改名或拆分的情况。2.3 配置大模型 API我这边以阿里云百炼为例因为 Spring AI Alibaba 对这个场景支持最完整。在阿里云百炼控制台开通模型服务后获取 API Key配置到环境变量中。编辑src/main/resources/application.ymlspring: application: name: java-agent-demo ai: dashscope: api-key: ${DASHSCOPE_API_KEY} base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 agent: project-base-path: ./如果你使用的是 DeepSeek 或其他兼容 OpenAI 协议的模型也可以通过 Spring AI 的 OpenAI 协议接入。配置思路如下spring: ai: openai: api-key: ${DEEPSEEK_API_KEY} base-url: https://api.deepseek.com chat: options: model: deepseek-chat具体模型名和接入方式以模型服务商文档为准。配置完成后先不要急着写 Agent 逻辑我们来理解一下工具调用循环的原理这是整个实战的核心。3. 核心原理工具调用循环3.1 ChatModel 与 ChatClient在 Spring AI 中ChatModel 是底层接口负责与模型服务端通信。开发者一般不会直接操作 ChatModel而是使用 ChatClient 这个高层封装。ChatClient 支持链式调用典型结构是String answer chatClient.prompt() .user(用户问题) .tools(toolObject) .call() .content();你可以先设置系统提示词defaultSystem再在每次请求中追加用户消息和工具列表。.tools()是核心把 Java 方法暴露给模型模型会根据用户意图自行决定是否调用。3.2 Tool 注解与 ToolCallback工具本身就是一个普通 Java 方法。为了让模型知道这个方法的存在、作用和参数Spring AI 提供了Tool注解。在方法上写清楚描述模型就能在生成结果时看到这段描述从而决定是否调用。示例import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; Component public class SimpleTools { Tool(description 获取当前时间) public String currentTime() { return java.time.LocalDateTime.now().toString(); } }Spring AI 扫描到Tool方法后会把它封装成ToolCallback。这个对象包含了工具名称、描述、参数结构以及真正执行时对 Java 方法的调用逻辑。工具描述写得好不好直接影响模型调用工具的准确率。3.3 Agent 循环如何工作很多初学者会误以为“模型会一边回答一边执行 Java 方法”实际上模型不会主动执行你的代码。模型只负责输出一个特殊结构tool_calls其中包含工具名和参数。流程如下用户输入消息框架把消息和工具定义一起发给模型。模型判断需要调用工具返回一个ToolCall例如listFiles。Spring AI 框架收到ToolCall后在本地执行对应的 Java 方法。框架把工具执行结果作为一条新消息回传给模型。模型继续生成回复可能再次请求调用其他工具。循环重复直到模型不再请求工具返回最终文本。这套自动循环就是 Agent 的“自主性”来源。你不需要手写 while 循环Spring AI 会自动处理多轮工具调用。理解这一点后下面可以开始写一个真实可运行的项目。4. 实战实现一个能读懂项目的 Java Agent4.1 设计工具集仿 ClaudeCode 的第一步是让 Agent 具备“查看项目文件”和“读取文件内容”的能力。这两个工具足以完成很多代码分析任务。工具设计如下listFiles查看项目根目录下的文件清单。readFile读取指定文件的文本内容。安全上要做两个约束无论用户传入什么路径都只能访问项目根目录内的文件。文件内容过长时要截断避免超出模型上下文窗口。4.2 实现项目文件浏览工具创建src/main/java/com/example/agent/tool/ProjectTools.javapackage com.example.agent.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; Component public class ProjectTools { private final Path basePath; public ProjectTools(Value(${agent.project-base-path:./}) String basePath) { this.basePath Paths.get(basePath).toAbsolutePath().normalize(); } Tool(description 列出项目根目录下的文件清单最多返回50条。) public String listFiles() throws IOException { StringBuilder sb new StringBuilder(); try (StreamPath paths Files.walk(basePath)) { paths.filter(Files::isRegularFile) .limit(50) .forEach(p - sb.append(relativize(p)).append(System.lineSeparator())); } return sb.length() 0 ? 项目目录为空 : sb.toString(); } Tool(description 读取指定文件的内容。path 必须是相对于项目根目录的路径。) public String readFile(String path) throws IOException { Path target basePath.resolve(path).normalize(); if (!target.startsWith(basePath)) { return 拒绝访问路径越界只允许读取项目根目录内的文件。; } if (!Files.exists(target) || !Files.isRegularFile(target)) { return 文件不存在或不是普通文件 path; } String content Files.readString(target); if (content.length() 3000) { content content.substring(0, 3000) \n...内容过长已截断; } return content; } private String relativize(Path path) { return basePath.relativize(path).toString(); } }关键点说明basePath在构造时通过配置注入默认是当前目录。readFile里做了startsWith(basePath)校验防止../路径穿越。文件内容超过 3000 字符后截断避免无用 token 消耗。4.3 编写 Controller 调用 Agent创建src/main/java/com/example/agent/controller/AgentChatController.javapackage com.example.agent.controller; import com.example.agent.tool.ProjectTools; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.*; import java.util.Map; RestController RequestMapping(/agent) public class AgentChatController { private final ChatClient chatClient; private final ProjectTools projectTools; public AgentChatController(ChatClient.Builder builder, ProjectTools projectTools) { this.projectTools projectTools; this.chatClient builder .defaultSystem(你是一个 Java 项目助手。你可以查看项目文件、读取文件内容帮助用户分析代码和项目结构。回答要简洁、直接。) .build(); } PostMapping(/chat) public MapString, String chat(RequestBody MapString, String body) { String message body.get(message); String answer chatClient.prompt() .user(message) .tools(projectTools) .call() .content(); return Map.of(answer, answer null ? : answer); } }启动类保持 Spring Boot 默认即可package com.example.agent; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class AgentApplication { public static void main(String[] args) { SpringApplication.run(AgentApplication.class, args); } }这里有一个工程上的细节ChatClient.Builder是 Spring AI 自动配置好的 Bean。通过构造器注入就不需要手动创建ChatClient后续所有 Agent 调用都基于同一个客户端。4.4 运行与验证启动应用后用 curl 模拟用户请求curl -X POST http://localhost:8080/agent/chat \ -H Content-Type: application/json \ -d {message:请查看这个项目有哪些文件}预期结果类似{ answer: 项目根目录下包含以下文件\npom.xml\nsrc/main/java/com/example/agent/AgentApplication.java\n... }再测试一个需要读取文件并总结的场景curl -X POST http://localhost:8080/agent/chat \ -H Content-Type: application/json \ -d {message:读取 README.md 并总结项目用途}如果 README 存在模型会先调用readFile工具拿到内容后再总结。日志里能看到模型请求工具、工具执行、结果回传的过程。4.5 结果说明到这里一个最简版的“仿 ClaudeCode”Agent 已经跑通了。用户提问、模型决策、工具执行、最终回答这一整条链路完全由 Spring AI 驱动。不过你可能会发现当前这个版本还比较简陋每次请求都要重复传入ProjectTools对象。没有流式输出大模型回答慢的时候体验不好。文件工具只有只读能力不能写文件。这些正是下一章要扩展的内容。5. 扩展让 Agent 更接近 ClaudeCode 体验5.1 增加流式输出ClaudeCode 的交互感很大程度来自流式输出。Spring AI 的 ChatClient 支持.stream()返回响应式流。示例片段import reactor.core.publisher.Flux; FluxString answerStream chatClient.prompt() .user(message) .tools(projectTools) .stream() .content();如果你使用 Spring WebFlux可以直接把FluxString作为接口返回值前端通过 SSE 接收。默认情况下工具调用阶段不会输出文字只有模型最终生成文本时才产生流式内容。5.2 限制 Agent 的工作目录在实际项目中不能允许 Agent 扫描整个服务器目录。建议启动时明确指定工作目录只让 Agent 操作某个项目仓库的副本java -jar java-agent-demo.jar --agent.project-base-path/var/repos/my-project这样工具类的basePath被限定在指定目录内路径穿越校验继续生效。如果团队有多个项目仓库可以按仓库维度启动一个 Agent 实例。5.3 与代码仓库结合后续演进方向仿 ClaudeCode 还可以继续叠加这些能力git diff工具让 Agent 查看当前分支改动辅助代码审查。searchCode工具基于关键词搜索代码片段替代全量读取文件。writeFile工具让 Agent 修改代码但必须加权限控制和操作审计。runTests工具执行测试命令并返回结果适合在 CI 环境使用。需要强调的是越接近真实的代码操作助手安全设计越重要。工具的能力越强越不能把 Agent 直接暴露给不可信用户。6. 常见问题与排查思路6.1 Spring AI 连接 DeepSeek 不输出 content有开发者反馈Spring AI 连接 DeepSeek 时请求成功但最终返回的content为空。这个问题的常见原因有几种原因现象解决方向模型返回了tool_calls而不是content第一次响应 content 为空但模型申请调用工具确认是不是工具调用场景检查finish_reason模型名配置错误返回内容为空或直接报错检查模型名例如deepseek-chat或deepseek-reasonerAPI Key 或额度问题偶发空内容查看服务商控制台的调用日志框架版本兼容问题工具调用回传后第二次请求异常升级 Spring AI / Spring AI Alibaba 版本排查建议是先开启 Spring AI 的请求日志看原始响应内容到底是什么再决定是模型侧问题还是框架侧问题。不要一上来就改代码。6.2 依赖解析失败 / 模块找不到如果在 Maven 中找不到spring-ai-agent-utils或 Spring AI Alibaba Starter大概率是版本匹配问题。Spring AI 2.0 的模块命名和 1.0 有差异BOM 中并不一定包含所有模块。解决思路先确认 Spring AI BOM 版本正确。去官方文档查看当前版本推荐的 artifactId。如果使用 Spring AI Alibaba需要单独引入它的 BOM 或版本号。不要混用 1.x 和 2.x 的依赖。6.3 编译与运行内存问题大模型相关项目常出现以下内存异常java: OutOfMemoryError: insufficient memory可能原因IDE 构建过程内存不足而不是 JVM 运行内存。Spring Boot 应用启动了多个实例导致宿主机内存被耗尽。工具读取超大文件把文件内容全部加载到内存。解决方向IDE 中调整构建进程堆内存。启动参数增加-Xmx例如-Xmx512m。工具方法读取文件时限制大小避免一次性读取超大文件。6.4 Windows 下 Docker / WSL 服务缺失有的开发者在 Windows 上运行依赖 Docker 的 ClaudeCode 或本地模型环境时会看到类似missing hcs services: hns, vmcompute, vfpext的报错。这个报错说明 Windows 的 Hyper-V 相关服务没有完全启动Docker Desktop 无法创建虚拟网络。排查步骤检查 Docker Desktop 是否正常运行尝试重启。打开 Windows 功能确认“虚拟机平台”和“适用于 Linux 的 Windows 子系统”已开启。打开 PowerShell 执行wsl --status查看 WSL 状态。重启电脑后再次启动 Docker Desktop。这个问题主要影响本地中间件环境不影响 Spring AI 本身的编码。6.5 Lombok 与 JDK 版本冲突报错示例java: You arent using a compiler supported by lombok, so lombok will not work.常见原因是 Lombok 版本过旧不支持当前使用的 JDK。解决方法是把 Lombok 升级到较新版本并确保 IDE 编译器的 JDK 配置与项目一致。如果你在 Spring AI 示例代码中大量使用Slf4j这类注解这步配置直接影响项目是否能编译通过。7. 最佳实践与工程建议7.1 API Key 与配置管理API Key 是最高优先级的安全资源绝对不能硬编码到代码或配置文件里。推荐方式本地开发用环境变量例如DASHSCOPE_API_KEY。生产环境使用配置中心或密钥管理服务。在.gitignore中排除包含密钥的本地配置文件。定期轮换 API Key降低泄露风险。7.2 提示词与工具设计Agent 的表现往往不取决于模型而取决于工具描述和系统提示词的设计。给工具方法写description时要写清楚“这个工具是干什么的”“参数应该传什么”“什么场景下使用”。比如Tool(description 读取指定文件的内容。path 必须是相对于项目根目录的路径例如 pom.xml 或 src/main/java/xxx.java。)清晰的描述能显著提高大模型调用工具的准确率。系统提示词中也要说明 Agent 的角色边界例如“你是项目助手不要编造文件内容如果需要信息请先读取文件”。7.3 安全边界这是 Agent 工程里最容易被忽略的部分。所有文件路径必须做归一化和前缀校验防止路径穿越。不要直接给 Agent 暴露任意命令执行工具尤其是bash、rm、sudo这类高风险命令。如果必须执行命令一定要做白名单控制和操作审计。工具返回值不要包含数据库密码、API Key 等敏感信息。生产环境建议把 Agent 能力封装成内部服务通过鉴权控制访问范围。7.4 可观测性与成本控制Agent 的多轮工具调用会放大 token 消耗。一次看似简单的问题可能背后发生了 3 次模型请求。建议在工程化时记录每次请求的模型名称和 Token 数量。工具调用顺序和时间。超时、重试、失败的次数。流式输出的首字延迟和总耗时。Spring AI 提供了一些可观测性扩展点可以对接 Micrometer、Prometheus 等监控体系。成本控制方面优先使用更便宜的模型处理简单的工具调用只在关键任务上使用更强模型。7.5 测试策略Agent 应用的测试和传统单元测试不一样它依赖外部大模型结果有一定随机性。建议这样测用接口测试验证工具方法本身的正确性重点测路径穿越、文件不存在、内容截断等边界。用录制好的模型响应做回归测试保证工具调用链路稳定。在真实模型上做少量人工验证观察工具描述是否清晰、调用是否正确。不要把大量真实模型请求写进 CI避免费用失控和结果不稳定。8. 总结与下一步学习路线通过本文的实战你已经完成了一个基于 Spring AI 2.0 的 Java Agent 最小闭环理解工具调用循环、用Tool暴露文件工具、用 ChatClient 完成多轮调用、最终实现类似 ClaudeCode 的项目浏览与代码分析能力。接下来可以按这个路线继续深入RAG 增强把项目文档、API 文档向量化让 Agent 在回答时能检索知识库。多 Agent 协作拆分成“代码阅读 Agent”“测试执行 Agent”“评审 Agent”让多个角色协同完成任务。Spring AI Alibaba 的 Graph 能力在复杂任务中用图编排控制 Agent 的执行流程。模型微调如果工具描述和系统提示词已经无法提升效果再考虑用领域数据微调小模型。更实际的做法是先把本文代码跑通然后尝试把ProjectTools扩展成你真实项目的工具类让 Agent 能读取你的业务代码和配置文件。在动手过程中你会比看十篇原理文章更清楚 Agent 的边界在哪里。如果本文对你有帮助可以收藏备用。后面我会继续更新 Spring AI 2.0 的 RAG 实战和多 Agent 编排案例欢迎持续关注。