用 Java 复刻 Claude Code:一个终端编程助手的实现

125 阅读10分钟

用 Java 复刻 Claude Code:一个终端编程助手的实现

一个基于 LangChain4j 的智能编程助手🐕柯基 Code,能实现简易编程功能,复刻 Claude Code确实是我标题党啦......

1. 项目概述

KejiCode(柯基Code)是一个基于 LangChain4j 框架的简易智能终端编程助手。

  • 自动化编程任务:创建文件、编辑代码、搜索内容
  • 智能工具链:AI 自主决定调用哪些工具及调用顺序
  • 安全可控:所有操作限制在指定工作目录内
  • 多模型支持:适配 Claude、GPT-4、通义千问等主流 AI

下面是整个项目用到的一些技术栈

技术版本选型理由
Java17稳定、生态成熟、企业级支持
LangChain4j0.35.0Java 生态最佳 AI 集成框架,原生支持工具调用
JLine3.24.1强大的终端交互库,支持命令补全、历史记录
Jackson2.16.0高性能 JSON/YAML 处理
Maven3.x依赖管理、构建自动化

2. 功能展示

  1. 启动项目

  2. 常见命令/help/model/version/params temperature=0.5/reset等,具体使用方式可参照/help

  3. 进行第一轮对话,让它写一个"图书管理系统",这里用的模型是 qwen-max

    请用纯java写一个图书管理系统,数据存存使用caffine,项目管理使用maven,可通过命令行对图书进行增删改查,项目目录为:D://eacape//keji-book-system
    

    终端输出内容

    > 请用纯java写一个图书管理系统,数据存存使用caffine,项目管理使用maven,可通过命令行对图书进行增删改查,项目目录为:D://eacape//keji-book-system
    处理用户消息: 请用纯java写一个图书管理系统,数据存存使用caffine,项目管理使用maven,可通过命令行对图书进行增删改查,项目目录为:D://eacape//keji-book-system
    ⚙️  创建文件: D://eacape//keji-book-system/pom.xml
    成功写入 1127 字节到 pom.xml
    ✅ 完成
    ⚙️  创建文件: D://eacape//keji-book-system/src/main/java/com/keji/book/Book.java
    成功写入 1133 字节到 src\main\java\com\keji\book\Book.java
    ✅ 完成
    ⚙️  创建文件: D://eacape//keji-book-system/src/main/java/com/keji/book/BookManager.java
    成功写入 3526 字节到 src\main\java\com\keji\book\BookManager.java
    ✅ 完成
    ⚙️  创建文件: D://eacape//keji-book-system/src/main/java/com/keji/book/Main.java
    成功写入 176 字节到 src\main\java\com\keji\book\Main.java
    ✅ 完成
    ⚙️  创建文件: D://eacape//keji-book-system/.gitignore
    成功写入 64 字节到 .gitignore
    ✅ 完成
    省略好多日志....
    ​
    ✨ 操作完成
    ​
    ​
    项目已成功创建在 `D://eacape//keji-book-system` 目录下。这个简单的图书管理系统使用了 Caffeine 作为内存缓存来存储书籍数据,并通过 Maven 进行依赖管理。系统支持从命令行进行增删改查操作。
    ​
    以下是项目的主要文件结构:
    ​
    - `pom.xml`: Maven 的配置文件,定义了项目的基本信息和依赖。
    - `src/main/java/com/keji/book/Book.java`: 定义了书本类。
    - `src/main/java/com/keji/book/BookManager.java`: 实现了图书管理功能,包括添加、删除、更新和列出所有书籍。
    - `src/main/java/com/keji/book/Main.java`: 主程序入口,启动图书管理系统。
    - `.gitignore`: 忽略编译后的文件和其他不需要纳入版本控制的文件。
    ​
    要运行此项目,请确保您的环境已安装 JavaMaven。然后按照以下步骤操作:
    ​
    1. 打开终端并导航到项目目录 `D://eacape//keji-book-system`2. 运行 `mvn compile` 命令以编译项目。
    3. 运行 `mvn exec:java -Dexec.mainClass="com.keji.book.Main"` 启动应用程序。
    ​
    现在你可以开始使用这个图书管理系统了!如果你有任何问题或需要进一步的功能,请告诉我。
    
  4. 可以在对应的目录成功的看到图书管理系统的代码

  5. 体验一下图书管理系统,简单的增删改查还是实现了呢~

  6. 上面看着交互还差一点,让柯基Code给优化下

    > 能让输入更有仪式感吗?然后交互的文字提示用中文
    

    终端不输出

    > 能让输入更有仪式感吗?然后交互的文字提示用中文
    处理用户消息: 能让输入更有仪式感吗?然后交互的文字提示用中文
    省略好多日志....
    ⚙️  创建文件: D://eacape//keji-book-system/src/main/java/com/keji/book/BookManager.java
    成功写入 3884 字节到 src\main\java\com\keji\book\BookManager.java
    ✅ 完成
    省略好多日志....
    ​
    ✨ 操作完成
    ​
    ​
    项目已更新,现在交互提示使用中文,并且输入和输出更有仪式感。以下是更新后的 `BookManager.java` 文件内容:
    ​
    ```java
    省略好代码....
    ```
    ​
    现在你可以按照以下步骤运行项目:
    ​
    1. 打开终端并导航到项目目录 `D://eacape//keji-book-system`2. 运行 `mvn compile` 命令以编译项目。
    3. 运行 `mvn exec:java -Dexec.mainClass="com.keji.book.Main"` 启动应用程序。
    ​
    现在你可以开始使用这个更具仪式感的图书管理系统了!如果你有任何问题或需要进一步的功能,请告诉我。
    
  7. 重新体验图书管理系统

