乐于分享
好东西不私藏

一个用Spring生态打造的本地化AI助手:开源项目JavaClaw的完整解析

一个用Spring生态打造的本地化AI助手:开源项目JavaClaw的完整解析

过去两年,AI Agent领域的聚光灯几乎都打在Python生态上。LangChain、AutoGPT、OpenClaw……这些明星项目让Python开发者如鱼得水。

但Java开发者呢?

很多人默认:要做AI,必须转Python。

JavaClaw项目告诉我们:不用转。

它的核心理念很直白:构建AI Agent不需要发明新轮子。Java生态里早已有所有需要的零件:Spring AI(LLM集成)、JobRunr(可靠任务调度)、Spring Events(多端消息路由)、Spring Modulith(模块化架构)。你只需要像“连接点”一样把它们拼起来。

这就是JavaClaw(原名ClawRunr)的诞生背景:一个纯Java写成的、运行在你自己设备上的AI Agent。

它已经能做到

  • 在Telegram或浏览器里跟它聊天(多端支持)
  • 让它“每天上午9点整理今天待办”(可靠调度 + 自动重试)
  • 教它新技能:往workspace/skills/丢一个SKILL.md文件,即刻生效
  • 连接MCP工具,读写文件,执行Shell命令——数据全部留在本地

发布不到一周,600+ GitHub Star,社区已经自发写了插件。

这说明什么?Java开发者对“本地优先、可扩展AI Agent”的需求,一直都在。我们只是缺一个把零件拼起来的示范。


构建AI Agent必须解决的4个核心问题

在深入代码之前,我们先建立全局认知。一个真正的AI Agent,远不止是“把用户问题转发给LLM”。它至少需要解决以下4个核心问题:

核心问题
通俗解释
Java生态组件
JavaClaw中的实现
1. 如何让Agent“开口说话”?
调用LLM、管理对话上下文、根据用户意图调用工具
Spring AI DefaultAgent

(20行代码封装ChatClient
2. 如何让Agent“耳听八方”?
同时接收Telegram、WebSocket、Discord等多端消息,响应原路返回
Spring Events Channel

接口 + ChannelMessageReceivedEvent事件驱动
3. 如何让Agent“靠得住”?
处理“每天早上8点总结邮件”这类定时任务,失败自动重试、可观测
JobRunr @Job(retries = 3)

 + 内置Dashboard
4. 如何让Agent“长得大”?
社区能方便地扩展新功能(新Channel、新Tool),不改核心代码
Spring Modulith base

 → app → plugins模块化分层

整体架构速览

JavaClaw的项目结构本身就对应了这4个问题的答案:

JavaClaw/├── base/           # 核心模块:Agent、Tasks、Tools、Channels、Config│                     → 解决“如何让Agent工作”(问题1、2、3的核心接口)├── app/            # 应用模块:Spring Boot入口、Onboarding、Chat UI│                     → 解决“如何让用户用起来”└── plugins/        # 插件模块:Telegram、Discord等具体实现│                     → 解决“如何长得大”(问题4)

核心组件一:Spring AI,让Agent“开口说话”

让Agent具备“智能”的第一步,当然是能调用LLM。JavaClaw没有自己封装OpenAI/Anthropic API,而是直接用了Spring AI——Spring生态的LLM抽象层。

20行代码的Agent核心

@ComponentpublicclassDefaultAgentimplementsAgent{privatefinal ChatClient chatClient;publicDefaultAgent(ChatClient chatClient){this.chatClient = chatClient;    }@Overridepublic String respondTo(String conversationId, String question){return chatClient                .prompt(question)                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))                .call()                .content();    }}

关键设计

  • Provider无关ChatClient是Spring AI的抽象,改一行配置就能从OpenAI切到Ollama
  • 对话记忆内置ChatMemory自动管理上下文
  • 极简抽象:Agent只暴露一个respondTo()方法

提示词管理:用户可编辑的System Prompt

String agentPrompt = workspace.createRelative("AGENT.md")        .getContentAsString(StandardCharsets.UTF_8)        + workspace.createRelative("INFO.md")        .getContentAsString(StandardCharsets.UTF_8);
  • AGENT.md:系统指令(角色、行为准则)
  • INFO.md:环境上下文(自动注入,如当前时间)

用户修改文件即可定制Agent行为,无需重启。

工具注册:让Agent“有手有脚”

chatClientBuilder    .defaultSystem(p -> p.text(agentPrompt))    .defaultToolCallbacks(mcpToolProvider.getToolCallbacks())    .defaultToolCallbacks(SkillsTool.builder()        .addSkillsDirectory(skillsDir.toString()).build())    .defaultTools(        TaskTool.builder().taskManager(taskManager).build(),        ShellTools.builder().build(),        FileSystemTools.builder().build(),        SmartWebFetchTool.builder(...).build()    )

