ARTICLE DETAIL

资讯详情

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

Spring AI 2.0 从零手写代码生成Agent:模型、工具与循环全解析

Spring AI 2.0 从零手写代码生成Agent:模型、工具与循环全解析 最近很多 Java 后端开发者开始认真看 Spring AI Agent但大部分人看完几篇教程后的感受往往是两种极端要么觉得这不过是大模型 API 换了个壳要么觉得离自己能写还差一个完整框架。我的建议是换个路径——不背概念直接动手写一个能看见全部过程的 Agent。这篇文章就是把一个 Claude Code 风格的代码生成助手用 Spring AI 2.0 从零搭起来的过程。为什么选这个例子因为代码生成是所有 Agent 场景里最不“玩具”的一个。它要求 Agent 必须会列目录、读文件、搜索关键词、写文件还要能把一次修改决策变成结构化结果。等这个流程跑通你再回头看各种 Agent 框架会感觉每个组件都落在了实处。先给结论Spring AI 2.0 对 Java 后端的真正价值不是“Java 终于能调用大模型了”而是 Agent 开发第一次成了 Spring 工程体系里的一等公民。你可以在一个 Spring Boot 服务里像组装普通组件一样把模型、工具、结构化输出和上下文管理拼成一个能处理多步任务的 AI 助手。接下来从零开始拆。1. 先搞清楚 Spring AI 2.0 真正改变的是什么1.1 没有 Spring AI 之前Java 调模型是什么样在 Spring AI 出现之前Java 后端要做一个带 AI 的功能通常得自己走一遍很原始的流程用 HTTP 客户端拼请求、处理鉴权、组织 messages 数组、解析流式响应再自己处理多轮对话历史。这些事不是不能做而是每个项目都要重复做一遍而且做得越深的团队越容易在细节上踩坑。踩坑点通常集中在几个地方OpenAI 兼容协议里角色字段写错模型直接不认。工具调用返回的tool_calls结构解析错了模型拿到错误输入继续瞎编。流式响应没处理好前端拿到的是半截 JSON。多轮对话没有维护消息历史第二轮开始模型就“失忆”。这些问题的本质是大模型能力本身没有门槛但把模型能力嵌进业务系统缺的是一层工程抽象。Spring AI 做的事情就是把这层抽象做成和 Spring Boot 本身一样的方式——配置驱动、Bean 组装、组件可替换。1.2 Spring AI 提供的不只是 SDK而是一套 Agent 组件模型很多人把 Spring AI 理解成“一个统一的模型 SDK”这低估了它。它真正给的是一整套 Agent 开发组件组件解决什么问题对应能力ChatModel统一不同模型厂商的接入协议切换模型服务只改配置ChatClient提供流式/非流式的对话门面一个入口处理所有调用Tool 工具注册让模型能调用外部方法并拿到结果读文件、查询数据、写文件Structured Output把模型输出直接转成 Java 对象结构化输出不再是手写 JSON 解析Advisor给调用链附加记忆、过滤、事件回调上下文管理、对话记忆ToolCallingManager编排模型发起工具调用的循环Agent 循环的底层支撑这套组件的关键不在单个能力而在于它们都能被 Spring 管理。你可以用Configuration去组装用Bean去注册用配置中心去切换模型参数用 AOP 去记录调用日志。这意味着 Agent 不再是游离在业务系统之外的一个 Python 脚本而是真正长在了 Java 后端的工程体系里。1.3 为什么拿“代码生成助手”当教学案例因为代码生成场景最能暴露 Agent 和普通 Chat 调用的差异。普通 Chat 调用是“问一句、答一句”模型只动嘴不动手。而代码生成助手要求模型动手先看项目里有哪些文件再读相关代码搜索关键词定位要改的位置最后生成代码并写回文件。这一步一步全部要依赖工具调用。这里还有一个很直观的对比像若依这类传统代码生成器解决的是“模板化生成”它的逻辑是确定的输入一张表结构输出一套固定 CRUD。AI Agent 解决的是“根据上下文动态生成”它能处理“这个模块风格和旁边那个模块保持一致”这种模糊指令。但代价是AI 生成的结果不一定对你要额外加校验、评审、人工确认。这种差异只有真的写一个 Agent 才能感受到。2. 动手之前先把 Agent 的工作模型拆开2.1 Agent 的本质模型、工具和循环我见过很多关于 Agent 的定义绕来绕去其实本质就三个东西模型负责理解需求、做决策、生成内容。工具负责接触外部世界比如读文件、查数据库、调接口。循环负责把“模型说要调用工具 → 执行工具 → 结果回填给模型 → 模型继续判断”这个过程串起来。普通聊天没有循环模型回答完就结束了。带工具的聊天可以调用一次工具但往往只处理单个步骤。Agent 的核心能力是循环——模型可以多次调用工具每次拿到结果后继续推理直到任务完成。这也解释了为什么很多 Agent 教程看起来简单实际跑起来却问题不断因为循环一旦出现异常比如模型反复调用同一个工具、工具返回格式不匹配、上下文被撑爆整个任务就卡住了。这些坑在后面都会遇到。2.2 编程助手内部到底在做什么Claude Code 这类编程 Agent 看起来神奇去掉外壳后它的工作流其实是可以拆解的理解需求用户说“给 UserController 加一个分页查询接口”。探查项目列出目录结构找到 Controller、Service、Mapper 在哪。阅读上下文读取相关文件搞清现有代码风格、分层方式、字段命名。生成方案决定新增哪个方法、改哪个文件、要不要新建 DTO。执行修改把代码写进文件再给出验证说明。每一步都对应一个工具能力。列目录是工具读文件是工具搜索关键词是工具写文件也是工具。所谓“手写一个 Claude Code”不是要去复制它的全部功能而是把这个工作流抽象出来用 Spring AI 的组件重新实现一遍。理解了这个模式你甚至可以把它改造成别的领域的 Agent比如数据库运维 Agent 或者接口测试 Agent。2.3 工作流到 Spring AI 组件的映射对应关系其实很清晰ChatClient 负责对话与工具编排。Tool 注解方法负责外部操作。entity(Class)负责把结果转成结构化对象。MessageChatMemoryAdvisor 负责多轮记忆。事件监听机制负责观察每一步的工具调用。这里要注意一点Spring AI 不同版本的 API 会有调整类名和包名可能变化。所以后面代码里我写的是常见写法真实落地时要以你引入的具体版本为准。理解组件之间的关系比背一个 API 重要得多。3. 用 Spring AI 2.0 手写最小可运行的代码助手3.1 环境准备和最小依赖先准备一套能跑的环境建议如下JDK 17 及以上。Spring Boot 3.x。Spring AI 2.x具体版本自己确认不要照抄网上过时的版本号。一个可用的模型服务支持 OpenAI 兼容协议即可。Maven 依赖通常长这样dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version替换成你实际使用的 Spring AI 版本/version /dependency如果你用的是国内模型服务也可以先看它是否提供 OpenAI 兼容接口。Spring AI 的一大好处就是模型抽象统一兼容协议的模型服务大多能通过配置接入。配置文件里核心就三个变量spring: ai: openai: base-url: ${AI_BASE_URL:} api-key: ${AI_API_KEY:} chat: options: model: ${AI_MODEL:}注意base-url、api-key、model必须以服务商文档为准。模型名称经常是各种诡异报错的源头配置之前先确认准确名称。3.2 先搭出项目骨架我建议把代码组织成下面这个结构方便后面扩展src/main/java/com/example/codeagent/ CodeAgentApplication.java config/AgentConfig.java agent/CodeAssistantTools.java agent/CodeAgentService.java agent/CodeChangeResult.java src/main/resources/application.ymlCodeAssistantTools放工具方法CodeAgentService放对话编排CodeChangeResult放结构化输出实体。一个小项目把层分开后面加功能不会乱。3.3 用 Tool 定义代码助手的能力代码助手最基础的工具就四个列出目录、读取文件、搜索关键词、写入文件。用 Spring AI 的Tool注解可以直接把普通 Java 方法暴露给模型Component public class CodeAssistantTools { Tool(description 列出指定目录下的文件返回相对路径列表) public String listFiles(String directory) { // 用 Files.list 遍历目录过滤掉 target、.git 等目录 return file1.java, file2.java; } Tool(description 读取指定文件的文本内容文件大小不能超过 50KB) public String readFile(String path) { // 用 Files.readString 读取先检查文件大小 return 文件内容; } Tool(description 在指定目录下搜索包含关键词的文件返回文件路径和匹配行) public String searchKeyword(String directory, String keyword) { // 递归遍历忽略构建产物目录 return UserController.java: 第42行, 包含 list(); } Tool(description 将内容写入指定文件若文件不存在则创建仅允许写入白名单目录) public String writeFile(String path, String content) { // 写文件前检查路径是否在白名单内防止模型乱写 return 写入成功; } }真正的实现里每个方法都要处理异常、路径校验和大小限制。上面代码只是结构示例。为什么强调这些边界因为模型对路径只有“文本上的理解”没有“文件系统上的感知”。你不在工具层做限制模型就可能把代码写到奇怪的地方。3.4 组装 ChatClient跑通第一个 Agent 调用工具定义好之后把它装进 ChatClient。Spring AI 的 ChatClient.Builder 可以设置默认系统提示词和默认工具Configuration public class AgentConfig { Bean ChatClient codeAssistantChatClient(ChatClient.Builder builder, CodeAssistantTools tools) { return builder .defaultSystem( 你是一个 Java 代码生成助手。你的工作方式 1. 先查看用户要修改的目录结构。 2. 读取相关文件理解现有代码风格。 3. 给出修改方案确认后再写入代码。 4. 写入前先确认路径和内容不要覆盖无关文件。 ) .defaultTools(tools) .build(); } }调用端就非常简洁了Service public class CodeAgentService { private final ChatClient chatClient; public CodeAgentService(ChatClient chatClient) { this.chatClient chatClient; } public String generate(String userRequirement) { return chatClient.prompt() .user(userRequirement) .call() .content(); } }这里有个关键点Spring AI 会在一次调用内自动处理“模型请求工具 → 执行工具 → 结果回填 → 模型继续”的循环直到模型认为任务完成或者达到框架设定的轮数上限。也就是说你写的是一个方法但模型背后可能已经跑了好几轮工具调用。3.5 用一个小 fixture 项目验证第一次跑通千万别直接在真实业务项目上试。先在临时目录里建一个小项目比如只有三四个 Java 文件然后给 Agent 一个可验证的需求在 user 包下查看 UserService 的代码新增一个分页查询方法并写回文件。然后观察日志里发生了什么模型有没有先列出目录有没有去读 UserService有没有按照你设计的工具描述一步步操作第一次跑通重点不是结果多完美而是要确认循环是通的工具调用是连续的。4. 从“能跑通”到“能协作”三个决定成败的细节4.1 结构化输出让模型返回可落地的数据当 Agent 只返回一段 Markdown 代码时它很难接入业务流程。你没法校验“这个结果到底要改哪个文件”也没法把它送进代码评审系统。真正可落地的方案是让模型返回结构化对象。比如定义一个修改结果实体public record CodeChangeResult( JsonPropertyDescription(要操作的文件相对路径) String filePath, JsonPropertyDescription(操作类型create / update / delete) String operation, JsonPropertyDescription(文件新内容如果删除则为空) String content, JsonPropertyDescription(本次修改的简要说明) String description ) {}然后在调用时用.entity()直接转成对象CodeChangeResult result chatClient.prompt() .user(在 UserController 里新增一个分页查询接口) .call() .entity(CodeChangeResult.class);这样模型输出的就不再是一团文本而是一个可以校验、可以入库、可以交给审批流程的 Java 对象。这也是 Spring AI 结构化输出Structured Output的核心价值。这里要特别提醒结构化输出依然是“概率性”的不是 100% 稳定。字段可能缺失枚举值可能不合法路径可能带多余空格。不要因为模型输出了一个对象就完全信任它落地时仍然要在业务层做二次校验。4.2 工具设计比调参更影响效果同一个模型工具设计得好不好效果能差出几倍。根据经验工具设计有四条原则描述要精确。readFile的 description 里要写明支持什么路径、有没有大小限制、返回什么格式。模型是靠描述来理解工具的描述含糊调用就含糊。粒度要适中。一个工具只做一件事。如果你把“列目录 读文件 写文件”合并成一个工具模型反而不知道该怎么用。写操作要加边界。所有能改文件、删数据的工具都要有路径白名单、参数校验和确认机制。宁可让模型多问一句也不能让它乱写。返回值要标准化。工具返回字符串最好有固定格式比如“成功/失败 结果摘要”模型才好判断下一步怎么做。有人会花大把时间调 temperature、top_p而忽略工具描述里的一个错别字。实际上工具调用出错、循环卡死绝大多数是工具设计问题。4.3 上下文管理代码助手最大的隐形瓶颈代码助手最容易崩的地方不是模型不够聪明而是上下文装不下整个项目。一个大型 Spring Boot 项目有几百个文件代码总量可能早就超出模型的上下文窗口。如果 Agent 每次都试图读全部文件轻则报错重则关键信息被截断模型开始胡编。实际策略通常是这几种按需读取先列目录再根据路径精准读取相关文件。限制单文件大小大文件只读关键片段。搜索代替扫描用searchKeyword定位关键词而不是全量读文件。先规划再动手让模型先输出一个“本次要查看哪些文件”的计划确认后再执行。这背后是一个更通用的方法论Agent 的上下文是稀缺资源所有设计都要围绕“如何用最小的 token 拿到最有用的信息”。这也是很多 Demo Agent 和真正能落地的 Agent 之间的分水岭。5. 常见问题排查链路从报错到结果不对5.1 先按五层逐层排查遇到 Agent 问题不要急着改 prompt。按下面五层顺序排查效率最高看现象是报错、卡住、没输出还是输出了但结果不对不同现象指向不同层次。看输入用户需求是否清晰文件路径、格式、参数值是否合法看环境依赖版本、网络连通性、API Key 权限、资源占用是否正常看参数模型名、base-url、timeout、最大工具调用轮数、输出解析配置是否对看工具边界工具描述是否准确返回格式是否匹配路径白名单是否挡住了合法操作5.2 典型错误与其原因实际开发中下面几类报错最常出现报错/现象常见原因处理思路模型名不被识别配置了服务商不存在的模型名或版本不支持去官方文档确认模型名称注意区分不同服务的命名规则Agent 执行提供方超时模型服务负载高、网络慢、单次调用耗时长先确认服务和网络再考虑调大超时时间最后优化工具调用次数工具调用循环卡住模型反复调用同一个工具或工具返回让模型无法判断结果检查工具返回格式限制最大轮数简化系统提示词结构化输出解析失败模型返回的 JSON 与实体字段不匹配先看原始响应再检查字段描述是否清晰、类型是否匹配文件写错位置路径校验缺失模型拼接了错误路径用白名单限制根目录执行前打印日志并人工确认这些错误背后的共同点是模型的行为具备不确定性。你没法保证它每次都走同一条路能做的是通过工具约束、格式约束和流程约束把不确定性限制在可控范围内。5.3 验证 Agent 是否正常的方法我习惯用三步验证法固定 fixture准备一个只有几个文件的测试项目用同一句需求反复跑观察输出是否稳定。看工具日志每次调用都打印工具名、入参、返回结果摘要。排错时先看模型每一步做了什么而不是只看最终结果。做 diff 验证Agent 写回文件后用代码评审工具对比改动确认没有覆盖无关内容。如果你的 Agent 能稳定通过这三步再说下一步优化不迟。6. 这个例子做完之后下一步怎么办6.1 它能做什么又不能做什么一个手写的代码生成助手能胜任的任务边界其实很清楚适合做什么不适合做什么批量生成可验证的模板代码直接放开写权限接管生产仓库跨文件修改前先输出改造方案完全自动化、无人评审的代码生成把重复的编码任务变成可审计流程复杂业务逻辑的一次性完整生成教学、原型验证、Agent 机制研究替代人做架构和技术选型决策理解边界比放大功能更重要。这个 Agent 的真正价值不在于“能写代码”而在于“把编码任务变成了可观察、可控制、可审计的流程”。6.2 从 Demo 到生产需要补的工程能力如果要把这个 Demo 推进到生产环境还差几块关键拼图权限模型写文件、执行命令等危险操作单独授权不能让模型自由决定。审批环节生成结果先进 MR/PR走代码评审再合并。日志与追踪记录每一次工具调用、输入输出摘要支持审计和回溯。预算与限流控制单次任务的最大调用轮数、token 消耗和并发量。沙箱环境让 Agent 在隔离环境里执行避免污染本地或生产数据。质量校验接入编译、单元测试、静态检查自动验证生成代码。这些能力单独看都不复杂但组合起来才是生产级 Agent。很多人只看到 Agent“自动写代码”的神奇没看到真正落地的项目里80% 的工作量都在这些工程细节上。6.3 长期价值Agent 正在成为 Java 后端的常规能力国内社区里Spring AI Alibaba 这类项目也在快速补齐 Agent 编排能力比如图编排Graph和更上层的开发框架。这也反映出一个趋势Agent 开发正在从“研究员的玩具”变成“Java 后端的常规能力”。就像几年前的分布式、缓存、消息队列一样理解 Agent 的工作机制会逐渐成为后端开发的基本功。手写一个 Agent目的从来不是替代 Claude Code而是理解它。理解了模型、工具、循环三者如何协作理解了结构化输出和上下文管理的边界以后无论用 Spring AI 原生的方式还是接某个更上层的 Agent 框架你都只是在换一层壳底层逻辑没有变。如果这篇文章只保留一句建议那就是不要先追求复杂框架先用 Spring AI 2.0 写一个最小可运行的代码生成助手把“模型调工具、工具回结果、循环到完成”这个全链路亲手跑通。这个最小闭环是你后续所有 Agent 能力的起点。
返回列表