3. 系统架构设计

3.1 分层架构

KejiCode 采用经典的四层架构,每层职责清晰、低耦合高内聚

3.2 层次职责详解

3.2.1 表现层 (Presentation Layer)

核心组件TerminalUI

职责

  • 输入监听:接收用户的文字命令
  • 输出展示:格式化显示 AI 回复和工具执行结果
  • 命令解析:处理特殊命令(/help, /switch, /model 等)
  • 用户体验:清晰的操作提示和状态反馈

设计亮点

// 使用 JLine 提供类似 Shell 的交互体验
LineReader lineReader = LineReaderBuilder.builder()
    .terminal(terminal)
    .build();
​
// 支持命令补全、历史记录
String input = lineReader.readLine("\n> ");

比喻:这是柯基的"耳朵和嘴巴",负责听指令和说话。

3.2.2 应用层 (Application Layer)
核心组件 1:CodeAssistantAgent

这是整个系统的大脑,协调各个组件工作

核心方法设计

// 1. 处理用户消息 - 入口方法
public String processUserMessage(String userMessage) {
    // 添加用户消息到历史
    conversationHistory.add(UserMessage.from(userMessage));
​
    // 调用 AI 模型(传递工具规范)
    Response<AiMessage> response = chatModel.generate(
        conversationHistory,
        getToolSpecifications()  // 🔑 关键:告诉 AI 有哪些工具
    );
​
    // 检查 AI 的意图
    if (response.hasToolExecutionRequests()) {
        return handleToolExecution(response.toolExecutionRequests());
    } else {
        return response.text();
    }
}
​
// 2. 工具链执行 - 核心逻辑
private String handleToolExecution(List<ToolExecutionRequest> requests) {
    // 执行第一批工具
    for (ToolExecutionRequest request : requests) {
        executeSingleTool(request);
    }
​
    // 🔑 工具链循环:让 AI 持续决策
    while (iteration < MAX_ITERATIONS) {
        Response<AiMessage> followUp = chatModel.generate(
            conversationHistory,
            getToolSpecifications()
        );
​
        if (followUp.hasToolExecutionRequests()) {
            // AI 还想调用更多工具
            for (ToolExecutionRequest req : followUp.toolExecutionRequests()) {
                executeSingleTool(req);
            }
        } else {
            // AI 完成任务,返回最终回复
            return followUp.text();
        }
        iteration++;
    }
}

