
在 AI 辅助编程的浪潮中,Cursor、Claude Code 等工具为开发者带来了革命性的体验。但对于 Java 生态的开发者来说,我们面临着一些独特的挑战:
Jimi 是基于纯 Java 技术栈构建的开源 AI 智能代理系统,旨在解决 Java 开发者在 AI 辅助编程中面临的技术栈割裂、企业级需求及私有化部署等痛点。项目采用 Spring Boot 3.2.5,支持响应式编程、MCP 协议集成、Skills 知识注入及多 LLM 模型抽象。通过分层架构设计,实现了 Agent 管理、工具注册、消息总线解耦等功能,并提供命令行交互界面。Jimi 强调模块化与可扩展性,适合企业级场景及教育用途,助力 Java 生态在 AI 时代的复兴。

在 AI 辅助编程的浪潮中,Cursor、Claude Code 等工具为开发者带来了革命性的体验。但对于 Java 生态的开发者来说,我们面临着一些独特的挑战:
Jimi 项目的诞生正是为了解决这些痛点——用纯 Java 技术栈,构建一个功能完整、可扩展、企业级的 AI 智能代理系统。

如果要建造一座智能大厦,我们需要什么?首先是坚实的地基(基础设施),然后是稳定的骨架(核心引擎),再是灵活的功能模块(Agent 和工具),最后是友好的交互界面(UI)。Jimi 的架构设计正是遵循了这样的思路:

这种分层设计带来了显著的优势:
Jimi 共包含 8 个核心功能域,每个域都有明确的职责边界:
| 功能域 | 核心模块 | 主要职责 | 设计亮点 |
|---|---|---|---|
| 核心引擎 | JimiEngineAgentExecutor | 任务执行与调度 | 委托模式,单一职责 |
| Agent 系统 | AgentRegistryAgentSpecLoader | Agent 管理与加载 | YAML 配置化,模板渲染 |
| 工具系统 | ToolRegistryToolRegistryFactory | 工具注册与执行 | Spring 集成,插件化 |
| LLM 集成 | LLMFactoryChatProvider | 多模型支持 | Caffeine 缓存,流式响应 |
| Skills 系统 | SkillMatcherSkillProvider | 知识智能注入 | 关键词匹配,自动激活 |
| 消息总线 | WireWireMessage | 组件解耦通信 | 响应式流,事件驱动 |
| 会话管理 | SessionContext | 上下文持久化 | 检查点机制,智能压缩 |
| UI 交互 | ShellUIOutputFormatter | 命令行界面 | JLine3,彩色输出 |

在早期版本中,JimiEngine 承担了过多的职责(执行循环、工具调用、上下文管理等),导致代码复杂度高、难以测试。经过重构,我们采用了委托模式:

