Java从0到1构建智能体:我的Agent实践全记录
2025年到2026年,AI Agent 无疑是技术圈最热门的话题之一。Claude Code、OpenCode、Cursor 等工具让"AI 编程助手"成为日常,而 Agent 的概念也从学术论文走进了工程实践——能自主思考、调用工具、完成复杂任务的智能体,正在重塑我们对软件能力的认知。
但有趣的是,目前 Agent 的开源生态几乎被 Python 垄断。LangChain、CrewAI、AutoGPT……这些框架的底层都是 Python。如果你是一个 Java 开发者,想深入理解 Agent 的原理,或者想在 Java 项目中落地 Agent 能力,会发现参考资料少得可怜。
这就是我动手写 agent-demo 的初衷。
我想知道:在 Java 生态里,能不能也构建出一个具备"思考-工具-记忆-技能"完整能力的 Agent 系统?如果能,架构长什么样?有哪些坑?和 Python 生态有什么本质不同?
这篇文章是我从零搭建 Java Agent 的完整记录。不只是一个 Demo 展示,更是整个思考过程和技术决策的复盘。希望给同样在 Java 领域探索 Agent 的开发者一些参考。
📋 项目总览:agent-demo 是什么
agent-demo 是一个基于 Spring AI Alibaba 的智能 Agent 演示项目。它不是一个玩具,而是一个具备完整 Agent 能力栈的工程化实现:多模型路由(通义千问 + 智谱 GLM)、工具调用(天气查询、文件操作、数学运算)、多轮对话记忆、以及核心的 Agent Skill 系统(基于 Anthropic 发布的 Agent Skills 开放标准)。
技术栈如下:
后端框架:Spring Boot 3.4.6 + Spring AI Alibaba 1.1.2.0 + Java 17
AI 模型:通义千问 qwen-plus(复杂任务)+ 智谱 GLM-5.1(简单问答)
工具集成:和风天气 API + 文件系统操作 + 数学运算
Skill 系统:3 个 Skill(weather / calculator / xiaohongshu),遵循三层加载架构
项目提供了 5 个 REST API 接口,以及一个交互式 CLI 客户端(agent-cli.sh),支持三种对话模式:无记忆对话、带记忆对话、智能路由对话。同时允许手动切换模型或恢复自动路由。
架构上,项目遵循一个清晰的层次:HTTP 层(Controller)→ 编排层(AgentService)→ 模型层(QWEN / GLM)→ 工具层(@Tool)→ Skill 系统。这个分层的核心思想是:每个组件只做一件事,通过组合实现复杂能力。
Skill 三层加载架构Tier 1 · 元数据层(常驻内存)name / description / keywords / tools~100 tokens/skill语义/关键词命中 ↓Tier 2 · 指令层(按需加载)SKILL.md 正文 → 注入 System Prompt~1k-5k tokens执行中需要 ↓Tier 3 · 资源层(预留)scripts/ 脚本 + references/ 参考文档— 按需加载,控制 Token 开销 —
👋 第一阶段:Hello Agent
一切从 Spring Initializr 开始。项目使用 Maven 构建,核心依赖只有三个:spring-boot-starter-web 提供 REST 能力,spring-ai-alibaba-starter-dashscope 接入通义千问,lombok 减少样板代码。
第一个 Agent 程序简单得令人惊讶:
@Bean
public ChatClient qwenChatClient(ChatClient.Builder builder) {
return builder
.defaultSystem(“你是一个helpful的助手”)
.build();
}
这个 ChatClient 是 Spring AI 的核心抽象。它封装了模型调用、参数传递、结果解析的全流程,提供了一套 Fluent API,让开发者可以用链式调用构建 Prompt。不需要手动拼接 HTTP 请求,不需要处理 JSON 序列化,一行代码就能发出对话请求。
但如果你以为这只是"调个 API",那就太小看 Agent 了。真正的 Agent 需要三个核心能力:调用工具(Tool Calling)、记住上下文(Memory)、以及按需激活知识(Skill)。接下来的每一阶段,都是在这三个维度上逐步扩展。
🛠️ 第二阶段:让 Agent 有手有脚
模型再聪明,如果只能"聊天",那就只是一个高级聊天机器人。Agent 的本质区别在于:它能调用工具,改变外部世界。
Spring AI 的 @Tool 注解让这件事情变得异常简单。你只需要在一个 Spring Bean 的方法上加上 @Tool 注解,框架就会自动生成对应的 JSON Schema,在模型需要时通过 Function Calling 协议完成调用。
我在 AgentService 中注册了 8 个工具,覆盖了三个典型场景:
🌤️ 天气查询 getWeather(city) — 对接和风天气 API,两步调用:城市名→城市ID→实时天气
📁 文件操作 listDirectory / readFile / writeFile / createDirectory — 完整的文件系统访问能力
➕ 数学计算 add(a, b) — 整数加法(虽简单,但完整演示了工具调用链路)
最让我印象深刻的是 Function Calling 的"思考-行动-观察"循环。当用户问"上海今天天气怎么样"时,模型会:① 理解意图 → ② 决定调用 getWeather("上海") → ③ 等待工具返回原始数据 → ④ 将结构化数据转化为自然语言回复。整个过程模型自主完成,开发者只需要注册工具并提供实现。
这个阶段让我意识到:工具的粒度决定了 Agent 的能力边界。每个工具的定义(参数、返回值、描述),都在告诉模型它能做什么、不能做什么。设计好工具层,就是设计好 Agent 的世界模型。
Tool Calling 执行流程① 用户提问 → 模型理解意图② 模型返回 Function Call 请求③ Spring AI 调用 @Tool 方法④ 模型用工具结果生成回复
🧠 第三阶段:给 Agent 装上记忆
没有记忆的 Agent 是"金鱼"——每次对话都是全新的开始。你告诉它你叫张三,下一轮它就忘了。这在真实场景中是不可接受的。
Spring AI 提供了 ChatMemory 抽象,配合 MessageChatMemoryAdvisor,可以用极少的代码实现多轮对话记忆:
@Bean
public ChatMemory chatMemory() {
return new MessageWindowChatMemory(
“agent-demo”,
new InMemoryChatMemoryRepository(),
20 // 保留最近 20 条消息
);
}
这里有两个关键设计。第一是 窗口大小限制为 20 条消息,防止上下文无限膨胀。第二是 sessionId 隔离,不同会话的记忆完全独立,互不干扰。在测试中,同一 sessionId 的三轮对话连贯无误,不同 sessionId 的切换则完全隔离。
但记忆系统和路由系统有一个微妙的问题:当用户回复模型的追问时(比如模型先问"你在哪个城市",用户再说"上海"),这个"上海"本身没有上下文含义,需要结合上一轮对话才能理解。为此我在 ModelRouter 中加入了一个"追问检测"逻辑——如果上一轮模型回复中包含疑问句相关关键词,即使当前消息很短,也强制走千问(避免路由到无工具的 GLM 导致对话断裂)。
这个细节让我深刻体会到:Agent 系统的难点不在单个功能实现,而在组合时的边界情况。记忆 + 路由 + 工具,三个系统各自独立工作时都没问题,一旦组合起来,到处都是"连接处"的细节。
🔄 第四阶段:双模型路由架构
项目最有趣的设计决策是双模型路由。为什么要搞两个模型?答案很简单:成本和能力的平衡。
通义千问(qwen-plus)能力强、支持工具调用,但需要按 Token 付费。智谱 GLM(GLM-5.1)有免费额度,能力足以处理日常闲聊,但不支持工具调用(或者说,项目初期没有对接它的 Tool Calling 接口)。
于是我设计了一个 ModelRouter,包含 5 条路由规则:
规则 0(最高优先级):命中 Skill → 千问
规则 1:AI 追问(上一轮含疑问词)→ 千问
规则 2:消息长度 > 50 字符 → 千问
规则 3:含复杂关键词(分析/对比/代码等)→ 千问
默认:简单闲聊 → GLM(免费)
路由的执行顺序是:先判断是否命中 Skill(需千问执行工具),再判断是否 AI 追问(需保持千问上下文),再判断消息复杂度和长度,最后兜底走 GLM。同时保留了手动覆盖的能力——用户可以通过 POST /api/model 强制指定任意模型。
这个设计在实践中运行良好。日常的"你好""今天心情不错"等简单对话全部走 GLM,零成本;天气查询、文件操作、技术分析等复杂请求自动路由到千问,保证能力完整。据我的粗略统计,约 60% 的日常对话由 GLM 处理,API 成本降低了 50% 以上。
ModelRouter 路由决策树用户消息规则0 命中Skill?规则1 AI追问?规则2 长度>50?规则3 复杂词?默认 → GLM→ QWEN— 任一规则命中则走千问;全不命中走 GLM —
🎯 第五阶段:Agent Skill 系统(最核心的设计)
如果说工具是 Agent 的"手",记忆是"脑",那么 Skill 就是 Agent 的"专业知识库"。这个设计灵感来源于 Anthropic 在 2025 年 12 月发布的 Agent Skills 开放标准,并被 Claude Code 和 OpenCode 广泛采用。
Skill 的核心思想非常简洁:通过结构化 Prompt 注入,让模型在特定任务上表现得更专业。它不是代码,而是指令——告诉模型在特定场景下应该怎么做。
我参照 Agent Skills 开放标准和 OpenCode 的源码分析,设计了三个核心原则:
原则一:三层加载架构
Tier 1(元数据,常驻)→ Tier 2(指令,按需加载)→ Tier 3(资源,预留)。核心在于"按需加载"——不把所有 Skill 的正文塞进上下文,只在匹配到才注入。
原则二:description 是最重要的字段
它是匹配的唯一依据。OpenCode 由 AI 自主判断,本项目提供了两种匹配器:关键词匹配(零 Token 成本)和 AI 语义匹配(更高准确率)。
原则三:Skill 驱动工具挂载
Skill 声明自己需要哪些工具,激活时自动挂载。这样模型不会在不需要的上下文中看到不必要的工具定义,减少误导。
在标准的 name + description 基础上,我扩展了两个字段:keywords(关键词匹配用)和 tools(声明需要哪些 @Tool 方法)。
name: weather
description: 查询城市天气、温度、天气预报相关问题。当用户询问天气时激活。
keywords: [天气, 温度, 气温, 下雨, 晴天]
tools: [getWeather]
你是一个专业的天气助手。当用户询问天气时:
- 如果用户没有指定城市,先询问城市
- 调用 getWeather 工具获取天气数据
- 将原始数据解析为友好的自然语言描述
- 回复格式:「{城市}今天{天气},气温{温度}°C」
SkillLoader 实现了两阶段加载:启动时只扫描 Frontmatter(Tier 1),匹配到 Skill 后才通过 loadContent() 读取正文(Tier 2),结果缓存到 ConcurrentHashMap。这种方式确保:无论有多少个 Skill,只有被激活的那个才消耗 Token。
两种匹配器各有优劣。KeywordSkillMatcher 简单粗暴——检查用户消息是否包含 keywords 中的词,零 Token 消耗,但覆盖不全。例如用户说"今天适合出门吗",关键词"天气"没出现就不会匹配。AiSkillMatcher 则将所有 Skill 的 description 发给 GLM-4.7-Flash,让模型语义判断,准确率高但每次匹配要消耗少量 Token。两种模式可通过配置切换。
目前项目注册了三个 Skill:weather(天气查询)、calculator(数学计算)、xiaohongshu(小红书文案生成)。每个 Skill 对应一个 SKILL.md 文件,放在 resources/skills/ 目录下。新加一个 Skill 只需要新建目录 + 写 SKILL.md,无需改代码——这是架构设计最让人满意的地方。
Skill 匹配注入流程用户输入消息SkillMatcher(Keyword / AI 语义)无 Skill 命中基础 Prompt 即可命中 SkillloadContent() 注入 Prompt挂载 @Tool
💡 踩坑与思考:那些在文档中找不到的答案
整个开发过程中遇到了不少问题,有些是 Spring AI 文档没覆盖到的,有些是 Java 生态做 Agent 特有的。记录下来,给后来者参考。
1. 为什么 GLM 用 OkHttp 直调,而不是 Spring AI Starter?
Spring AI Alibaba 官方只提供了 DashScope(通义千问)的 Starter,没有智谱 GLM 的官方集成。虽然可以使用 spring-ai-starter-model-zhipuai 社区版,但版本兼容性存在风险。考虑到 GLM 只处理简单的纯对话(不需要工具调用),直接通过 OkHttp 调用其 OpenAI 兼容接口反而更可控:依赖更少、代码更透明、出问题时更容易排查。
2. Skill 触发方式为什么没走"AI 自主调用"?
在 OpenCode 中,Skill 由 AI 自主调用 SkillTool 触发。这需要模型支持工具调用,并且在系统 Prompt 中告诉模型"你有 Skill 可用"。但本项目使用 Spring AI 的 ChatClient,它虽然支持 @Tool,但要让模型理解"还有一个叫 Skill 的特殊工具"需要额外设计,而且会额外消耗 Token。权衡之下,我选择了在 Java 端通过 SkillMatcher 做匹配,再注入 Prompt——实现简单、逻辑透明、不增加 Token 消耗。这是适配 Java 生态的务实选择。
3. 三层加载解决了什么问题?
最开始我打算把所有 Skill 的完整内容拼到一个超大 System Prompt 里,看起来简单粗暴。但算了一笔账:假如有 10 个 Skill,每个平均 2000 字(约 500 Tokens),全部注入就是 5000 Tokens 的常驻开销。而实际每次对话只用到 1~2 个 Skill,90% 的 Token 被浪费了。三层加载的核心价值就是:按需加载,不为不用的能力付费。这个原则在 Agent 系统中极其重要——上下文窗口是有限的,每一寸都很珍贵。
4. 记忆系统的边界在哪里?
当前使用内存存储,重启即丢失。生产环境需要接入 Redis 或数据库做持久化。另一个问题是:当路由规则把对话从 GLM 切换到 QWEN(或反之)时,记忆内容是否能连贯?目前在 route-chat 接口中,两种模型共享同一个 ChatMemory 实例,所以连续性没问题。但这也意味着 GLM 能"看到"千问之前的对话历史——目前没有区分模型之间的记忆隔离,这是一个待改进点。
🌟 为什么值得动手写一个 Agent
写到这里,我想聊聊一个更根本的问题:作为开发者,我们为什么要花时间去写一个 Agent?直接用 ChatGPT、Claude 或者各种 AI 编程助手不好吗?
我的答案是:因为"用"和"造"之间有一条巨大的认知鸿沟。
用 AI 聊天、用 AI 写代码,你学到的是"这个模型能做什么"。但当你自己动手搭建一个 Agent 系统,你要回答的是"模型怎么知道该调用什么工具"、“工具返回的数据怎么变成人话”、“多轮对话的历史存在哪里、怎么取”、“不同任务该用哪个模型”——这些是产品文档里不会写的东西。
这个项目的整个过程,让我对 Agent 有了几个核心认知:
Agent 是"架构问题",不是"模型问题"
很多人以为 Agent 能力取决于模型大小。实际上,GPT-4 不会知道自己该查天气还是该聊天——是代码让它知道的。路由、记忆、工具编排、Skill 注入,这些架构设计决定了 Agent 的上限。
Prompt 工程正在变成"架构工程"
一两年前我们聊"写 Prompt"是指写一段话让模型理解。现在写 Prompt 意味着设计多层注入策略、按需加载、Token 预算管理——它本质上和写后端服务没有区别。
从"调 API"到"造能力",是技术成长的质变
调一个 LLM 的 API 十分钟就能学会。但设计和实现一个 Agent 系统,需要你理解模型特性、API 协议、内存管理、成本优化、系统架构——这些能力在任何技术领域都通用。
回到最初的问题:Java 生态适不适合做 Agent?
我的结论是:完全适合,而且有独特优势。Spring AI 的 ChatClient + @Tool + ChatMemory 三板斧已经覆盖了 Agent 的核心基础设施。Java 的类型安全、成熟的生态(Spring、Maven)、企业级运维能力,是 Python 生态不具备的护城河。如果你的团队以 Java 为主,完全没必要为了"AI"去学一整套 Python 技术栈。
但 Java 生态也有它的短板:Agent 相关的开源工具和文档远不如 Python 丰富,很多模式需要自己从零摸索。这篇文章所记录的,就是这种摸索的过程。希望它能成为 Java 开发者进入 Agent 领域的一块垫脚石。
毕竟,最好的学习方式从来不是读文档,而是动手做一个。
写在最后"Agent 不是模型能力的延伸,而是架构能力的体现。"最好的学习方式从来不是读文档而是动手做一个。
— 一个从北京回到西安、从大厂回归生活的程序员
假如你从2026年开始学大模型,按这个步骤走准能稳步进阶。
接下来告诉你一条最快的邪修路线,
3个月即可成为模型大师,薪资直接起飞。
阶段1:大模型基础

阶段2:RAG应用开发工程

阶段3:大模型Agent应用架构

阶段4:大模型微调与私有化部署

配套文档资源+全套AI 大模型 学习资料,朋友们如果需要可以微信扫描下方二维码免费领取【保证100%免费】👇👇





配套文档资源+全套AI 大模型 学习资料,朋友们如果需要可以微信扫描下方二维码免费领取【保证100%免费】👇👇

更多推荐


所有评论(0)