设计模式应用

  • 策略模式:不同 AI 模型的统一接口
  • 观察者模式:工具执行结果通知
  • 模板方法:工具执行的统一流程
核心组件 2:Configuration

配置管理采用分层配置策略:

配置优先级:命令行参数 > 环境变量 > YAML 配置 > 默认值

设计理由

  • 安全性:敏感信息(API Key)优先从环境变量读取
  • 灵活性:不同环境可以有不同配置
  • 便捷性:命令行参数方便临时调整
核心组件 3:PathValidator

路径验证是安全架构的基石,采用沙箱模式

核心算法

public static Path validateAndNormalize(String targetPath) {
    Path workDir = getWorkingDirectory();
​
    // 1. 转换为绝对路径
    Path target = Paths.get(targetPath).isAbsolute()
        ? Paths.get(targetPath)
        : workDir.resolve(targetPath);
​
    // 2. 规范化(核心:移除 .. 和 .)
    target = target.normalize();
​
    // 3. 边界检查(核心:startsWith)
    if (!target.startsWith(workDir)) {
        throw new SecurityException("路径越界");
    }
​
    return target;
}

安全保障

  • 防止路径遍历攻击(../../../etc/passwd
  • 防止符号链接逃逸(link-to-/etc
  • 防止绝对路径直达(/tmp/malicious.sh
3.2.3 领域层 (Domain Layer)
工具系统设计

所有工具实现统一的 Tool 接口:

public interface Tool {
    String getName();          // 工具名称
    String getDescription();   // 工具描述(给 AI 看)
    ToolResult execute(String parameters) throws Exception;
}

工具分类

类别工具作用典型场景
文件操作Read读取文件内容(支持分页)查看代码、配置文件
Write创建或覆盖文件生成新文件、重写配置
Edit精确字符串替换编辑修改代码片段、更新变量名
搜索工具Glob按模式匹配文件名查找所有 .java 文件
Grep按正则搜索文件内容查找所有 TODO 注释
命令执行Bash执行任意 shell 命令编译、运行、系统操作
版本控制GitGit 操作封装提交、推送、查看历史
构建工具MavenMaven 操作封装编译、打包、测试
NpmNpm 操作封装安装依赖、运行脚本

工具注册机制

工具规范(Tool Specification)设计

这是告诉 AI "工具能做什么" 的关键:

ToolSpecification writeSpec = ToolSpecification.builder()
    .name("Write")
    .description("""
        将内容写入文件。创建新文件或覆盖已有文件。

        参数:
        - file_path (必需): 文件的完整路径,如 "src/Main.java"
        - content (必需): 要写入的文件内容

        示例: {"file_path": "test.txt", "content": "Hello"}
        """)
    .build();

为什么需要详细的描述?

比喻:就像给新员工写岗位说明书

  • 简单描述:"处理文件" → AI 不知道具体能做什么
  • 详细描述:"创建或覆盖文件,需要 file_path 和 content 参数" → AI 清楚知道何时使用
模型工厂设计

采用抽象工厂模式 + 策略模式

工厂方法实现

public class AIModelFactory {
    public static ChatLanguageModel createChatModel(
        String provider,
        String apiKey,
        String modelName,
        double temperature,
        int maxTokens,
        String customUrl,
        String customHeaders
    ) {
        return switch (provider.toLowerCase()) {
            case "anthropic" -> buildAnthropicModel(apiKey, modelName, temperature, maxTokens);
            case "openai" -> buildOpenAiModel(apiKey, modelName, temperature, maxTokens);
            case "dashscope" -> buildDashScopeModel(apiKey, modelName, temperature, maxTokens);
            case "deepseek" -> buildDeepSeekModel(apiKey, modelName, temperature, maxTokens);
            case "custom" -> buildCustomModel(apiKey, modelName, temperature, maxTokens, customUrl, customHeaders);
            default -> throw new IllegalArgumentException("不支持的提供商: " + provider);
        };
    }

    private static ChatLanguageModel buildAnthropicModel(...) {
        return AnthropicChatModel.builder()
            .apiKey(apiKey)
            .modelName(modelName)
            .temperature(temperature)
            .maxTokens(maxTokens)
            .build();
    }
    // ... 其他模型构建方法
}
对话历史管理

对话历史是 AI 的"记忆",设计要点

消息类型

  1. UserMessage:用户输入
  2. AiMessage:AI 回复(文字或工具调用请求)
  3. ToolExecutionResultMessage:工具执行结果

历史管理策略

public class ConversationHistory {
    private final List<ChatMessage> messages;
    private final int maxHistorySize;  // 防止内存溢出

    public void add(ChatMessage message) {
        messages.add(message);

        // 策略1:限制总消息数
        if (messages.size() > maxHistorySize) {
            // 保留最近的消息
            messages.subList(0, messages.size() - maxHistorySize).clear();
        }

        // 策略2:智能压缩(未来优化)
        // - 保留完整的最近 N 轮对话
        // - 较早的对话只保留摘要
    }
}

设计考虑

  • 上下文连贯性:AI 需要记住之前的对话才能正确理解
  • 内存管理:过长的历史会占用大量内存
  • 成本控制:AI API 按 token 收费,历史越长成本越高
3.2.4 基础设施层 (Infrastructure Layer)

这一层封装了所有第三方依赖系统调用

核心职责

  • AI 服务对接:通过 LangChain4j 调用各家 AI API
  • 文件系统操作:Java NIO 文件读写
  • 进程管理:ProcessBuilder 执行外部命令
  • 日志记录:SLF4J + Logback

为什么需要这一层?

比喻:就像建筑的地基,上层不用关心底层细节

// ❌ 不好的设计:直接调用
Files.writeString(Paths.get("file.txt"), "content");

// ✅ 好的设计:通过抽象层
fileSystemService.writeFile("file.txt", "content");

优势

  • 易于测试:可以 mock 文件系统
  • 易于替换:底层实现变化不影响上层
  • 统一控制:在这一层统一处理异常、日志等

4. 对话流程设计

4.1 完整交互流程

这是 KejiCode 最核心的流程设计

4.2 工具链决策机制

AI 如何决定调用哪些工具?这是一个智能决策过程

决策影响因素

  1. System Prompt:定义 AI 的行为规范

    "当用户说'创建文件'时,使用 Write 工具"
    "当用户说'查找'时,使用 Glob 或 Grep 工具"
    
  2. 工具描述:详细说明工具用途和参数

    "Write: 创建新文件或覆盖已有文件,参数..."
    
  3. 对话历史:上下文决定下一步操作

    用户: "创建文件"
    AI: [创建文件]
    用户: "编辑它" ← AI 知道"它"指的是刚创建的文件
    

4.3 错误恢复流程

当工具执行失败时,如何处理?

错误处理策略

public ToolResult execute(String parameters) {
    try {
        // 1. 参数验证
        validateParameters(parameters);

        // 2. 安全检查
        PathValidator.validateAndNormalize(filePath);

        // 3. 执行操作
        performOperation();

        return ToolResult.success("操作成功");

    } catch (SecurityException e) {
        // 安全错误:清晰提示边界
        return ToolResult.failure("安全错误: " + e.getMessage());

    } catch (IllegalArgumentException e) {
        // 参数错误:提示正确用法
        return ToolResult.failure("参数错误: " + e.getMessage() + "\n使用说明: ...");

    } catch (IOException e) {
        // IO 错误:提示具体问题
        return ToolResult.failure("IO 错误: " + e.getMessage());

    } catch (Exception e) {
        // 未知错误:记录日志
        log.error("工具执行异常", e);
        return ToolResult.failure("执行失败: " + e.getMessage());
    }
}

结语

KejiCode 可以帮你简单了解Coding Agent的工作原理,Happy Coding! 🐕💻

代码可以通过关注微信公众号【柯基写代码】回复【柯基Code】获取~