让我们深入 AgentExecutor 的核心逻辑,看看它是如何协调各个组件的:
@Slf4j
public class AgentExecutor {
private final Agent agent;
private final Runtime runtime;
private final Context context;
private final Wire wire;
private final ToolRegistry toolRegistry;
private final Compaction compaction;
private final SkillMatcher skillMatcher;
private final SkillProvider skillProvider;
/**
* 执行 Agent 任务的入口方法
*/
public Mono<Void> execute(List<ContentPart> userInput) {
return Mono.defer(() -> {
// 1. 创建检查点 0(初始状态)
return context.checkpoint(false)
// 2. 添加用户消息到上下文
.flatMap(checkpointId -> context.appendMessage(Message.user(userInput)))
// 3. 启动 Agent 主循环
.then(agentLoop())
.doOnSuccess(v -> log.info("Agent execution completed"))
.doOnError(e -> log.error("Agent execution failed", e));
});
}
/**
* Agent 主循环的单步执行
*/
private Mono<Void> agentLoopStep(int stepNo) {
// 检查是否超过最大步数限制
int maxSteps = runtime.getConfig().getLoopControl().getMaxStepsPerRun();
if (stepNo > maxSteps) {
return Mono.error(new MaxStepsReachedException(maxSteps));
}
// 发送步骤开始消息(通过 Wire 通知 UI)
wire.send(new StepBegin(stepNo, isSubagent, agentName));
return Mono.defer(() -> {
// 1. 检查上下文是否超限,必要时触发压缩
return checkAndCompactContext()
// 2. 创建步骤检查点
.then(context.checkpoint(true))
// 3. 匹配并注入 Skills(智能知识注入)
.then(matchAndInjectSkills(stepNo))
// 4. 执行单步:调用 LLM 并处理响应
.then(step())
.flatMap(finished -> {
if (finished) {
// LLM 返回纯文本,没有工具调用,循环结束
log.info("Agent loop finished at step {}", stepNo);
return Mono.empty();
} else {
// 有工具调用,继续下一步
return agentLoopStep(stepNo + 1);
}
});
});
}
}
设计亮点:
LLM 的流式响应(Streaming)是提升用户体验的关键。Jimi 采用了累加器模式来处理流式数据:
/**
* 流式累加器:用于组装完整的 Assistant 消息
*/
private static class StreamAccumulator {
StringBuilder contentBuilder = new StringBuilder();
List<ToolCall> toolCalls = new ArrayList<>();
ChatCompletionResult.Usage usage;
String currentToolCallId;
String currentFunctionName;
StringBuilder currentArguments = new StringBuilder();
}
/**
* 处理流式数据块
*/
private StreamAccumulator processStreamChunk(StreamAccumulator acc, ChatCompletionChunk chunk) {
switch (chunk.getType()) {
case CONTENT:
// 文本内容:实时累加并通过 Wire 发送到 UI
String contentDelta = chunk.getContentDelta();
acc.contentBuilder.append(contentDelta);
wire.send(new ContentPartMessage(new TextPart(contentDelta)));
break;
case TOOL_CALL:
// 工具调用:逐块累积参数 JSON
handleToolCallChunk(acc, chunk);
break;
case DONE:
// 流结束:记录 Token 使用情况
acc.usage = chunk.getUsage();
break;
}
return acc;
}
技术亮点:

Jimi 支持多种专业化 Agent,每个 Agent 都有明确的职责边界:

Jimi 采用YAML 配置 + Markdown 模板的方式定义 Agent,极大提升了可定制性:
agent.yaml 示例:
name: "Code Agent"
description: "专业的代码实现 Agent"
model: null # 继承默认模型
subagents:
- review
- test
tools:
- read_file
- write_to_file
- str_replace_file
- patch_file
- bash
- glob
- grep
- think
exclude_tools:
- fetch_url
- set_todo_list
system_prompt_args:
CODING_STYLE: "遵循阿里巴巴 Java 开发手册"
JAVA_VERSION: "17"
system_prompt.md 模板:
# 角色定位
你是一位经验丰富的 Java 高级工程师,专注于高质量代码实现。
# 工作环境
- 当前时间:${JIMI_NOW}
- 工作目录:${JIMI_WORK_DIR}
- Java 版本:${JAVA_VERSION}
- 编码规范:${CODING_STYLE}
# 核心能力
1. 根据设计文档完成代码实现
2. 编写符合规范的注释和文档
3. 进行代码重构和优化
4. 处理异常情况和边界条件
# 工作流程
1. 仔细阅读需求和设计文档
2. 分析现有代码结构
3. 实现新功能或修复 Bug
4. 编写必要的注释
5. 委托 Review Agent 进行代码审查
AgentRegistry 负责 Agent 的加载和管理:
@Service
public class AgentRegistry {
@Autowired
private AgentSpecLoader specLoader;
/**
* 加载 Agent 实例,支持模板渲染
*/
public Mono<Agent> loadAgent(Path agentFile, Runtime runtime) {
return loadAgentSpec(agentFile).flatMap(spec -> {
// 渲染系统提示词模板
String systemPrompt = renderSystemPrompt(
spec.getSystemPromptPath(),
spec.getSystemPromptArgs(),
runtime.getBuiltinArgs()
);
// 构建 Agent 实例
Agent agent = Agent.builder()
.name(spec.getName())
.systemPrompt(systemPrompt)
.model(spec.getModel())
.tools(processedTools)
.build();
return Mono.just(agent);
});
}
/**
* 渲染系统提示词(使用 Apache Commons Text)
*/
private String renderSystemPrompt(Path promptPath, Map<String, String> args,
BuiltinSystemPromptArgs builtinArgs) {
String template = Files.readString(promptPath);
// 准备替换参数(内置参数 + 自定义参数)
Map<String, String> substitutionMap = new HashMap<>();
substitutionMap.put("JIMI_NOW", builtinArgs.getJimiNow());
substitutionMap.put("JIMI_WORK_DIR", builtinArgs.getJimiWorkDir().toString());
if (args != null) {
substitutionMap.putAll(args);
}
// 执行字符串替换
StringSubstitutor substitutor = new StringSubstitutor(substitutionMap);
return substitutor.replace(template);
}
}
设计优势:


Jimi 的工具系统采用注册表模式,支持工具的动态注册和执行:

所有工具都实现统一的 Tool 接口:
public interface Tool<P> {
/**
* 工具名称(对应 LLM function calling 的 name)
*/
String getName();
/**
* 工具描述(告诉 LLM 这个工具的用途)
*/
String getDescription();
/**
* 参数类型(用于 JSON Schema 生成)
*/
Class<P> getParamsType();
/**
* 执行工具
*/
Mono<ToolResult> execute(P params);
}
以 ReadFile 工具为例:
@Component
@Scope("prototype")
public class ReadFile extends AbstractTool<ReadFile.Params> {
@Data
public static class Params {
@JsonProperty(required = true)
@JsonPropertyDescription("要读取的文件路径(相对或绝对路径)")
private String path;
@JsonPropertyDescription("起始行号(从 1 开始)")
private Integer startLine;
@JsonPropertyDescription("结束行号")
private Integer endLine;
}
@Override
protected Mono<ToolResult> executeInternal(Params params) {
return Mono.fromCallable(() -> {
Path filePath = resolveWorkPath(params.getPath());
// 验证文件存在且可读
if (!Files.exists(filePath)) {
return ToolResult.error("文件不存在:" + params.getPath());
}
// 读取文件内容
List<String> lines = Files.readAllLines(filePath);
// 处理行范围
if (params.getStartLine() != null || params.getEndLine() != null) {
int start = params.getStartLine() != null ? params.getStartLine() - 1 : 0;
int end = params.getEndLine() != null ? params.getEndLine() : lines.size();
lines = lines.subList(Math.max(0, start), Math.min(lines.size(), end));
}
return ToolResult.success()
.withOutput(String.join("\n", lines))
.withMessage("成功读取文件");
});
}
}
设计亮点:

Jimi 支持多种 LLM 提供商(OpenAI、Kimi、DeepSeek、Qwen、Ollama 等),通过统一的 ChatProvider 接口进行抽象:
public interface ChatProvider {
/**
* 流式生成聊天完成
*/
Flux<ChatCompletionChunk> generateStream(
String systemPrompt,
List<Message> messages,
List<Object> toolSchemas
);
/**
* 获取模型名称
*/
String getModelName();
}
LLMFactory 使用Caffeine 高性能缓存来管理 LLM 实例:
@Service
public class LLMFactory {
/**
* LLM 实例缓存(Caffeine)
*/
private final Cache<String, LLM> llmCache;
public LLMFactory(JimiConfig config, ObjectMapper objectMapper) {
// 初始化 Caffeine 缓存
this.llmCache = Caffeine.newBuilder()
.maximumSize(10) // 最多缓存 10 个模型
.expireAfterAccess(30, TimeUnit.MINUTES) // 30 分钟未使用则过期
.recordStats() // 记录缓存统计
.build();
}
/**
* 获取或创建 LLM 实例
*/
public LLM getOrCreateLLM(String modelName) {
return llmCache.get(modelName, key -> createLLM(key));
}
/**
* 解析 API Key(优先使用环境变量)
*/
private String resolveApiKey(LLMProviderConfig providerConfig) {
// 构建环境变量名称:{PROVIDER_TYPE}_API_KEY
String envVarName = providerConfig.getType().toString().toUpperCase() + "_API_KEY";
String envApiKey = System.getenv(envVarName);
return envApiKey != null ? envApiKey : providerConfig.getApiKey();
}
}
设计优势:

Skills(技能包)解决了一个关键问题:如何让 AI 在特定领域任务中表现得更专业?
传统方法是在系统提示词中硬编码所有知识,但这会导致:
Skills 系统的解决方案:

每个 Skill 由一个 skill.yaml 文件定义:
name: "Java Code Review Checklist"
description: "Java 代码审查的完整检查清单和最佳实践"
scope: global
# 触发词(用于智能匹配)
triggers:
- code review
- 代码审查
- 代码质量
- quality check
# 技能内容(Markdown 格式)
content: |
## Java 代码审查清单
### 1. 代码规范
- [ ] 命名符合规范(驼峰、常量大写等)
- [ ] 缩进和格式一致
- [ ] 注释清晰且必要
### 2. 设计原则
- [ ] 遵循 SOLID 原则
- [ ] 单一职责原则
- [ ] 方法长度合理(建议<50 行)
### 3. 异常处理
- [ ] 异常不被吞掉
- [ ] 使用合适的异常类型
- [ ] 资源正确关闭
SkillMatcher 实现了基于关键词的智能匹配:
@Service
public class SkillMatcher {
private Cache<String, List<SkillSpec>> matchCache;
/**
* 匹配用户输入,返回相关的 Skills
*/
public List<SkillSpec> matchFromInput(List<ContentPart> userInput) {
String inputText = extractText(userInput);
// 尝试从缓存获取
String cacheKey = String.valueOf(inputText.hashCode());
List<SkillSpec> cachedResult = matchCache.getIfPresent(cacheKey);
if (cachedResult != null) {
return cachedResult;
}
// 执行实际匹配
List<SkillSpec> matchedSkills = performMatch(inputText);
// 缓存结果
matchCache.put(cacheKey, matchedSkills);
return matchedSkills;
}
/**
* 执行匹配逻辑
*/
private List<SkillSpec> performMatch(String inputText) {
// 1. 提取关键词
Set<String> keywords = extractKeywords(inputText);
// 2. 从注册表中查找候选 Skills
List<SkillSpec> candidateSkills = skillRegistry.findByTriggers(keywords);
// 3. 计算匹配得分并排序
return candidateSkills.stream()
.map(skill -> new ScoredSkill(skill, calculateScore(skill, keywords, inputText)))
.filter(scored -> scored.score >= scoreThreshold)
.sorted(Comparator.comparingInt(ScoredSkill::getScore).reversed())
.limit(maxSkills)
.map(ScoredSkill::getSkill)
.collect(Collectors.toList());
}
/**
* 计算匹配得分
*
* 计分策略:
* - 触发词精确匹配:+50 分
* - 触发词部分匹配:+30 分
* - 名称匹配:+40 分
* - 关键词在描述中出现:+10 分
*/
private int calculateScore(SkillSpec skill, Set<String> keywords, String fullText) {
int score = 0;
// 检查触发词匹配
for (String trigger : skill.getTriggers()) {
if (keywords.contains(trigger.toLowerCase()) ||
fullText.toLowerCase().contains(trigger.toLowerCase())) {
score += 50;
}
}
return Math.min(score, 100);
}
}
SkillProvider 负责将匹配的 Skills 注入到上下文:
@Service
public class SkillProvider {
/**
* 注入 Skills 到上下文
*/
public Mono<Void> injectSkills(Context context, List<SkillSpec> skills) {
// 过滤已激活的 Skills
List<SkillSpec> newSkills = skills.stream()
.filter(skill -> !context.getActiveSkills().contains(skill))
.collect(Collectors.toList());
if (newSkills.isEmpty()) {
return Mono.empty();
}
// 格式化 Skills 为系统消息
String skillsContent = formatSkills(newSkills);
Message skillsMessage = Message.system(skillsContent);
// 添加到上下文并记录激活的 Skills
return context.appendMessage(skillsMessage)
.then(context.addActiveSkills(newSkills));
}
/**
* 格式化 Skills 为 Markdown 文本
*/
private String formatSkills(List<SkillSpec> skills) {
StringBuilder sb = new StringBuilder();
sb.append("<system type=\"skills\">\n\n");
sb.append("以下技能包已根据当前任务自动激活,请在执行任务时遵循这些专业指南:\n\n");
for (int i = 0; i < skills.size(); i++) {
SkillSpec skill = skills.get(i);
sb.append("### ").append(i + 1).append(". ").append(skill.getName()).append("\n\n");
sb.append(skill.getContent()).append("\n\n");
if (i < skills.size() - 1) {
sb.append("---\n\n");
}
}
sb.append("</system>");
return sb.toString();
}
}
Skills 系统的优势:

