尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Spring AI 2.0构建Java版AI Agent:仿Claude Code的核心协议与实践

Spring AI 2.0构建Java版AI Agent:仿Claude Code的核心协议与实践 过去一个月我一直在折腾一个 Java 版 AI Agent 项目目标很明确用 Spring AI 2.0 加 Agent Utils 搭一个类似 Claude Code 的终端智能助手。折腾完以后我最真实的感受不是“Java 终于也能做 Agent 了”而是很多人可能从一开始就把 Claude Code 理解错了。它真正值得模仿的不是那个酷炫的终端界面也不是它一次性能写多少代码而是一整套“任务拆解、工具调用、人工确认、循环终止”的协作协议。如果你也准备用 Spring AI 2.0、Agent Utils、Spring AI Alibaba 做仿 Claude Code 项目我建议你先别急着写代码。先把“你到底在仿什么”这个问题想清楚否则做出来大概率只是一个套了聊天框的文件编辑器。1. 先看清“仿 Claude Code”到底要仿什么1.1 Claude Code 不是聊天框而是一套“人机协作文书”很多人第一次打开 Claude Code看到的是它在终端里一行一行输出然后自动改文件、跑命令、提交代码。你以为这是“AI 写代码”实际上它更像一个非常守规矩的实习生它每做一个重要动作之前都会先告诉你它打算干什么等你确认后才会执行。这一点看起来简单但实际做起来非常难。因为它意味着 Agent 不能只生成文本它必须能感知当前工作目录、能读取文件、能执行命令、能把中间结果回传给模型让模型决定下一步做什么。这个回路一旦形成就不再是“一问一答”而是“目标驱动的多步协作”。所以仿 Claude Code 的核心不是复刻它的外壳而是复刻三个关键能力模型能够把大目标拆成小步骤Agent 能够调用外部工具比如读文件、写文件、执行命令每一步之间能保留上下文并且允许人工介入。1.2 拆解成 Java 工程模块后事情突然没那么多把上面三个能力翻译成 Java 工程结构你会发现它并不神秘对话入口接收用户自然语言输入比如“帮我在当前目录生成一个 Spring Boot 项目启动类”任务拆解由大模型根据系统提示词把用户需求拆成多个子任务工具注册Java 方法以工具形式暴露给模型函数描述、参数 json schema 必须清晰工具执行Agent 执行模型选中的工具把结果返回给模型循环控制模型可能连续调用多个工具直到它认为任务完成人工确认在关键操作前暂停等待用户输入 y/N。这一套流程在 Spring AI 2.0 里基本都有对应的编程模型。模型接入层用 Spring AI Alibaba 解决工具调用和 Agent 循环用 Spring AI 的 function calling 加上自己封装的 Agent Utils 解决。1.3 这个工程的主判断我的观点是这类项目的难点不在“让模型说话”而在“让模型在约束下行动”。Spring AI 2.0 给了你一个很好的底座但真正拉开差距的是工具边界、上下文管理、人工确认和日志审计。如果你能接受这个判断再看后面的代码和方案就不会被各种概念绕晕。2. Spring AI 2.0 和 Spring AI AlibabaAgent 的 Java 底座2.1 为什么 Java 团队不需要自己造模型接入层早期 Java 团队接入大模型最常见的方式是直接写 HTTP 客户端手动拼请求体、解析响应。Demo 阶段没问题一旦进入 Agent 阶段就非常痛苦因为要处理流式输出、函数调用、多轮上下文、工具结果回填自己写一套很容易失控。Spring AI 2.0 把这些问题做了分层ChatModel 负责模型通信ChatClient 负责面向业务开发者的 prompt 封装Function Calling 负责把 Java 方法暴露给模型Tool Context 负责管理工具执行时的上下文。你不需要关系底层 HTTP 细节只需要定义好业务工具。这就是 Spring 体系一贯的思路把变化频繁的模型协议藏在统一接口后面让业务代码稳定下来。2.2 用 Spring AI 2.0 搭一个最小客户端无论你最终接入哪个模型第一步都是引入依赖并配置模型连接。下面这段依赖结构是常见写法具体版本号请以你实际使用的 Spring AI 版本为准dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version${spring-ai.version}/version /dependency如果你的模型是阿里云通义千问系列还可以改用 Spring AI Alibaba 提供的 DashScope Starterdependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId version${spring-ai-alibaba.version}/version /dependency配置层面通常会在 application.yml 中填写模型 api-key、模型名称、基础地址等。不同版本配置项会有差异我现在更建议先按官方文档的最小配置跑通一次再根据实际报错补充spring: ai: openai: api-key: ${MODEL_API_KEY} base-url: ${MODEL_BASE_URL:https://api.openai.com} chat: options: model: ${MODEL_NAME:gpt-4o-mini}如果走 DashScope则替换为 DashScope 的配置。这里不要盲目照搬因为 Spring AI 2.0 之后配置结构有过调整关键是确认你的 starter 版本对应哪个配置前缀。然后注入 ChatClientConfiguration public class AgentConfig { Bean ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultSystem(你是一个可靠的编程助手你可以在用户允许下检查文件和执行命令。) .build(); } }到这里你已经有能力发起一次模型对话。但距离 Agent 还很远。2.3 Spring AI Alibaba 解决的是“国产模型接入”的最后一公里Spring AI Alibaba 的意义不是多了一个“国产模型 starter”而是让国内开发者可以用同一套 Spring AI 接口快速接入 DashScope 上的 qwen 系列模型并且适配企业开发里常见的模型切换场景。在仿 Claude Code 项目里模型能力直接决定了任务拆解质量和工具调用准确性。qwen 系列模型对中文指令和代码任务的理解都较好而且工具调用格式比较规范配合 Spring AI Alibaba 的适配能减少很多解析上的麻烦。但要注意模型接入层只是底座不是难点。真正难的是接下来这些工程问题。3. 第一个可运行的 Agent让模型学会“接需求、拆任务、调工具”3.1 先定义 Agent 的工作协议Agent 的工作协议可以这样理解用户给一个目标模型先拆出计划每一步尽量选择一个可执行工具工具执行完把结果反馈给模型模型再决定下一步。整个过程不需要前端界面也能跑通。我们需要先定义两个东西一个是系统提示词告诉模型它的身份、能力边界和工作方式另一个是工具列表让模型知道它能调用什么。很多教程把这两个东西混在一起结果模型要么频繁调用不存在的工具要么生成一堆解释就是不执行。我的经验是系统提示词里越早说明“你是一个 Agent不是聊天助手”模型的行为就越接近工具型任务执行器。3.2 系统提示词给模型立规矩下面这段提示词可以用于第一版测试你是一个运行在终端里的 AI 编程助手。 你的工作方式是 1. 先理解用户的最终目标。 2. 把目标拆成可执行的小步骤。 3. 对每一步选择合适工具如果不需要工具就直接回答。 4. 每次工具执行后根据结果决定是继续下一步还是结束任务。 5. 如果要执行可能改变文件或运行命令的操作必须停下来询问用户确认。 6. 不要编造工具执行结果一切以工具返回为准。这段提示词的价值不在于“写得好”而在于它定义了循环。没有这个循环模型就只会输出一段“建议”而不是真正调用工具。3.3 用 Function Calling 暴露 Java 方法Spring AI 支持把 Java 方法注册成模型可调用的工具。下面是一个读取文件的工具方法示例import org.springframework.ai.tool.annotation.Tool; import java.nio.file.Files; import java.nio.file.Path; Service public class FileAgentTools { Tool(description 读取指定路径的文本文件内容) public String readFile(String filePath) throws Exception { Path target resolvePath(filePath); return Files.readString(target); } private Path resolvePath(String filePath) throws Exception { Path root Path.of(System.getProperty(user.dir)).toRealPath(); Path target root.resolve(filePath).normalize(); if (!target.startsWith(root)) { throw new SecurityException(路径越界); } return target; } }这个例子做了两件事第一把 Java 方法变成了模型可以调用的工具第二对路径做了边界校验。第二点非常重要因为一个能读文件、能跑命令的 Agent如果没有路径边界和命令白名单在小项目里没问题一旦在真实项目里使用可能会造成不可逆影响。3.4 验证流程从写死响应到跑通工具调用第一版验证不需要做太多。你可以先写一个测试用例让用户输入“把当前目录下的 README.md 内容总结给我”然后看模型是否会调用 readFile 工具并把文件内容作为上下文。更简单的验证方式是直接向 ChatClient 提交一条用户消息String result chatClient.prompt() .user(请读取当前目录下的 README.md 文件然后告诉我它讲了什么。) .call() .content();如果配置正确你会看到模型先发起一次工具调用Spring AI 执行 Java 方法后把返回结果回传给模型最终模型生成总结。这个“工具调用回填”的过程就是 Agent 区别于普通聊天的关键。4. Agent Utils 的意义把“循环决策”封装成可维护代码4.1 Agent 不是一次问答而是“决策-执行-反馈-再决策”如果只是让模型调用一次工具那还称不上 Agent。真正复杂的部分是循环。举个实际例子用户说“把这个项目的接口文档整理成 README。”模型可能需要先读目录结构再读若干 Controller 文件每次读文件都是一次工具调用。最终模型要把多次读取结果汇总。如果循环逻辑写在业务代码里会特别凌乱。这时候Agent Utils 的价值就出来了。我理解的 Agent Utils不是一个必须依赖的官方框架而是你自己工程里沉淀下来的一组 Agent 工具模块它负责以下事情维护当前任务的上下文消息列表注册工具实例和工具描述解析模型返回的工具调用参数调用对应 Java 方法把结果封装成 tool result 回传判断循环是否应该结束。4.2 一个最小 Agent 主循环实现下面是非常简化的伪代码用于说明 Agent 循环的骨架public class AgentLoop { private final ChatClient chatClient; private final ToolRegistry toolRegistry; private final int maxIterations; public String execute(String userTask) { ListMessage messages new ArrayList(); messages.add(systemMessage()); messages.add(userMessage(userTask)); for (int i 0; i maxIterations; i) { String modelResponse chatClient.prompt() .messages(messages) .call() .content(); // 假设模型返回里包含 toolCalls 解析结果 ToolCallResult result ToolCallResult.parse(modelResponse); if (!result.hasToolCalls()) { // 模型认为任务完成直接返回最终回答 return result.text(); } for (ToolCall call : result.toolCalls()) { Object toolResult toolRegistry.execute(call.name(), call.arguments()); messages.add(toolResultMessage(call, toolResult)); } } throw new AgentLoopLimitException(超过最大迭代次数); } }实际项目中Spring AI 的 ChatClient 和 ToolCalling API 会帮你处理一部分通信细节但 Agent 循环的终止条件、上下文拼接和失败重试仍然需要你自己设计。Agent Utils 把这些逻辑收拢到一个模块业务层只负责提交任务和拿最终结果。4.3 控制循环的边界条件Agent 循环最怕两件事死循环和无效循环。模型不断调用同一个工具或者每次都返回一模一样的中间步骤。所以至少要设置三个边界maxIterations单次任务最多工具调用次数maxToken或maxContextWindow当消息太长时需要做裁剪或摘要identicalResultLimit如果连续多次工具调用返回结果一致应该主动终止。这三个参数在你第一版里不需要做得太精致但不能没有。否则线上跑着突然消耗大量 token很容易失控。5. 让它像 Claude Code 一样在终端干活文件、命令和确认5.1 工作目录与路径安全Claude Code 之所以让人放心不是因为它“什么都能干”而是它在执行敏感操作前会停下来问人。我们在 Java 版 Agent 里也可以做到。第一步是给所有文件工具加工作目录限制。前面代码里的 resolvePath 就是一个示例。所有工具都基于同一个 root 目录不接收绝对路径或者接收后强制 normalize。这样模型即使被诱导输入超出目录的参数Java 侧也不会执行。5.2 命令执行的边界和人工确认这个 Agent 如果想在终端里真正有用必须支持执行命令。比如“运行测试”“构建项目”“查看 git 状态”。但命令执行是最危险的一环。我建议按这个顺序控制风险只允许白名单命令比如 mvn、git、ls、cat、grep不允许连续拼接复杂命令所有参数交给工具解析执行前必须打印完整命令并等待用户输入 y/N 确认命令执行加超时默认 30 秒或 60 秒执行结果最多返回给模型前 200 行避免上下文爆炸。Tool(description 在项目根目录执行白名单命令执行前需要用户确认) public String executeCommand(String command) throws Exception { if (!isAllowed(command)) { return 命令不在白名单内已拒绝; } System.out.println(Agent 准备执行 command); System.out.print(是否继续(y/N) ); Scanner scanner new Scanner(System.in); String input scanner.nextLine().trim(); if (!y.equalsIgnoreCase(input)) { return 用户已取消执行; } Process process new ProcessBuilder(bash, -c, command) .directory(workDir.toFile()) .redirectErrorStream(true) .start(); boolean finished process.waitFor(30, TimeUnit.SECONDS); if (!finished) { process.destroyForcibly(); return 命令执行超时; } String output new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8); return output.length() 20000 ? output.substring(0, 20000) : output; }这段代码不一定能直接跑在你的环境里但它给出了几个必须有的动作白名单校验、用户确认、超时控制、输出截断。没有这四步命令执行工具就不应该上线。5.3 用 Spring Shell 做一个最小 CLI如果你希望 Agent 看起来像 Claude Code 一样在终端里交互可以用 Spring Shell 快速做一个 Shell 命令。Spring Shell 会帮你处理命令解析和交互循环比你手写 Scanner 更清晰。一个最简单的命令类如下ShellComponent public class AgentCommand { private final AgentService agentService; public AgentCommand(AgentService agentService) { this.agentService agentService; } ShellMethod(运行一个 Agent 任务) public String agent(ShellOption String task) { return agentService.run(task); } }启动 Spring Boot 应用后在终端输入agent 帮我整理当前目录结构就能看到一个最简单的 CLI Agent 雏形。5.4 异常与超时先判断是模型还是工具问题Agent 跑起来后你会遇到很多“看起来像模型问题”的问题。但我的排查经验是大部分问题不在模型而在工具调用链路。比如模型“不输出内容”可能根本原因是工具执行超时导致模型没有拿到结果于是返回空。又比如模型调用工具时参数解析失败Spring AI 会返回 tool call error模型可能无法从这里恢复最终表现为输出内容很怪。所以要在日志里把每个环节都打出来模型请求开始、模型响应原始内容、工具调用参数、工具执行耗时、工具返回结果、下次模型请求消息数。没有这些日志你只能靠猜。6. 模型接入不是玄学DeepSeek、本地模型与通义选型与排查6.1 不同模型的接入形态仿 Claude Code 项目对模型的要求并不低需要较强的工具调用能力。你可以选择的模型接入方式大致有三类接入类型典型模型优点需要注意云端 OpenAI 兼容接口DeepSeek、qwen 等接入成本低很多都是 OpenAI 协议需要配置 base-url 和 key注意网络和计费Spring AI Alibaba / DashScopeqwen-max、qwen-plus国内访问稳定中文工具调用表现好依赖阿里云账号和已开通模型服务本地模型Ollama 部署 qwen、deepseek 蒸馏版等数据不出门离线可用硬件成本高工具调用能力通常弱于云端大模型如果你只是学习我建议从云端 OpenAI 兼容接口开始因为大多数模型已经兼容 OpenAI 的 function calling 协议Spring AI 用起来最顺。如果你在给公司做内部工具并且对数据合规要求高再考虑本地部署。6.2 “不输出 content”这类问题的排查链路很多人在 Spring AI 里接入 DeepSeek 或本地模型后会遇到模型调用工具后返回空 content 的情况。这种问题不能只看最后一步要按链路排查先看是否真的调到了模型日志里有没有模型请求和响应再看模型响应是否包含 tool calls如果返回了 tool calls那 content 为空是正常的因为模型正在等待工具结果确认工具是否成功执行工具抛异常、超时都会导致没有结果回填检查消息列表是否合法工具调用后必须把对应 tool result 消息补上否则模型会懵最后才是模型参数问题temperature 太高或太低max tokens 太小也可能导致模型截断或不输出。这五步听起来简单但实际排查时能帮你省几个小时。6.3 上下文长度与成本预算才是真正的约束工具型 Agent 非常消耗上下文。每次工具结果都会追加到消息列表里几轮之后可能就超过模型上下文窗口。我的建议是不要把所有工具结果都原样塞给模型。比较大的文件内容可以先做截断、摘要或向量化检索。比如读 README如果文件超过几千字只返回前 2000 字加一个“文件较长已截断”的提示。这样能显著降低 token 消耗。同时给每个任务设置一个 token 预算。最简单的方法就是设置 maxIterations但更精细的做法是在 Agent Utils 中累计每条消息的 token 估算接近预算时强制结束并提示用户。7. 从跑通到能用Agent 工程的四个检查项7.1 会话数据能否恢复第一版 Agent 很可能是一次性会话。用完就丢用户必须重新描述任务。真正的终端智能助手至少要能保存会话历史让用户随时恢复上一个任务。比较轻量的做法是用本地文件保存 JSON 消息列表重一点的做法是放到数据库。7.2 工具调用是否可审计在 Agent 执行过文件修改和命令后必须能回答几个问题谁在什么时间发起的任务模型调用了哪些工具工具参数是什么执行结果如何这些信息既是安全审计需要也是日后调优 Agent 行为的重要数据。建议在 Agent Utils 中加一个工具调用日志表落库或写入文件。7.3 资源占用和并发是否可控Agent 的任务可能持续几十秒甚至几分钟。如果用户同时发起多个任务模型 API 并发、命令子进程、文件锁都会成为瓶颈。至少要做到单用户单任务串行或者给 Agent 执行器加线程池和并发上限。7.4 有没有失败重试和人工兜底模型接口不稳定、工具执行超时、用户中途取消这些都是常见问题。第一版可以没有自动重试但必须有明确的失败提示和人工兜底路径。比如命令超时后不要直接说“我失败了”而是告诉用户“命令已超时你可以在终端自己运行或者让我换一个方式继续”。这四个检查项是我判断一个 Agent 项目能不能从 Demo 走向内部使用的分水岭。只跑通聊天和工具调用不算完成把这四件事补上才算“可用”。8. 我的最终建议先做“有边界的工具 Agent”再谈“全自动化”做这个仿 Claude Code 项目我最开始目标很大想做一个全自动编码代理后来被现实打了脸。模型确实能理解任务但真正项目里文件依赖、环境差异、编译错误、测试失败每一环都可能让 Agent 陷入很长的无效循环。全自动化不是不可能而是需要大量工程打磨。更现实的路径是先做一个“有边界的工具 Agent”。它只做几件事读取文件、搜索代码、查看 git 状态、执行白名单命令、生成变更说明。每一步需要用户确认每一次执行都有日志。这个 Agent 用起来不会被同事骂“太可怕”反而真的能节省不少重复劳动。当你把这套边界、循环、确认、审计做好之后你手里的就不只是一个模仿 Claude Code 的 Demo而是一个真正符合 Java 工程习惯的 AI Agent。到那时候是否还要复刻更多花哨功能只是时间问题。Java 生态做 Agent 从来都不缺模型接入能力缺的是把模型行为和工程约束缝合在一起的实践。Spring AI 2.0、Agent Utils、Spring AI Alibaba 只是工具真正让 Agent 可靠的是你给他画的边界。
返回列表