在AI辅助编程领域,Cursor、Claude Code等工具引领了开发体验的变革。然而,对于广大Java生态的开发者而言,在拥抱这股浪潮时却面临独特挑战:主流工具多基于Python/TS,与Java项目集成困难;企业级开发所需的Spring Boot、Maven、微服务等框架支持不足;以及出于代码安全考虑,对私有化、可深度定制的本地解决方案存在迫切需求。
Jimi项目正是为解决这些痛点而生——它是一个基于纯Java技术栈构建的功能完整、可扩展、面向企业级的AI智能代理系统。
核心设计理念与价值
Jimi致力于为Java开发者提供一个“原生友好”的AI编程伙伴,其核心价值体现在:
- 🎓 教育友好:清晰的代码架构与完整的中文注释,使其本身成为优质的学习资源。
- 🏢 企业级基石:基于Spring Boot 3.2.5构建,支持响应式编程,具备生产级质量。
- 🔌 开放生态:支持MCP协议,可轻松集成各类外部工具与服务。
- 🧩 极致模块化:组件职责明确,像搭乐高一样组合功能,易于扩展和维护。
- 💡 智能知识注入:通过灵活的Skills系统,让AI能够按需获取领域专业知识。
架构之道:清晰的分层设计
Jimi的架构遵循了清晰的分层思想,如同建造智能大厦:基础设施层是坚实的地基,核心引擎层是稳定的骨架,功能模块层(Agent与工具)提供灵活的组件,交互层则是友好的界面。
这种分层带来了显著优势:
- 🔒 底层稳定:消息总线和执行引擎为系统提供可靠基础。
- 🎯 中层灵活:Agent、工具及Skills系统提供了丰富的可插拔功能组件。
- 🚀 上层开放:支持多种大语言模型(LLM)、自定义Agent及外部工具集成。
项目包含8个核心功能域,各司其职:
| 功能域 |
核心模块 |
主要职责 |
设计亮点 |
| 核心引擎 |
JimiEngine, AgentExecutor |
任务执行与调度 |
委托模式,单一职责 |
| Agent系统 |
AgentRegistry, AgentSpecLoader |
Agent管理与加载 |
YAML配置化,模板渲染 |
| 工具系统 |
ToolRegistry, ToolRegistryFactory |
工具注册与执行 |
Spring集成,插件化 |
| LLM集成 |
LLMFactory, ChatProvider |
多模型支持 |
Caffeine缓存,流式响应 |
| Skills系统 |
SkillMatcher, SkillProvider |
知识智能注入 |
关键词匹配,自动激活 |
| 消息总线 |
Wire, WireMessage |
组件解耦通信 |
响应式流,事件驱动 |
| 会话管理 |
Session, Context |
上下文持久化 |
检查点机制,智能压缩 |
| UI交互 |
ShellUI, OutputFormatter |
命令行界面 |
JLine3,彩色输出 |
核心引擎:AgentExecutor 的职责解耦
早期版本中,JimiEngine承担了过多职责。经过重构,采用委托模式进行了清晰的责任划分:
JimiEngine:作为总控入口,负责核心组件的装配与协调。
AgentExecutor:承担主循环调度和执行逻辑,是真正的“引擎心脏”。
让我们深入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);
}
});
});
}
}
设计亮点:
- 响应式编程:基于Reactor的
Mono,原生支持异步非阻塞。
- 检查点机制:每个步骤创建检查点,支持错误恢复与状态回滚。
- 消息总线解耦:通过
Wire发送事件,使UI层与核心逻辑解耦。
- 智能循环控制:内置最大步数限制、连续无工具调用检测等控制逻辑。
流式响应处理:累加器模式
为提升用户体验,Jimi采用累加器模式处理LLM的流式响应,实现实时反馈。
/**
* 流式累加器:用于组装完整的Assistant消息
*/
private static class StreamAccumulator {
StringBuilder contentBuilder = new StringBuilder(); // 累积文本内容
List<ToolCall> toolCalls = new ArrayList<>(); // 累积工具调用
ChatCompletionResult.Usage usage; // Token使用统计
// 用于临时存储正在构建的工具调用
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;
}
技术亮点:
- 实时反馈:每个内容片段立即推送至UI,用户感知延迟极低。
- 容错处理:能处理LLM可能先发送参数、后发送调用ID的异常情况。
- 临时ID策略:当收到参数但缺少ID时,创建临时ID确保数据不丢失。
Agent系统:配置化与多Agent协作
Jimi支持多种专业化Agent协同工作,例如Code Agent负责实现,Review Agent负责审查,Test Agent负责生成测试用例。
配置化设计
Agent采用 YAML配置 + Markdown模板 的方式定义,极大提升了可定制性。
agent.yaml 示例:
name: "Code Agent"
description: "专业的代码实现Agent"
model: null # 继承默认模型
# 可以委托的子Agent
subagents:
- review
- test
# 可用工具列表
tools:
- read_file
- write_to_file
- str_replace_file
- patch_file
- bash
- glob
- grep
- think
# 系统提示词参数(用于模板渲染)
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负责加载和渲染这些配置,利用Apache Commons Text将参数动态注入模板,实现Agent行为的灵活定制。
工具系统:标准化与安全执行
所有工具都实现统一的Tool<P>接口,并通过ToolRegistry进行注册管理。工具作为Spring Bean,支持依赖注入,并以原型(Prototype)作用域避免状态污染。
以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("成功读取文件");
});
}
}
系统还为文件写入、Shell执行等敏感工具设计了审批机制,确保操作安全可控。
LLM集成:统一抽象与缓存优化
通过统一的ChatProvider接口,Jimi抽象了不同LLM提供商(如OpenAI、DeepSeek、Ollama等)的细节。LLMFactory利用Caffeine高性能缓存管理LLM实例,避免重复创建,提升性能。
@Service
public class LLMFactory {
/**
* LLM实例缓存(Caffeine)
*/
private final Cache<String, LLM> llmCache;
public LLMFactory(JimiConfig config, ObjectMapper objectMapper) {
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支持通过环境变量配置(如OPENAI_API_KEY),优先级高于配置文件,提升了安全性。
Skills系统:领域知识的动态注入
Skills系统解决了如何让AI在特定任务中更专业的问题。它摒弃了在提示词中硬编码所有知识的传统做法,采用按需激活、模块化管理的策略。
每个Skill由一个skill.yaml文件定义,包含名称、描述、触发词和Markdown格式的知识内容。
name: "Java Code Review Checklist"
description: "Java代码审查的完整检查清单和最佳实践"
scope: global
# 触发词(用于智能匹配)
triggers:
- code review
- 代码审查
- 代码质量
- quality check
# 技能内容(Markdown格式)
content: |
## Java代码审查清单
### 1. 代码规范
- [ ] 命名符合规范(驼峰、常量大写等)
- [ ] 缩进和格式一致
- [ ] 注释清晰且必要
### 2. 设计原则
- [ ] 遵循SOLID原则
- [ ] 单一职责原则
- [ ] 方法长度合理(建议<50行)
SkillMatcher基于用户输入的关键词进行智能匹配,并使用缓存提升效率。匹配到的Skills由SkillProvider格式化为系统消息,动态注入到对话上下文中,确保AI只获取当前任务最相关的专业知识,节省Token的同时提升了专业性。
MCP协议集成:扩展外部能力
Jimi集成了MCP协议,使其能够以标准化的方式连接外部服务,如数据库、文件系统、Git服务等,极大地扩展了AI Agent的能力边界。通过实现JsonRpcClient接口,Jimi可以方便地与各种MCP服务器通信,调用其提供的工具。
消息总线(Wire):组件通信的枢纽
Wire消息总线基于发布-订阅模式,使用Reactor的Sinks实现,是系统内部解耦的关键。核心组件(如AgentExecutor)将执行步骤、流式内容、工具调用等事件发布到Wire,而UI层订阅这些事件并实时呈现,实现了业务逻辑与交互界面的彻底分离。
技术架构总结与展望
Jimi项目综合运用了多种经典设计模式,如委托模式、工厂模式、策略模式、观察者模式等,构建了一个高内聚、低耦合的系统。其技术栈精选了Spring Boot、Project Reactor、Caffeine、JLine等成熟框架,确保了系统的性能、可维护性和开发体验。
在性能优化上,Jimi采取了多层缓存、响应式并发、上下文智能压缩等策略。展望未来,项目计划在Skills系统智能化、工具生态扩展、Web/IDE集成、引入RAG以及企业级多租户与安全增强等方面持续演进。
结语
Jimi不仅仅是一个AI编程辅助工具,更是一次对“使用Java开发现代AI应用”的深度实践。它证明了Java技术栈完全能够胜任复杂AI系统的构建,并且能与Spring Boot等企业级框架完美融合,提供坚实、可维护的基础。通过开源该项目,作者希望吸引更多Java开发者共同探索AI工程化的可能性,为Java在智能时代的生态繁荣贡献力量。
项目开源地址:https://github.com/Leavesfly/Jimi