MCP(Model Context Protocol)是一个标准化的 AI 工具调用协议,允许 AI 模型与外部服务进行结构化通信。Jimi 通过 MCP 集成,可以:

/**
* JSON-RPC 客户端接口
*/
public interface JsonRpcClient extends AutoCloseable {
/**
* 初始化连接
*/
MCPSchema.InitializeResult initialize() throws Exception;
/**
* 获取工具列表
*/
MCPSchema.ListToolsResult listTools() throws Exception;
/**
* 调用工具
*/
MCPSchema.CallToolResult callTool(String toolName, Map<String, Object> arguments)
throws Exception;
}
HTTP 实现示例:
public class HttpJsonRpcClient implements JsonRpcClient {
private final WebClient webClient;
@Override
public MCPSchema.CallToolResult callTool(String toolName, Map<String, Object> arguments)
throws Exception {
JsonRpcRequest request = JsonRpcRequest.builder()
.method("tools/call")
.params(Map.of(
"name", toolName,
"arguments", arguments
))
.build();
return sendRequest(request, MCPSchema.CallToolResult.class);
}
}
{
"name": "database-server",
"description": "PostgreSQL 数据库查询服务",
"type": "http",
"config": {
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer ${DB_API_KEY}"
}
}
}

Wire 消息总线通过发布 - 订阅模式实现组件解耦:

/**
* Wire 消息总线接口
*/
public interface Wire {
void send(WireMessage message);
Flux<WireMessage> asFlux();
void complete();
}
/**
* Wire 实现(基于 Reactor 的 Sinks)
*/
public class WireImpl implements Wire {
private final Sinks.Many<WireMessage> sink;
public WireImpl() {
// 使用 Multicast 支持多订阅者
this.sink = Sinks.many().multicast().onBackpressureBuffer();
}
@Override
public void send(WireMessage message) {
sink.tryEmitNext(message);
}
@Override
public Flux<WireMessage> asFlux() {
return sink.asFlux();
}
}
// 步骤开始消息
public class StepBegin implements WireMessage {
private int stepNumber;
private boolean isSubagent;
private String agentName;
}
// 内容部分消息(LLM 流式输出)
public class ContentPartMessage implements WireMessage {
private ContentPart contentPart;
}
// 工具调用消息
public class ToolCallMessage implements WireMessage {
private ToolCall toolCall;
}
// Skills 激活消息
public class SkillsActivated implements WireMessage {
private List<String> skillNames;
private int count;
}
public class ShellUI {
private void subscribeWire() {
wireSubscription = wire.asFlux()
.subscribe(this::handleWireMessage);
}
private void handleWireMessage(WireMessage message) {
if (message instanceof StepBegin stepBegin) {
printStatus("🤔 Step " + stepBegin.getStepNumber() + " - Thinking...");
} else if (message instanceof ContentPartMessage contentMsg) {
// 实时打印 LLM 输出
printAssistantText(contentMsg.getContentPart().getText());
} else if (message instanceof ToolCallMessage toolCallMsg) {
// 显示工具调用
toolVisualization.onToolCallStart(toolCallMsg.getToolCall());
} else if (message instanceof SkillsActivated skillsMsg) {
printInfo("💡 激活 Skills: " + String.join(", ", skillsMsg.getSkillNames()));
}
}
}

