1 line
12 KiB
JSON
1 line
12 KiB
JSON
{"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"}}}} |