资讯动态

Spring AI 2.0 + Agent Utils:构建企业级AI编程助手的完整实践

发布时间:2026/8/30 13:34:53 来源:尧图企业网站定制
关于标题里那个“Spring AI 2.0 Agent Utils 开发企业级 Claude Code 项目”我先给一个明确结论如果你以为这是讲 Claude Code 的安装命令、快捷键或 VSCode 插件配置可以直接关掉但如果你是想通过 Java / Spring AI 技术栈在企业内部做一个类似 Claude Code 的 AI 编程助手那下面这套流程值得照着做一遍。Spring AI 2.0 的价值是让 Java 项目接入大模型时不用自己维护一堆 HTTP 调用、JSON 解析和流式响应逻辑。Agent Utils 的价值是把“模型思考、调用工具、观察结果、再次思考”这个 Agent 循环从你手里收走。两个东西组合起来就能用相对少的代码完成一个带工具调用、会话管理、审计日志的企业级编程助手核心骨架。把“Claude Code”拆开看它不是一个黑盒而是一套可复现的工作流模型负责理解任务工具负责操作代码循环负责持续推进。本文就按这个思路从零开始一步步落地。1. 先想清楚企业级 Claude Code 项目核心到底是什么1.1 我们做的不是安装器而是 Agent 工作流Claude Code 这类工具用户看到的只是终端里的一个交互窗口但真正值钱的不是界面而是界面后面的 Agent 工作流。一次典型的编程任务大概是这样的用户提出需求找到项目中所有没被引用的公共方法。模型先把需求拆成步骤扫描目录、读取文件、分析引用关系、给出结论。模型调用工具读文件、搜索关键词、执行 grep 或代码分析命令。工具返回结果模型继续分析。反复多轮直到形成最终答案。这个循环不是一次问答而是“计划、执行、观察、再计划”的迭代过程。用 Spring AI 2.0 做企业级项目核心就是把这条循环接住并让它稳定、可追踪、可控制。1.2 企业级和玩具 Demo 的差距在哪里很多团队都能在半天内跑通一个 ChatGPT 式的问答 Demo但做企业级项目时差距会立刻暴露对话上下文怎么保存重启后还在不在。多个用户同时用会话之间会不会串。工具能不能执行执行权限怎么控制。失败重试、超时、限流怎么做。每个操作能不能追溯到人和时间。这些都不是模型本身的问题而是工程问题。Spring AI 2.0 解决了一部分Agent Utils 又解决一部分剩下的要靠项目结构和管理能力补上。下面用一个表格拆开看维度玩具 Demo企业级项目对话上下文进程内存里存一下数据库持久化支持恢复用户隔离单用户测试多租户/多项目隔离工具调用写死一个方法统一注册、权限校验、审计失败处理报错就结束重试、降级、日志记录性能一次一请求并发控制、队列、限流可观测性没有耗时、Token、调用次数、成功率所以后续每一步都不能只看“能不能跑”还要看“能不能稳定地跑能不能出问题后快速定位”。1.3 技术选型为什么是 Spring AI 2.0 Agent UtilsJava 团队做 AI 应用比较自然的选择是 Spring AI 而不是自己写 SDK。原因很简单模型切换成本低Spring AI 把 OpenAI、Anthropic、兼容网关等都封装成了统一接口。和 Spring Boot 集成好配置、依赖、注入、拦截器都能复用现有体系。ChatClient 支持流式、工具调用、Advisor 链方便加日志和权限。Agent Utils 可以把 Agent 循环抽出来不在业务代码里塞一堆 while 循环。这里要说明一句Spring AI 2.0 的版本更新比较快Agent Utils 在不同版本里的模块名和类名也可能调整。下面所有代码都是演示结构落地时以你实际引入的版本和官方迁移说明为准。2. 全景拆解一个类 Claude Code 的 Agent 应该长什么样2.1 四个核心模块对话入口、模型调用、工具执行、会话与审计一个可企业化的编程助手我习惯拆成四层来看。第一层是入口。可以是 REST API、命令行、WebSocket也可以是内部系统的一个页面。入口层只负责收请求和返回结果不写业务逻辑。第二层是模型调用。用 Spring AI 的 ChatClient 统一封装把用户输入加上系统提示词发给大模型。这一层要处理好流式输出和超时。第三层是工具执行。模型不能直接操作数据库和文件系统它需要“请求”工具。工具层负责真正执行读文件、搜索代码、分析依赖等操作并把结果返回给模型。第四层是会话与审计。每一次请求属于哪个会话每一条工具调用是谁触发的结果是什么都要留下记录。这一层最容易在 Demo 阶段被忽略但企业落地时最重要。四层之间的关系是单向依赖的入口依赖模型层模型层依赖工具层会话审计横跨所有层。2.2 工具不是越多越好关键是“权限边界”很多第一次做 Agent 的团队会把能想到的工具全部注册进去读文件、写文件、执行 shell、调 API、发邮件。结果发现问题很多。工具多模型就容易被干扰。模型可能调错工具、传错参数也可能因为工具太多导致上下文过长。更关键的是安全。我的建议是分几类控制只读工具读文件、查目录、搜索代码、查看 Git 状态。可以放开。写操作修改文件、新增文件。需要明确范围最好限定在指定目录。危险操作执行 shell 命令、删除文件、提交代码。必须二次确认或者干脆不开放。外部系统调用必须经过 API 网关、鉴权、限流。在企业级 Claude Code 项目里Agent 的“能力边界”比“能力范围”更重要。先用最小权限设计工具集再按业务需要逐步放权是最稳妥的方式。2.3 会话和任务的纵向拆分如果只用“用户说一句Agent 回一句”的思路后面代码会越来越乱。我建议一开始就把两个概念分开会话用户和 Agent 之间的一段持续交流包含多轮消息。任务一次请求中Agent 从接入开始到最终输出结束的完整处理过程。会话可以有上下文任务必须能独立追踪。每个任务应该对应一个 taskId工具调用、错误日志、Token 消耗都挂在 taskId 下。这样排查问题的时候就不用在一个巨大的消息表里翻来翻去。3. 从零搭工程环境、依赖和最小可运行骨架3.1 环境准备和检查开始写代码之前先确认基础环境。我在本地实测时一般按这个顺序检查JDK建议 JDK 17 以上Spring AI 2.0 对高版本 JDK 支持更稳。构建工具Maven 3.9 或 Gradle 8。Spring Boot版本不要乱选最好和你引入的 Spring AI 版本官方匹配。版本差距太大会出现类找不到。模型 API Key一个可用的模型平台账号或者企业内部模型网关地址。网络确认应用能访问到模型服务的 base-url。如果走企业网关直接配置网关心跳。这里要注意不要一上来就追最新的快照版本。快照版本可能有新功能但依赖冲突和类变更概率也高。我的习惯是先选一个已经发布的稳定版本跑通后再升级。3.2 创建 Spring Boot 工程并引入依赖假设项目叫enterprise-code-agent使用 Maven 管理。pom.xml 里需要引入 Spring AI BOM再引入对应的模型 starter。下面的坐标是演示结构具体版本号以你实际拉取到的为准。dependencyManagement dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version2.0.0-SNAPSHOT/version typepom/type scopeimport/scope /dependency /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-anthropic/artifactId /dependency !-- 如果 Agent Utils 已经拆分成独立模块以官方坐标为准 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-agent-utils/artifactId /dependency /dependencies如果你的公司使用的是兼容 OpenAI 协议的网关也可以把 Anthropic starter 换成 OpenAI starter。重要的是必须先确定模型服务协议再选依赖。3.3 配置模型客户端配置放在application.yml里。不要硬编码 API Key通过环境变量注入。spring: ai: model: anthropic: api-key: ${ANTHROPIC_API_KEY} model: ${AI_MODEL:}model不要随便填需要填模型平台真正支持的模型名称。你可以用环境变量AI_MODEL来指定这样换模型时不需要重新打包。如果使用兼容 OpenAI 协议的网关配置类似spring: ai: openai: base-url: ${AI_BASE_URL:https://api.example.com} api-key: ${AI_API_KEY} chat: options: model: ${AI_MODEL}这里容易踩的坑是只改了 base-url忘了改模型名。很多网关要求传完整模型名和大模型官方平台的名字不一样配置好后要先用一个最小请求验证。3.4 先跑通不带工具的 Agent 对话不要一开始就接入 Agent Utils 和工具。先写一个最小服务确认 ChatClient 能正常收到模型返回值。Service public class CodeAgentService { private final ChatClient chatClient; public CodeAgentService(ChatClient.Builder builder) { this.chatClient builder.build(); } public String chat(String message) { return chatClient.prompt() .user(message) .call() .content(); } }再暴露一个简单接口RestController public class AgentController { private final CodeAgentService codeAgentService; public AgentController(CodeAgentService codeAgentService) { this.codeAgentService codeAgentService; } PostMapping(/chat) public String chat(RequestBody String message) { return codeAgentService.chat(message); } }启动后用 curl 验证curl -X POST http://localhost:8080/chat \ -H Content-Type: text/plain \ -d 请用一句话介绍 Spring AI如果这一步返回乱码/报错/超时先不要动 Agent 配置优先检查 API Key、模型名、网络连通性。先跑通单轮对话再往上加复杂度。4. 接入 Agent Utils让 Agent 真正开始干活4.1 Agent Utils 的工作单元一次思考、一次工具调用、一次观察Agent Utils 的核心不是帮你创建一个“机器人”而是把 Agent 循环标准化。每一次循环通常包括模型根据当前对话历史判断下一步动作。如果模型决定调用工具Agent 框架会把工具名和参数解析出来。工具实际执行返回结果。结果重新作为消息内容交给模型继续判断。直到模型认为任务完成或达到最大迭代次数。这个循环的好处是业务代码不需要自己维护“模型到底该调用哪个方法”的逻辑。你需要做的是定义好工具并告诉 Agent 这些工具怎么用。4.2 定义第一组工具读文件、查目录、搜索关键字定义工具的时候我建议先做三个最常用的只读工具。第一个是读文件Component public class FileTools { Tool(description 读取项目文件内容path 是相对项目根目录的路径) public String readFile(String path) throws IOException { Path filePath Path.of(path).toAbsolutePath().normalize(); if (!Files.exists(filePath)) { return 文件不存在: path; } return Files.readString(filePath, StandardCharsets.UTF_8); } }第二个是列出目录Tool(description 列出指定目录下的文件和子目录) public String listDirectory(String path) throws IOException { Path dirPath Path.of(path).toAbsolutePath().normalize(); if (!Files.isDirectory(dirPath)) { return 目录不存在: path; } try (StreamPath paths Files.list(dirPath)) { return paths.map(p - Files.isDirectory(p) ? p / : p.toString()) .collect(Collectors.joining(\n)); } }第三个是关键字搜索。示例可以先做一个简单实现Tool(description 在项目文件中搜索关键字返回包含关键字的文件和行号) public String searchKeyword(String keyword, String path) throws IOException { Path root Path.of(path).toAbsolutePath().normalize(); StringBuilder result new StringBuilder(); try (StreamPath paths Files.walk(root)) { paths.filter(Files::isRegularFile) .filter(p - p.toString().endsWith(.java)) .limit(100) .forEach(p - { try (StreamString lines Files.lines(p, StandardCharsets.UTF_8)) { int[] lineNo {0}; lines.forEach(line - { lineNo[0]; if (line.contains(keyword)) { result.append(p).append(:).append(lineNo[0]) .append(:).append(line).append(\n); } }); } catch (IOException e) { // ignore } }); } return result.toString().isBlank() ? 未找到关键字 : result.toString(); }代码里最关键的是两点路径要做标准化避免模型传../跑到项目外返回内容不要太长否则会撑爆上下文。4.3 将工具注册进 Agent 并验证调用链把工具注册到 Agent 的方式在每个版本里略有不同。下面是一个演示结构重点看思路AgentExecutor executor AgentExecutor.builder(chatClient) .tools(fileTools, codeSearchTool) .maxIterations(8) .build(); String result executor.execute( 分析当前项目找出所有包含 TODO 的 Java 文件 );这里的AgentExecutor是演示类名实际落地时以 Agent Utils 提供的核心类为准。只要循环能跑起来模型就会自动判断“需要读目录”“需要搜索关键字”然后调用对应的工具方法。验证调用链是否正常最好的方式不是直接看最终答案而是看中间过程。你可以在工具方法里加日志打印模型传入的参数和返回结果。如果模型没有调用任何工具大概率是工具描述不够清楚或者系统提示词里没有说明“你可以使用工具”。4.4 为什么 maxIterations 是第一个要调的参数Agent 循环最怕两种情况循环太少任务没完成循环太多Token 消耗失控。maxIterations 就是控制循环上限的。我建议第一次先设 5 到 10让 Agent 做简单任务。跑几次后看日志里实际调用了多少次工具再调整。如果任务是“统计项目代码量”5 次可能够如果是“重构一个模块”20 次都可能不够。但企业环境里我一般不会设置成无限循环。宁可一次任务没做完返回部分结果也不能让一个请求吃掉大量成本、卡住整个服务。注意如果模型连续调用同一个工具两次以上还没进展不要急着调大 maxIterations先看工具返回结果和 prompt 有没有歧义。5. 给 Agent 增加企业级约束会话持久化和审计5.1 会话应该存数据库而不是只放内存Demo 阶段把对话放在内存里没问题但企业应用一旦重启用户上下文全部丢失没法接受。Spring AI 提供了内存型 ChatMemory也支持扩展持久化实现。我的建议是直接落库。简单起见可以设计三张表表作用conversation会话主表存用户、项目、会话状态message每次请求/响应的消息内容存角色和文本tool_call_log工具调用记录存工具名、参数、结果摘要、耗时会话表要保存用户 ID 和项目 ID确保不同项目之间不会串上下文。message 表要按会话 ID 索引方便恢复上下文。如果消息量太大还需要考虑裁剪。Spring AI 的 MessageWindow 可以按窗口大小保留最近 N 条消息但窗口太大会超 token 限制太小又会丢失历史。具体参数要以你的模型上下文窗口和单条消息长度为准。5.2 审计日志记录什么企业级项目里模型调用不可避免会接触到代码内容、路径、甚至密钥信息。审计日志至少应该包含用户 ID / 调用来源。会话 ID 和任务 ID。模型名称和输入 Token / 输出 Token。工具名称、入参、出参摘要。耗时、成功/失败状态。错误信息和重试次数。特别注意工具返回结果可能很大也可能包含敏感信息。不要直接把完整结果存库存截断后的摘要即可。完整结果可以放到临时文件或对象存储里设置过期时间。5.3 敏感操作怎么做二次确认如果 Agent 只能读文件、查搜索限制会比较清晰。但如果要写文件、执行命令风险就上来了。我的方案是给操作加“审批位”。Agent 遇到写操作时不直接执行而是先返回一条“需要人工确认”的消息。系统管理员在管理端看到操作请求确认后对应工具才真正执行。实现上可以抽象一个接口public interface CommandExecutor { boolean requireApproval(String toolName); String executeAfterApproval(String taskId, String toolName, String params); }这个设计会让 Agent 流程多一个环节但企业落地时非常值得。很多安全事故不是模型不够聪明而是工具权限放得太宽。6. 模型接入的通用套路和参数细节6.1 ChatClient 和 Model 接口的区别Spring AI 中有底层 Model 接口和上层 ChatClient。很多刚接触的人会混淆。Model 接口偏向底层面向某个具体的模型服务比如 AnthropicApi、OpenAiApi。ChatClient 是面向业务的高层 API封装了 prompt、消息历史、Advisor 和工具调用。企业开发里我建议业务代码统一依赖 ChatClient。这样后续换模型只需要改配置和依赖业务代码基本不用动。Agent Utils 也是基于 ChatClient 来工作的。你可以在 ChatClient 外面包 Advisor比如日志 Advisor、Token 统计 Advisor、权限校验 Advisor而不影响 Agent 循环本身。6.2 temperature、maxTokens、topP 到底影响什么模型参数不是越多越好。刚开始只需要关注三个temperature控制随机性。代码生成、结构化输出适合低值比如 0 到 0.2开放聊天可以稍微高一点。maxTokens控制单次输出最大长度。代码任务建议设得大一些否则工具调用还没结束响应就被截断。topP控制采样范围一般不用同时调 temperature 和 topP。我通常保持 topP 默认只调 temperature。这几个参数在不同模型里的取值范围并不完全一致。有的模型 temperature 是 0 到 1有的是 0 到 2。配置前先看模型官方文档不要盲目套用 OpenAI 的经验。6.3 换模型时最容易踩的坑换模型看起来只是改一个配置实际上容易踩几个坑模型名不识别看日志里请求体传的 model 字段很多平台报错都在这里。工具调用格式不兼容不同模型对 function/tool 参数的描述格式不同Spring AI 封装后会有差异但版本不对时会暴露。上下文窗口不同把 Claude 的项目直接换成别的模型可能会因为上下文窗口变小而报错。参数范围不同temperature 范围、maxTokens 范围都要重新确认。我的排查顺序是先看报错请求体再看响应体最后才怀疑代码。90% 的“模型接入失败”问题都出在配置而不是代码。7. 常见报错和排查顺序7.1 401 / 模型名不识别现象最直接调用模型接口返回 401或者提示 model not found。排查顺序确认环境变量API_KEY是否真的注入成功。确认基础配置里的 base-url 是否拼写正确。确认 model 名称和平台支持的名称完全一致包括版本号和后缀。看应用启动日志里有没有加载到配置不要只改配置文件忘了重启。如果模型名是从环境变量读的可以在测试环境先打印一下spring.ai.model相关配置确认没有空格或转义问题。7.2 工具调用失败或返回空工具调用是最容易出问题的一环。常见原因工具描述不准确模型不知道什么时候该用。参数类型不匹配模型传了字符串工具方法需要 Integer。工具返回内容太大被截断。工具内部抛异常但 Agent 框架没把异常转成明确消息。这个阶段我会打开调试日志打印模型完整请求和工具调用参数。如果模型压根没调用工具就改进描述如果调了但返回空就检查工具方法逻辑。注意工具方法里不要返回堆栈信息给模型模型会把错误信息当成正常内容导致后续判断混乱。尽量把异常转成用户能看懂的一句话。7.3 上下文超长Agent 带工具调用后上下文增长很快。工具返回的目录列表、搜索结果、文件内容都可能几百行。如果报错提示超出模型上下文限制顺着这个顺序处理调小工具返回内容搜索结果只返回前 50 条匹配。对文件内容做截断比如每行截断 200 字符总行数限制。清理对话历史只保留最近几轮消息。在系统提示词里限制模型“不要一次性读取大文件”。上下文太长不是靠换更大的模型就能解决的更合理的方式是让 Agent 先把问题定位到具体文件和行号再读文件片段。7.4 Agent 一直循环或超时如果 Agent 反复调用同一个工具或者卡在某个步骤不结束先看 maxIterations 是否太小或太大。太小会提前结束但经常没完成太大会让无意义循环拖很久。还要看工具返回结果。如果工具返回“文件不存在”这类错误模型应该调整路径而不是继续调同一个参数。如果模型反复用同一个错误参数说明提示词里没有给出“遇到错误后换一种方式”的指令。最后还要加超时和熔断。Agent 循环整体耗时可能会到几十秒甚至几分钟不能让 HTTP 请求一直挂着。建议给每次工具调用设置超时给整个任务设置最大时长。8. 从 Demo 到生产还要补哪些东西8.1 批量任务和并发控制本地跑通之后很多人第一反应就是开接口给团队用。这时要控制并发。Agent 请求不是普通 HTTP 请求它会连续调用模型接口多次。一次并发 10 的 Agent 请求可能相当于模型接口 50 次调用。如果限流没有提前设计后端的模型网关和成本账单都会很难看。我的建议是用线程池限制并发数而不是让请求无限进入。加一个任务队列超过并发上限的任务排队处理。同一个用户同时只允许一个 Agent 任务。为每个任务设置预算上限Token 消耗超过预算就停止。8.2 RAG让 Agent 理解企业私有代码你可能已经发现简单工具调用只能帮 Agent 看到局部文件。企业级项目里代码量很大Agent 不可能把所有代码读完这时候 RAG 就派上用场。思路是先把企业代码按类、方法、注释拆成小块用向量模型转成向量存入向量数据库。Agent 回答问题前先检索相关代码片段再把检索结果拼进 prompt然后再进入工具循环。Spring AI 提供了向量存储抽象和 EmbeddingModel 接口。落地时可以先用本地文件做测试再切换到 PostgreSQL pgvector 或专业的向量数据库。注意检索结果不要太多一般取 top 5 到 top 10 就足够。8.3 可观测性和验收清单生产环境最怕黑盒。Agent 环节多任何一个环节失败都不容易定位。所以日志结构一定要统一。建议给每个任务打一个taskId所有日志都带上这个 ID。日志内容至少包括每次模型输入的 token 数和输出 token 数。模型调用了哪些工具参数是什么。工具返回的摘要和耗时。最终答案是否正常返回。最后给一份验收清单供自己或团队检查单任务是否多次运行结果稳定。工具权限是否限定在指定目录。会话是否可以恢复不同用户是否隔离。高并发下模型网关是否被打爆。工具超时和失败重试是否正常。审计日志是否完整是否可以查到具体任务链路。模型名称和参数是否通过配置管理不写死在代码里。我自己的习惯是先把第一部分单聊跑稳再把第二个工具接上最后才扩到批量任务和多用户。很多问题不是模型能力不够而是前置环境和输入材料没有处理干净。类 Claude Code 的项目真正难的不是“让模型回答问题”而是“让模型在规则边界内安全地操作代码”。把工具、会话、审计做好这个项目就成功了一大半。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价