# 1. 启动 Jimi
./scripts/start.sh
# 2. 查看帮助
/help
# 3. 开始对话
请帮我分析 src/main/java 目录下的代码结构
# 4. 使用专业 Agent
/agent code
请帮我实现一个用户注册功能
1. 配置 Skills 功能
编辑 application.yml:
jimi:
skill:
enabled: true
auto-match: true
matching:
score-threshold: 30
max-matched-skills: 5
2. 使用 YOLO 模式(自动批准)
./scripts/start.sh --yolo
3. 指定工作目录
./scripts/start.sh --work-dir /path/to/your/project
4. 会话恢复
# 恢复上次会话
./scripts/start.sh --resume
# 使用指定会话
./scripts/start.sh --session my-project
创建 agents/my-agent/agent.yaml:
name: "My Custom Agent"
description: "我的专属 Agent"
subagents:
- code
- review
tools:
- read_file
- write_to_file
- bash
system_prompt_args:
MY_CUSTOM_PARAM: "自定义参数值"
创建 agents/my-agent/system_prompt.md:
你是一位专业的${MY_CUSTOM_PARAM}助手。当前工作目录:${JIMI_WORK_DIR}
...
启动时使用:
./scripts/start.sh --agent my-agent
@Component
@Scope("prototype")
public class MyCustomTool extends AbstractTool<MyCustomTool.Params> {
@Data
public static class Params {
@JsonProperty(required = true)
@JsonPropertyDescription("参数描述")
private String param1;
}
@Override
public String getName() {
return "my_custom_tool";
}
@Override
public String getDescription() {
return "我的自定义工具";
}
@Override
protected Mono<ToolResult> executeInternal(Params params) {
return Mono.fromCallable(() -> {
// 工具逻辑实现
return ToolResult.success()
.withOutput("执行结果")
.withMessage("执行成功");
});
}
}
在 ToolRegistryFactory 中注册:
public ToolRegistry createStandardRegistry(...) {
// ... existing code
registry.register(createMyCustomTool());
return registry;
}

| 设计模式 | 应用位置 | 优势 |
|---|---|---|
| 委托模式 | JimiEngine → AgentExecutor | 职责分离,易于测试 |
| 工厂模式 | LLMFactory, ToolRegistryFactory | 对象创建与使用解耦 |
| 策略模式 | ChatProvider 多实现 | 支持多种 LLM 提供商 |
| 注册表模式 | ToolRegistry, AgentRegistry | 动态注册和查找 |
| 观察者模式 | Wire 消息总线 | 组件解耦通信 |
| 模板方法 | AbstractTool | 复用公共逻辑 |
| 原型模式 | Spring Prototype Bean | 避免状态污染 |
1. 多层缓存:
2. 并发执行:
3. 上下文压缩:
4. 资源管理:

Jimi 项目不仅仅是一个 AI 代理工具,更是一次对'用 Java 做 AI'的深度探索。通过这个项目,我们证明了:
如果你也对 AI 感兴趣,不妨从 Jimi 开始:
感谢以下开源项目为 Jimi 提供的灵感和支持:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online