Files

1 line
12 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "chen-jiacheng/code-agent", "files": {"README.md": {"4ae30a3484f6da7ce7b70d9d725b94e757ca2023": {"t": 1785746578.004887, "n": 9177, "h": "f059a1fbc9db4837", "c": "# Coding Agent (Java)\n\n一个基于 **Spring Boot + LangChain4j** 的交互式编程助手(Coding Agent),通过工具调用(Tool Use)实现文件读取、目录列举和文件编辑等功能。支持 DeepSeek 等兼容 OpenAI API 格式的大模型服务。\n\n## 项目简介\n\n本项目是 Python 版 Coding Agent 的 Java 实现,采用 ReAct(Reasoning + Acting)循环模式:\n\n```\n用户输入 → LLM 推理 → 解析工具调用 → 执行工具 → 反馈结果 → LLM 继续推理 → 最终回答\n ↑ |\n └────────────── 循环直到无工具调用 ──────────────────┘\n```\n\n## 技术栈\n\n| 技术 | 说明 |\n|------|------|\n| **Spring Boot 3.x** | 应用框架 |\n| **LangChain4j** | LLM 编排框架(Agent + Tool 调用) |\n| **JDK 17+** | 最低要求(推荐 JDK 21) |\n| **Maven** | 构建工具 |\n| **Fastjson** | JSON 处理 |\n| **dotenv** | 环境变量加载(`me.paulschwarzer/dotenv`) |\n\n## 功能特性\n\n- **项目目录选择**:启动时交互式选择项目工作目录,所有文件操作基于此目录\n- **文件读取**(`read_file`):读取指定文件的完整内容\n- **目录列举**(`list_files`):列出指定目录下的文件和子目录\n- **文件编辑**(`edit_file`):替换文件中的指定字符串,或创建新文件\n- **彩色终端输出**:用户(蓝色)、助手(黄色)、系统信息(绿色)\n\n## 环境要求\n\n- JDK 17+(推荐 JDK 21)\n- Maven 3.8+\n- DeepSeek API Key(或其他兼容 OpenAI API 的 LLM 服务)\n\n## 快速开始\n\n### 1. 克隆项目\n\n```bash\ngit clone <repo-url>\ncd coding-agent-java\n```\n\n### 2. 配置环境变量\n\n在项目根目录创建 `.env` 文件:\n\n```env\n# DeepSeek API 配置\nLLM_API_KEY=sk-your-deepseek-api-key-here\nLLM_BASE_URL=https://api.deepseek.com\nLLM_MODEL=deepseek-chat\n```\n\n| 参数 | 说明 | 示例 |\n|------|------|------|\n| `LLM_API_KEY` | API 密钥 | `sk-xxx` |\n| `LLM_BASE_URL` | API 服务地址 | `https://api.deepseek.com` |\n| `LLM_MODEL` | 模型名称 | `deepseek-chat` / `deepseek-coder` / `deepseek-reasoner` |\n\n### 3. 构建\n\n```bash\nmvn clean package -DskipTests\n```\n\n### 4. 运行\n\n```bash\n# 方式一:Maven 运行\nmvn spring-boot:run\n\n# 方式二:JAR 运行\njava -jar target/coding-agent-0.1.0.jar\n```\n\n运行后:\n1. 选择项目目录(输入路径或回车使用当前目录)\n2. 进入交互式对话,输入编程任务\n3. Agent 自动调用工具完成任务\n4. 按 `Ctrl+C` 退出\n\n## 项目结构\n\n```\ncoding-agent-java/\n├── src/\n│ ├── main/\n│ │ ├── java/com/codingagent/\n│ │ │ │\n│ │ │ ├── CodingAgentApplication.java # Spring Boot 启动类\n│ │ │ │\n│ │ │ ├── config/ # 配置层\n│ │ │ │ ├── LlmConfig.java # LLM 客户端配置(ApiKey/BaseUrl/Model)\n│ │ │ │ └── AgentConfig.java # Agent Bean 装配\n│ │ │ │\n│ │ │ ├── agent/ # Agent 核心层\n│ │ │ │ ├── CodingAgent.java # Agent 主循环(ReAct 循环)\n│ │ │ │ ├── PromptBuilder.java # System Prompt 构建器\n│ │ │ │ └── DirectorySelector.java # 项目目录交互式选择\n│ │ │ │\n│ │ │ ├── tool/ # 工具层\n│ │ │ │ ├── ToolDefinition.java # 工具接口(name/description/execute)\n│ │ │ │ ├── ToolRegistry.java # 工具注册表(Map<String, Tool>)\n│ │ │ │ ├── FileUtils.java # 路径解析 + 项目目录管理\n│ │ │ │ ├── ReadFileTool.java # 文件读取工具\n│ │ │ │ ├── ListFilesTool.java # 目录列举工具\n│ │ │ │ └── EditFileTool.java # 文件编辑工具\n│ │ │ │\n│ │ │ └── ui/ # 终端交互层\n│ │ │ └── ConsoleUI.java # 彩色终端输出工具\n│ │ │\n│ │ └── resources/\n│ │ └── application.yml # Spring Boot 配置\n│ │\n│ └── test/\n│ └── java/com/codingagent/\n│ ├── tool/\n│ │ ├── ReadFileToolTest.java\n│ │ ├── ListFilesToolTest.java\n│ │ └── EditFileToolTest.java\n│ └── agent/\n│ └── CodingAgentTest.java\n│\n├── .env # 环境变量(需自行创建)\n├── pom.xml # Maven 配置\n└── README.md\n```\n\n## 核心设计\n\n### 1. 工具接口 (`ToolDefinition`)\n\n所有工具实现统一接口,LangChain4j 通过 `@Tool` 注解自动发现:\n\n```java\npublic interface ToolDefinition {\n String getName();\n String getDescription();\n Map<String, Object> execute(Map<String, Object> args);\n}\n```\n\n### 2. 工具注册 (`ToolRegistry`)\n\n```java\n@Component\npublic class ToolRegistry {\n private final Map<String, ToolDefinition> tools = new LinkedHashMap<>();\n\n @Autowired\n public ToolRegistry(List<ToolDefinition> toolList) {\n toolList.forEach(t -> tools.put(t.getName(), t));\n }\n\n public Map<String, ToolDefinition> getAll() { return tools; }\n}\n```\n\n### 3. 路径解析 (`FileUtils`)\n\n```java\n@Component\npublic class FileUtils {\n private Path projectDir = Path.of(\"\").toAbsolutePath();\n\n public void setProjectDir(Path dir) { this.projectDir = dir; }\n public Path getProjectDir() { return projectDir; }\n\n public Path resolveAbsPath(String pathStr) {\n Path path = Path.of(pathStr).expanduser(); // 需处理 ~\n if (!path.isAbsolute()) {\n path = projectDir.resolve(path).toRealPath();\n }\n return path;\n }\n}\n```\n\n### 4. Agent 主循环 (`CodingAgent`)\n\n```java\n@Component\npublic class CodingAgent {\n // LangChain4j 的 ChatLanguageModel\n private final ChatLanguageModel llm;\n private final ToolRegistry toolRegistry;\n private final PromptBuilder promptBuilder;\n\n public void run() {\n // 1. 选择项目目录\n Path projectDir = directorySelector.select();\n fileUtils.setProjectDir(projectDir);\n\n // 2. 构建 System Prompt(含工具列表 + 工作目录)\n String systemPrompt = promptBuilder.build(projectDir);\n\n // 3. ReAct 循环\n List<ChatMessage> conversation = new ArrayList<>();\n conversation.add(SystemMessage.from(systemPrompt));\n\n while (true) {\n String userInput = scanner.nextLine();\n conversation.add(UserMessage.from(userInput));\n\n while (true) {\n String response = llm.generate(conversation);\n List<ToolInvocation> invocations = extractToolInvocations(response);\n\n if (invocations.isEmpty()) {\n console.printAssistant(response);\n conversation.add(AiMessage.from(response));\n break;\n }\n\n for (ToolInvocation inv : invocations) {\n Map<String, Object> result = toolRegistry.execute(inv.getName(), inv.getArgs());\n conversation.add(ToolResponseMessage.from(inv.getName(), toJson(result)));\n }\n }\n }\n }\n}\n```\n\n### 5. LangChain4j 集成\n\n```java\n@Configuration\npublic class LlmConfig {\n\n @Bean\n public ChatLanguageModel chatLanguageModel(\n @Value(\"${llm.api-key}\") String apiKey,\n @Value(\"${llm.base-url}\") String baseUrl,\n @Value(\"${llm.model}\") String model) {\n\n return OpenAiChatModel.builder()\n .apiKey(apiKey)\n .baseUrl(baseUrl)\n .modelName(model)\n .maxTokens(2000)\n .build();\n }\n}\n```\n\n### 6. application.yml\n\n```yaml\nllm:\n api-key: ${LLM_API_KEY:}\n base-url: ${LLM_BASE_URL:https://api.deepseek.com}\n model: ${LLM_MODEL:deepseek-chat}\n\nspring:\n main:\n banner-mode: off\n```\n\n## 三个工具的实现逻辑\n\n### `read_file(filename)`\n```\n1. resolveAbsPath(filename) → 绝对路径\n2. 读取文件全部内容(UTF-8)\n3. 返回 {\"file_path\": \"...\", \"content\": \"...\"}\n```\n\n### `list_files(path)`\n```\n1. resolveAbsPath(path) → 绝对路径\n2. 遍历目录,列出所有文件和子目录\n3. 返回 {\"path\": \"...\", \"files\": [{\"filename\": \"x\", \"type\": \"file|dir\"}, ...]}\n```\n\n### `edit_file(path, old_str, new_str)`\n```\n1. resolveAbsPath(path) → 绝对路径\n2. if old_str 为空 → 创建/覆写文件,返回 {\"action\": \"created_file\"}\n3. 查找 old_str,未找到返回 {\"action\": \"old_str not found\"}\n4. 替换第一次出现的 old_str → new_str\n5. 写回文件,返回 {\"action\": \"edited\"}\n```\n\n## LLM API 调用格式\n\n```\nPOST {LLM_BASE_URL}/chat/completions\nHeaders: Authorization: Bearer {LLM_API_KEY}\n\nBody: {\n \"model\": \"{LLM_MODEL}\",\n \"messages\": [\n {\"role\": \"system\", \"content\": \"系统提示词(含工具列表 + 工作目录)\"},\n {\"role\": \"user\", \"content\": \"用户输入\"},\n {\"role\": \"tool\", \"content\": \"tool_result({JSON})\"}\n ],\n \"max_tokens\": 2000\n}\n```\n\n## 工具调用协议\n\nLLM 通过文本格式调用工具(LangChain4j 会自动处理):\n\n```\ntool: read_file({\"filename\": \"src/Main.java\"})\ntool: list_files({\"path\": \"src/\"})\ntool: edit_file({\"path\": \"src/Main.java\", \"old_str\": \"hello\", \"new_str\": \"world\"})\n```\n\n## 扩展工具\n\n添加新工具只需两步:\n\n1. 创建工具类实现 `ToolDefinition` 接口\n2. 添加 `@Component` 注解,Spring 自动注入到 `ToolRegistry`\n\n```java\n@Component\npublic class SearchTool implements ToolDefinition {\n @Override\n public String getName() { return \"search\"; }\n\n @Override\n public String getDescription() { return \"在文件中搜索关键词\"; }\n\n @Override\n public Map<String, Object> execute(Map<String, Object> args) {\n // 实现搜索逻辑\n }\n}\n```\n\n## pom.xml 核心依赖\n\n```xml\n<parent>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-parent</artifactId>\n <version>4.1.0</version>\n</parent>\n\n<dependencies>\n <!-- Spring Boot -->\n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter</artifactId>\n </dependency>\n\n <!-- LangChain4j OpenAI 兼容(支持 DeepSeek) -->\n <dependency>\n <groupId>dev.langchain4j</groupId>\n <artifactId>langchain4j-open-ai-spring-boot4-starter</artifactId>\n <version>1.18.1-beta28</version>\n </dependency>\n\n <!-- 测试 -->\n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-test</artifactId>\n <scope>test</scope>\n </dependency>\n</dependencies>\n```\n"}}}}