ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring AI 2.0构建Java Agent实战:仿ClaudeCode工具调用与任务拆解

Spring AI 2.0构建Java Agent实战:仿ClaudeCode工具调用与任务拆解 这次我们来看一个经常被问到的组合Spring AI 2.0 Agent Utils Spring AI Alibaba目标是用 Java 生态像 ClaudeCode 那样搭出一个能对话、能调用工具、能拆解任务并逐步执行的 Agent 项目。如果你正在做 Java 大模型 Agent 开发或者想从“调用大模型 API”升级到“让模型自主完成任务”这篇可以直接参考。文章会从架构设计、环境准备、代码实现到接口 API 与批量任务完整走一遍最后给出可落地的排查清单和工程建议。先说结论用 Java 做 Agent 完全可行而且并不比 Python 差太多。模型推理本身发生在远端 API 或本地推理服务Java 这边主要负责三件事组织提示词、管理对话上下文、编排工具调用。这三件事刚好是 Spring AI 最擅长的领域。本文所有内容基于 Spring AI 2.0 时代的编程模型展开结合 Spring AI Alibaba 的国内模型接入能力最后会落到一个仿 ClaudeCode 的 Java Agent 实战项目上。1. 核心能力速览能力项说明项目定位Java 大模型 Agent 实战项目仿 ClaudeCode 的对话式任务执行模式核心框架Spring AI 2.0统一模型接入与工具调用模型接入Spring AI Alibaba 接入通义千问也可通过 OpenAI 兼容模式接入 DeepSeek 等Agent 工具层Agent Utils统一管理工具注册、上下文窗口、任务拆解与结果回填运行方式Spring Boot 应用提供 Web 对话接口与命令行交互两种模式是否需要 GPU不需要。默认调用云端大模型 API如需本地部署模型再单独接入推理服务支持批量任务支持通过任务队列 线程池实现批量对话与工具执行接口 API提供 REST API便于接入 Web 前端、运维平台或自动化脚本适合读者Java 后端工程师、对 Agent 开发有兴趣但不熟 Python 的开发者这里要先提醒一点Spring AI 2.0 的具体 API 在版本迭代中会有调整。文章里展示的ChatClient、Tool、消息内存等写法都是当前版本的主干用法实际使用时要按你引入的版本对应官方文档做微调但整体设计思路不变。2. 为什么用 Java 做 Agent 项目Python 生态在大模型领域确实先发优势明显LangChain、LlamaIndex 这类框架都是从 Python 起步的。但如果你所在团队的技术栈以 Java 为主强行引入 Python 服务会带来额外的部署和维护成本。Java 侧做 Agent 的真正优势有三个第一Spring AI 已经把模型接入抽象成统一的ChatModel和ChatClient切换模型厂商通常只需要改配置不用改业务代码。这比自己在代码里拼接 HTTP 请求维护多套 API 要高效得多。第二Java 的类型系统和 Spring 的依赖注入非常适合做工具管理。Agent 最核心的能力是调用外部工具而 Spring 的 Bean 容器天然可以管理一组工具 Bean通过注解或注册表把工具暴露给模型比脚本语言里用字典维护函数列表更清晰。第三企业级集成成本低。Agent 最终要落到业务系统里需要对接权限、数据库、消息队列、监控告警。这些能力在 Java 生态非常成熟而 Spring AI 可以无缝嵌进现有 Spring Boot 工程不需要额外搭一座“桥”。所以这个实战项目的定位不是复刻一个完整的 ClaudeCode 命令行工具而是把 ClaudeCode 的核心工作方式抽象出来用户给目标 → Agent 拆解任务 → 调用工具 → 根据结果继续执行 → 输出最终答复。这套模式用 Java 实现一遍你就掌握了 Agent 开发最通用的骨架。3. 环境准备与前置条件在做代码之前先把运行环境准备好。按下面清单逐项检查即可检查项要求JDKJDK 17 及以上推荐 JDK 21MavenMaven 3.9国内网络环境可配置阿里云镜像Spring Boot3.3 及以上版本模型 API Key通义千问 DashScope API Key或 DeepSeek API Key网络能正常访问模型服务商 API 即可IDEIntelliJ IDEA 或 Eclipse推荐 IDEA这里有一个很多人会搞混的点如果你的 Agent 只调用云端 API本地完全不需要 GPU 和显存。显存只有在本地部署大模型推理服务时才需要。我们的项目默认走云端 API所以一台普通开发机就能跑通。如果你的需求是本地部署大模型那么建议先单独部署一个兼容 OpenAI 接口的推理服务比如 vLLM 或 Ollama再把 Spring AI 的 base-url 指向本地服务。后面章节会给出对应的配置方法。4. 项目初始化与依赖接入4.1 创建 Spring Boot 项目可以直接在 Spring Initializr 生成一个基础工程也可以手动创建 Maven 工程。关键依赖是spring-ai-bom和spring-ai-alibaba-starter。下面是 pom.xml 的核心片段parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.5/version relativePath/ /parent properties java.version21/java.version spring-ai.version2.0.0/spring-ai.version spring-ai-alibaba.version1.0.0/spring-ai-alibaba.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model/artifactId version${spring-ai.version}/version /dependency dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version${spring-ai-alibaba.version}/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /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注意版本号需要以 Maven 中央仓库实际可用的版本为准。spring-ai-alibaba-starter的版本演进比较快尽量选稳定版。4.2 配置文件如果你使用通义千问通过 Spring AI Alibaba 接入application.yml可以这样写spring: application: name: java-agent-claude ai: dashscope: api-key: ${DASHSCOPE_API_KEY} chat: options: model: qwen-plus如果你使用 DeepSeekSpring AI 的 OpenAI 兼容模式可以这样配置spring: ai: openai: base-url: https://api.deepseek.com api-key: ${DEEPSEEK_API_KEY} chat: options: model: deepseek-chat两套配置的核心区别只在于base-url和api-key。项目里建议通过环境变量或配置中心管理密钥不要把真实 Key 提交到 Git。5. 仿 ClaudeCode Agent 的架构设计ClaudeCode 的交互模式本质是一个“目标驱动的循环”用户输入目标 → 模型规划 → 执行工具 → 观察结果 → 继续决策 → 输出最终结果。我们的 Java 项目按这个思路拆成五个模块对应下图这里不使用流程图仅用表格说明模块职责模块职责对应实现chat-api对外交互入口Controller / 命令行 REPLagent-core对话循环与任务编排AgentLoop、TaskPlanneragent-tools工具注册与调用Tool 注解 ToolRegistryagent-memory上下文状态管理MessageWindowChatMemoryagent-utils公共工具与批量任务PromptBuilder、BatchTaskExecutorAgent Utils这个词在标题里可以理解为“Agent 公共工具层”它不一定是某个第三方开源库而是我们工程里负责任务拆解、上下文裁剪、工具结果解析、批量任务调度的模块统称。这样组织的好处是Agent 的核心循环代码保持稳定新增工具、新增模型、新增交互方式都只动对应模块。整个执行的伪代码如下接收用户目标 将目标写入消息会话 循环 将消息列表发送给模型 如果模型返回工具调用请求 执行对应工具 将工具结果作为消息回填给模型 否则 返回最终答复结束循环这套循环是 ClaudeCode 这类 Agent 最核心的骨架。Spring AI 的ChatClient已经帮你处理了大部分底层细节我们要做的是把工具注册和上下文管理接进来。6. 核心功能实现6.1 对话交互层先实现一个 Web 接口接收用户的对话请求。这是整个 Agent 的入口。RestController RequestMapping(/api/agent) public class AgentController { private final AgentService agentService; public AgentController(AgentService agentService) { this.agentService agentService; } PostMapping(/chat) public AgentResponse chat(RequestBody AgentRequest request) { return agentService.chat(request.sessionId(), request.message()); } }请求与响应用 Java Record 定义简洁且适合 Spring 的 JSON 序列化public record AgentRequest(String sessionId, String message) {} public record AgentResponse(String sessionId, String answer, boolean taskFinished) {}sessionId用来标识一次独立会话。同一个 session 内的多条消息会共享上下文不同 session 之间互不干扰。6.2 Agent 服务层AgentService是整个项目的中心。它负责创建ChatClient、维护会话上下文、调用工具并返回结果。Service public class AgentService { private final ChatModel chatModel; private final ToolRegistry toolRegistry; private final MapString, ChatMemory sessionMemory new ConcurrentHashMap(); public AgentService(ChatModel chatModel, ToolRegistry toolRegistry) { this.chatModel chatModel; this.toolRegistry toolRegistry; } public AgentResponse chat(String sessionId, String userMessage) { ChatMemory memory sessionMemory.computeIfAbsent(sessionId, k - MessageWindowChatMemory.builder() .maxMessages(20) .build()); ChatClient chatClient ChatClient.builder(chatModel) .defaultTools(toolRegistry.getAllToolNames().toArray(String[]::new)) .defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build()) .build(); String answer chatClient.prompt() .user(userMessage) .call() .content(); return new AgentResponse(sessionId, answer, true); } }MessageWindowChatMemory负责控制上下文窗口避免无限对话导致请求内容过长。maxMessages可以根据你的模型上下文上限调整通义千问和 DeepSeek 的长文本能力都不错但单轮请求还是建议控制在合理范围。6.3 工具注册与调用Agent 与普通聊天机器人的最大区别是能调用工具。Spring AI 提供了Tool注解可以直接把 Java 方法暴露给模型。我们做一个计算器工具和一个时间工具Component public class CommonTools { Tool(description 获取当前系统时间) public String getCurrentTime() { return LocalDateTime.now().toString(); } Tool(description 计算两个数字的加减乘除运算符支持 add、subtract、multiply、divide) public String calculate(double a, double b, String operator) { double result switch (operator) { case add - a b; case subtract - a - b; case multiply - a * b; case divide - { if (b 0) { throw new IllegalArgumentException(除数不能为0); } yield a / b; } default - throw new IllegalArgumentException(不支持的运算符: operator); }; return String.valueOf(result); } }工具类需要注册到 Spring 容器。可以在配置类中把所有ToolBean 的工具方法收集到ToolRegistryConfiguration public class AgentConfig { Bean public ToolRegistry toolRegistry(ListToolCallbackProvider providers) { ToolRegistry registry new ToolRegistry(); providers.forEach(provider - provider.getToolCallbacks() .forEach(registry::registerToolCallback)); return registry; } }模型在需要时会在响应中返回工具调用指令。Spring AI 的ChatClient会自动执行已注册的工具并把结果回填给模型无需我们自己写调用逻辑。这比手动解析 OpenAI 函数调用格式要省事很多。6.4 任务拆解与多步执行ClaudeCode 风格的 Agent 有很强的任务拆解能力。简单场景下模型会自己根据用户指令决定调用哪些工具复杂场景下我们需要主动引导模型先生成执行计划再逐步执行。这里可以用 Spring AI 的结构化输出能力把模型输出约束成一个任务列表Service public class TaskPlanner { private final ChatClient chatClient; public TaskPlanner(ChatModel chatModel) { this.chatClient ChatClient.builder(chatModel).build(); } public ListTaskStep plan(String goal) { String prompt 你是任务规划器。请把用户目标拆解为最多5个可执行步骤。 每个步骤要包含步骤名、执行动作、预期结果。 用户目标%s .formatted(goal); TaskPlan plan chatClient.prompt() .user(prompt) .call() .entity(TaskPlan.class); return plan.steps(); } }public record TaskStep(String stepName, String action, String expectedResult) {} public record TaskPlan(ListTaskStep steps) {}结构化输出是 Spring AI 的强项模型返回的 JSON 会自动绑定到 Java Record 上。拿到步骤列表后Agent 就可以按顺序执行每一步每一步的结果继续回填到上下文中。多步执行的核心循环可以这样组织public String executePlan(String sessionId, String goal) { ListTaskStep steps taskPlanner.plan(goal); StringBuilder resultBuilder new StringBuilder(); for (int i 0; i steps.size(); i) { TaskStep step steps.get(i); resultBuilder.append(步骤) .append(i 1) .append() .append(step.action()) .append(\n); String stepResult chat(sessionId, 请执行步骤 step.action()).answer(); resultBuilder.append(stepResult).append(\n); } return resultBuilder.toString(); }这里的实现刻意保持简单方便理解核心逻辑。生产环境需要考虑步骤失败时的降级和重试机制。6.5 命令行交互模式仿 ClaudeCode 的项目很适合提供一个命令行交互入口。Spring Boot 的CommandLineRunner可以实现程序启动后进入 REPL 模式Component public class CommandLineAgentRunner implements CommandLineRunner { private final AgentService agentService; private final Scanner scanner; public CommandLineAgentRunner(AgentService agentService) { this.agentService agentService; this.scanner new Scanner(System.in); } Override public void run(String... args) { System.out.println(Java Agent 已启动输入 /quit 退出); while (true) { System.out.print( ); String input scanner.nextLine(); if (/quit.equalsIgnoreCase(input.trim())) { break; } AgentResponse response agentService.chat(cli-session, input); System.out.println(Agent: response.answer()); } } }命令行模式的好处是便于快速验证 Agent 的工具调用是否符合预期也方便在没有前端页面的服务器上做演示。7. 接口 API 与批量任务设计7.1 对外 API 接口Web 接口已经在上文给出了/api/agent/chat它的能力本质是“单轮对话 工具调用”。为了让外部系统更好地接入可以补充两个接口RestController RequestMapping(/api/agent) public class AgentController { PostMapping(/chat) public AgentResponse chat(RequestBody AgentRequest request) { return agentService.chat(request.sessionId(), request.message()); } PostMapping(/tasks) public TaskExecuteResponse executeTasks(RequestBody TaskExecuteRequest request) { return agentService.executeBatch(request.sessionId(), request.messages()); } GetMapping(/sessions/{sessionId}/history) public ListChatMessage history(PathVariable String sessionId) { return agentService.getHistory(sessionId); } }executeBatch会接收一批消息并逐个处理这样外部系统可以批量提交任务。7.2 批量任务实现批量任务的核心是并发控制。大模型 API 通常有速率限制不能无限并发。我们可以用线程池限制并发数并且记录每个任务的状态方便失败重试Service public class BatchTaskService { private final AgentService agentService; private final ExecutorService executor; public BatchTaskService(AgentService agentService) { this.agentService agentService; this.executor Executors.newFixedThreadPool(5); } public void processMessages(String sessionId, ListString messages) { ListFuture? futures messages.stream() .map(msg - executor.submit(() - { agentService.chat(sessionId, msg); })) .toList(); // 等待所有任务完成 futures.forEach(future - { try { future.get(); } catch (Exception e) { // 记录失败任务并加入重试队列 System.err.println(任务执行失败: e.getMessage()); } }); } }注意ExecutorService使用完毕后要调用shutdown()或者在 Spring 生命周期中统一管理线程池。批量任务建议在配置文件中支持线程池大小和队列长度配置避免突发流量把 API 限额打满。7.3 curl 调用示例接口启动后可以用 curl 快速验证curl -X POST http://localhost:8080/api/agent/chat \ -H Content-Type: application/json \ -d { sessionId: test-001, message: 请帮我查一下当前系统时间 }如果工具调用成功返回结果中会包含模型基于工具输出生成的最终答复。这样一个请求走通后后面接前端或自动化脚本就很顺畅了。8. 资源占用与性能观察因为默认走云端 API本地 Java 进程的资源占用主要取决于 JVM 本身和并发任务数。你可以重点观察四个指标指标观察方式正常范围JVM 堆内存IDEA 自带监控或 jstat 命令根据场景通常几百 MB 以内CPU 使用率系统监控工具空闲时低批量任务时会升高API 响应延迟业务日志记录耗时取决于模型厂商通常 1~10 秒线程池排队情况自定义日志或 ThreadPoolExecutor 指标不应该有大量任务堆积如果你把模型部署到本地那么资源观察重点会转移到 GPU 显存和推理延迟。常见做法是单独部署 Ollama 或 vLLM用nvidia-smi查看显存占用再调整并发数和上下文长度。通过spring.ai.openai.base-url指向本地推理服务后Java 侧不需要任何代码改动。在性能调优时要注意上下文长度越长请求处理时间越长费用也越高。MessageWindowChatMemory是一个窗口超出部分会被丢弃。如果你的业务需要更长的历史记忆可以把对话历史持久化到 Redis 或数据库按需加载而不是全部塞进每次请求。9. 常见问题与排查方法下面整理这个项目最常见的几类问题按现象、原因、排查方式、解决方案列出问题现象可能原因排查方式解决方案启动报错 chatModel Bean 找不到未正确引入模型 Starter 依赖检查 pom.xml 是否包含 spring-ai-alibaba-starter补全依赖并刷新 Maven401 UnauthorizedAPI Key 配置错误或为空检查环境变量 DASHSCOPE_API_KEY重新配置 Key 并重启应用模型返回内容与预期不符提示词设计不合理或工具描述不够清晰查看请求日志中的完整消息列表优化系统提示词和 Tool 的 description工具一直不被调用工具方法没有注册到 ChatClient检查 ToolRegistry 是否包含该方法确保工具类被 Spring 扫描且方法上有 Tool 注解任务执行中卡住模型返回异常格式或网络超时查看日志中模型返回的原始响应给调用设置超时时间并加入失败重试逻辑显存不足本地部署了模型推理服务使用 nvidia-smi 查看显存占用降低并发数选择更小的模型或切换到云端 API批量任务频繁失败触发了 API 限流查看模型服务商返回的状态码降低线程池并发数增加重试间隔会话上下文串乱了sessionId 使用不当检查日志中每个请求的 sessionId确认统一会话使用相同 sessionId如果你的项目是接入本地 Ollama遇到“连接失败”时先确认 Ollama 服务是否启动、端口是否正确、是否能通过浏览器访问。localhost:11434是一个常见默认端口但具体以你本机配置为准。10. 最佳实践与合规边界项目跑通后建议从以下几个方面做工程化加固。第一先小参数验证再上批量。第一次运行先只测一条消息确认工具调用正常、上下文累积效果符合预期再放开批量任务。批量任务必须加日志、加失败重试、加并发限制。第二模型输入输出要留痕。Agent 的输入是用户指令输出是模型生成的文本或动作。生产环境中建议把每次完整请求、工具调用、模型响应都记录到日志或数据库中。这样一旦出现异常可以回溯是提示词问题、模型问题还是工具实现问题。第三权限与数据边界。Agent 能调用工具本质上是把模型的能力和外部系统能力打通了。工具方法必须做参数校验和权限控制不能因为模型说了一个参数就直接执行危险操作。比如删除、转账、发布类工具一定要有二次确认或审批流。第四隐私和版权合规。用户输入和业务数据如果会发送到云端大模型 API必须先确认是否符合公司的数据安全规范。涉及个人信息、商业机密的内容要脱敏后再发送或者选择私有化部署模型。模型生成的内容尤其是代码、合同、新闻类文本商用前必须人工复核不能完全信任模型输出。第五工具描述要写清楚。模型是依据Tool注解里的 description 来决定是否调用工具的。描述越具体模型越容易正确选择。比如“计算两个数字的加减乘除”就比“计算方法”强很多。11. 总结与下一步这个 Java 大模型 Agent 项目最值得尝试的点是把 Spring AI 2.0 的ChatClient、工具调用、消息记忆和结构化输出完整串起来形成一套仿 ClaudeCode 的可运行骨架。你已经可以照着上面的步骤从零初始化项目、接入模型、注册工具、启动 Web 接口、跑通批量任务然后逐步扩展成自己的 Agent 平台。最优先验证的功能是工具调用让 Agent 调用一次时间工具或计算器工具观察它是否能在多轮对话里保持上下文、执行工具并回填结果。最容易踩的坑也集中在这里——工具没有注册、Tool 描述不清晰、会话 sessionId 不一致都会导致结果异常。后续扩展方向有四个一是把工具从简单方法扩展成外部系统 API 调用对接数据库查询、HTTP 请求、定时任务二是引入多 Agent 协作规划 Agent、执行 Agent、审查 Agent 各司其职三是把对话历史持久化到 Redis支持跨服务共享会话四是在项目里加入指标监控统计每次 Agent 运行的耗时、Token 消耗和工具调用成功率为后续优化提供数据依据。建议现在就动手做一件事用这五个模块的最小骨架先跑通一个“查询时间 做一次数学计算”的 Agent 会话把工具调用链路验证完成再继续扩展任务拆解和批量执行。代码不复杂核心逻辑都在ChatClient和工具注册上剩下的就是业务场景的填充。
返回列表