@Tool注解的方法会被Spring AI自动发现,LLM根据对话决定何时调用。


核心组件二:Spring Events,让Agent“耳听八方”

一个AI Agent只支持单一聊天界面显然不够用。JavaClaw用Spring Events实现了一套优雅的多端抽象。

Channel接口:统一所有通信渠道

publicinterfaceChannel{default String getName(){return getClass().getSimpleName();    }voidsendMessage(String message);}

事件驱动:解耦消息接收和处理

// 收到消息时发布事件applicationEventPublisher.publishEvent(new ChannelMessageReceivedEvent("telegram", userMessage, chatId));

ChannelRegistry:记住“从哪来”,回哪去

@ServicepublicclassChannelRegistry{public Channel getLatestChannel(){// 返回最后活跃的Channel,用于响应原路返回    }}

工作流程

  1. Telegram收到消息 → 发布ChannelMessageReceivedEvent
  2. 事件被捕获 → 记录“最后活跃渠道”
  3. Agent处理请求(不关心消息来源)
  4. 响应 → 从最后活跃渠道发送回去

Agent核心代码完全不知道消息来自哪里。


核心组件三:JobRunr,让Agent“靠得住”

如果说Spring AI让Agent“有脑子”,Spring Events让Agent“有耳朵”,那么JobRunr让Agent“靠得住”。

TaskHandler:一个注解搞定重试和持久化

@Job(name = "%0", retries = 3)  // 核心:自动重试3次publicvoidexecuteTask(String taskId){    Task task = taskRepository.getTaskById(taskId);// ... 执行逻辑// 异常时抛出,JobRunr自动重试}

JobRunr带来的能力

  • 持久化:任务存入数据库,重启不丢失
  • 重试:指数退避策略,最多3次
  • 仪表盘:内置Web UI(localhost:8081),可视化监控
  • 分布式:支持集群部署

三种任务创建方式

// 立即执行taskManager.create("整理笔记""把临时笔记整理到归档文件夹");// 延迟执行taskManager.schedule(LocalDateTime.now().plusHours(1), "提醒开会""下午2点有技术评审");// 周期执行(Cron)taskManager.scheduleRecurrently("0 0 9 * * *""每日站会提醒""每天早上9点发站会通知");

与通用方案的对比

方案
持久化
重试
Dashboard
Cron支持
@Scheduled

 + @Async
Quartz
JobRunr

核心组件四:Spring Modulith,让Agent“长得大”

一个AI Agent要想有生命力,必须能被社区轻松扩展。JavaClaw用Spring Modulith来保障模块边界清晰。

模块化的项目结构

JavaClaw/├── base/           # 定义接口和抽象├── app/            # 组装模块,提供运行入口└── plugins/        # 具体实现(Telegram、Discord等)

依赖方向单向app → base ← plugins

社区扩展案例

作者提到:*”发布三天内,有人写了一个把Bot消息流式传输到Web界面的插件。他们不需要修改Agent核心代码,只需要一个新模块,实现对应的接口。”*


动手跑起来:快速开始指南

克隆与启动

git clone https://github.com/jobrunr/javaclaw.gitcd javaclaw./gradlew :app:bootRun
  • 主应用端口:8080
  • JobRunr Dashboard端口:8081

Onboarding向导

访问 http://localhost:8080/onboarding,7步配置:

  1. 选择LLM Provider(OpenAI/Anthropic/Ollama)
  2. 填写API Key
  3. 定制Agent Prompt(编辑workspace/AGENT.md
  4. 可选配置MCP服务器、Telegram等
  5. 完成

首次对话验证

访问 http://localhost:8080/chat,输入消息:

你好,请帮我列出当前目录下的文件

Agent应该会调用FileSystemTools,返回目录列表。


进阶与调优

动态工具发现

当工具有几十个,提示词过长、LLM容易选错时,启用动态发现:

javaclaw:tools:dynamic-discovery:enabled:truemax-results:8lucene-min-score-threshold:0.25

Skills:运行时添加新能力

创建workspace/skills/groceries/SKILL.md

# 购物清单管理技能## 能力描述你可以用这个技能管理用户的购物清单## 操作指南查看清单:读取 workspace/lists/groceries.md添加物品:追加到文件末尾

Agent会在运行时自动加载,不需要重启。

安全边界提醒

建议在AGENT.md中加入安全约束:

## 安全约束1. 执行Shell命令前,先列出命令内容并请求用户确认2. 不要读取`/etc/``/root/`等系统目录3. 删除文件前,先输出文件内容请求确认

写在最后

  • 不要被“AI Agent必须用Python”的观念限制
  • 善用Spring生态,它们不是为AI设计的,但恰好解决了Agent的需求
  • 从JavaClaw开始,但不止于JavaClaw

如果本文帮你打开了Java+AI的思路,欢迎收藏和分享。

你也在用Java构建AI应用吗?欢迎在留言区分享你的经验。