从零构建AI编程智能体:基于ReAct框架的迷你实现与核心原理
1. 项目概述为什么我们需要一个“迷你”AI编程智能体最近几个月AI编程助手或者说“智能体”的热度居高不下。从GitHub Copilot到Cursor再到各种宣称能“自主完成复杂任务”的Agent框架它们确实极大地提升了开发效率。但作为一个在一线写了十几年代码的老兵我总感觉有点“隔靴搔痒”。这些工具要么是闭源的“黑盒”你只知道它能补全代码却不知道它内部的决策逻辑要么就是架构庞大、依赖复杂动辄需要启动十几个服务学习成本高想根据自己的业务定制一下更是无从下手。于是我萌生了一个想法能不能用最精简的代码亲手打造一个“真正能干活”的AI编程智能体这个智能体不需要面面俱到但核心的“感知-思考-行动”循环必须完整它的架构必须清晰透明每一行代码你都能看懂并且能轻松地修改和扩展。这就是mini-cc项目的初衷——ClearCode,Core Concept。“真正能干活”是什么意思它不是说能替代一个资深架构师去设计一个分布式系统而是指它能完成一个编程智能体最本质的工作流理解你的自然语言需求比如“在/src/utils目录下创建一个格式化日期的函数”分析当前代码库的上下文规划出具体的执行步骤如检查目录是否存在、读取现有相关代码、生成新函数代码然后安全、准确地执行这些操作创建文件、写入代码。整个过程是自主的、闭环的。这个项目适合谁首先当然是所有对AI应用开发、智能体架构感兴趣的中高级开发者。其次如果你对现有AI编程工具感到“知其然不知其所以然”想深入理解其内部机制那么通过复现一个迷你版是最好的学习方式。最后它也是一个极佳的“脚手架”或“样板工程”你可以基于它快速构建属于你自己的、垂直领域的AI助手比如自动化测试生成器、SQL查询优化器、API文档生成器等。2. 核心架构设计拆解智能体的“大脑”与“手脚”一个能自主工作的编程智能体其核心架构可以抽象为一个经典的“感知-规划-行动”循环在AI领域常被称为ReAct (Reasoning Acting)框架。我们的mini-cc就严格遵循这个范式但用最直白的代码实现。整个系统的架构可以分为三层Orchestrator协调层、Planner规划层和Actuator执行层。2.1 协调层智能体的总指挥中心协调层是智能体的入口和总调度中心。它的职责非常明确接收用户指令将用户模糊的自然语言需求如“帮我修复登录模块的BUG”转化为结构化的任务描述。管理对话上下文维护与智能体的多轮对话历史确保智能体拥有“记忆”能理解指代和后续的细化要求。调用规划层与执行层它是规划层和执行层的“粘合剂”负责在“思考”和“行动”之间有序切换。在mini-cc中协调层由一个主循环函数实现。这个循环会持续运行直到任务被标记为完成或失败。它的核心逻辑是首先将用户指令和当前代码库的摘要信息通过一个轻量级的代码扫描器获得打包发送给规划层大语言模型去“思考”下一步该做什么。拿到规划层的“行动指令”后再分发给对应的执行器去“动手”执行。执行完毕后将结果反馈给规划层进行下一轮的思考和行动。注意这里的关键设计是“上下文管理”。我们不会把整个项目的代码都塞给大模型那样会超出其上下文窗口且成本高昂。而是通过一个“代码摘要器”动态地提取与当前任务最相关的文件路径、函数名和类名作为上下文的一部分提供给规划层。这模仿了人类程序员接到任务后先快速浏览相关文件再动手的习惯。2.2 规划层大语言模型驱动的“思考”引擎规划层是整个智能体的“大脑”由大语言模型驱动。它的输入是协调层提供的“任务描述”和“当前上下文”输出是一个具体的、可执行的“行动指令”。这个“行动指令”不是一个简单的代码片段而是一个结构化的JSON对象。在mini-cc中我们定义了有限的几种原子操作例如read_file: 读取指定文件的内容。search_code: 在代码库中搜索包含特定模式或文本的文件。edit_file: 在指定文件的特定位置行号插入、删除或替换代码。run_command: 运行一个Shell命令如运行测试、安装依赖。final_answer: 任务完成输出最终结果。为什么是结构化输出而不是自由文本这是实现可靠自动化的关键。自由文本的指令如“请打开app.py在第30行后面添加一段打印日志的代码”难以被程序稳定解析。而结构化的JSON指令{action: edit_file, path: app.py, operation: insert_after_line, line: 30, content: print(Debug info)}可以被执行层无歧义地理解和处理。我们通过精心设计的大模型提示词引导它输出这种格式。2.3 执行层安全可靠的“双手”执行层是智能体的“手脚”负责将规划层发出的结构化指令转化为对实际系统的操作。这是整个系统中最需要谨慎处理的部分因为涉及到对文件系统的读写和命令的执行。mini-cc的执行层为每一种原子操作都实现了一个对应的“执行器”。每个执行器都有严格的边界和安全检查FileEditor文件编辑器在执行任何写操作前会先备份原文件。对于插入/删除操作会严格校验行号是否在文件有效范围内。CommandRunner命令运行器会限制可运行的命令白名单例如只允许git,npm,python等构建和测试命令禁止执行rm -rf /等危险操作。同时会捕获命令的标准输出和错误流将其作为“观察结果”反馈给协调层。CodeSearcher代码搜索器利用ripgrep或grep等工具进行快速文本搜索而不是直接让大模型去“回忆”代码效率更高且准确。执行层执行完毕后会将结果成功后的文件内容、命令输出、或错误信息封装起来送回给协调层。协调层再将其作为新的“观察”上下文连同原始任务一起再次提交给规划层进行下一轮决策。这就形成了一个完整的“思考 - 行动 - 观察 - 再思考”的闭环。3. 关键技术实现与代码拆解理解了架构我们来看具体实现。mini-cc的核心代码控制在500行以内但每一部分都至关重要。我们选用 Python 作为实现语言因为它生态丰富与各大语言模型API交互方便。3.1 与大语言模型的交互提示词工程与结构化输出与规划层大模型的交互是核心。我们以 OpenAI 的 GPT-4 或 Claude 3 的 API 为例。关键在于构造一个能让模型稳定输出结构化指令的提示词。# 简化的提示词模板 PLANNER_PROMPT_TEMPLATE 你是一个专业的编程助手AI。你的目标是根据用户的请求和当前的上下文决定下一步做什么。 你只能从以下行动中选择一个并严格按照JSON格式输出 行动列表 1. read_file: 读取文件内容。参数: path (文件路径)。 2. search_code: 在项目中搜索代码。参数: pattern (搜索模式)。 3. edit_file: 编辑文件。参数: path, operation (‘insert_after_line‘, ‘replace_line‘, ‘delete_lines‘), line (行号), content (新内容对insert和replace有效)。 4. run_command: 运行shell命令。参数: command (命令字符串)。 5. final_answer: 给出最终答案。参数: answer (文本回答)。 当前任务{task} 当前已知上下文{context} 上一步执行结果{last_observation} 请分析接下来最应该做什么。只输出一个JSON对象不要有任何其他解释。 这个提示词做了几件事限定了行动范围、明确了输出格式、注入了任务和上下文。在代码中我们调用API后会用json.loads()解析返回的文本如果解析失败会触发重试或降级处理。实操心得让大模型输出稳定的JSON是一大挑战。除了在提示词中强调还可以使用API的response_format参数如果支持如OpenAI的JSON模式或者在输出后使用一个轻量级的语法校验和修复层比如用另一个快速模型进行修正这能极大提高系统的可靠性。3.2 代码上下文的动态管理轻量级摘要器如前所述我们不会传送整个代码库。实现一个轻量级的上下文管理器是关键。class CodeContextManager: def __init__(self, root_path): self.root_path root_path self.relevant_files_cache [] def get_context_summary(self, task_description): # 1. 基于任务描述用简单关键词提取可能相关的文件类型 keywords self._extract_keywords(task_description) # 2. 快速文件系统扫描找出最近修改过的或路径名包含关键词的文件 candidate_files self._scan_files(keywords) # 3. 对候选文件读取前N行和后N行获取函数/类定义摘要 summary [] for file in candidate_files[:5]: # 限制数量控制上下文长度 summary.append(fFile: {file}) summary.append(self._get_file_summary(file)) return \n.join(summary) def _get_file_summary(self, filepath): # 简化实现读取文件用正则匹配类定义和函数定义行 # 返回一个简化的概要而不是全部内容 ...这个管理器在每次循环开始时被调用它为规划层提供了一个高度浓缩的、任务相关的“地图”让模型知道“战场”的概况而不是所有“士兵”的细节。3.3 原子执行器的安全实现以最核心也最危险的FileEditor为例看看如何实现安全编辑。class FileEditor: def execute(self, action_params): path action_params[path] operation action_params[operation] line_no action_params.get(line) content action_params.get(content) # 安全检查1路径合法性防止路径遍历攻击 if not self._is_safe_path(path): return {status: error, message: Invalid file path.} # 安全检查2备份原文件 backup_path f{path}.backup_{int(time.time())} shutil.copy2(path, backup_path) try: with open(path, r) as f: lines f.readlines() # 根据操作类型修改内存中的lines列表 if operation insert_after_line: # 检查行号有效性 if 0 line_no len(lines): lines.insert(line_no 1, content \n) else: raise IndexError(Line number out of range.) elif operation replace_line: # ... 类似逻辑 # 其他操作... # 写入文件 with open(path, w) as f: f.writelines(lines) # 读取修改后的文件内容作为观察结果返回 with open(path, r) as f: new_content f.read() return {status: success, observation: fFile {path} edited successfully.\nContent preview:\n{new_content[:500]}} except Exception as e: # 发生错误恢复备份 shutil.copy2(backup_path, path) return {status: error, message: fEdit failed: {str(e)}. File restored from backup.} finally: # 清理备份或可选择保留一段时间 os.remove(backup_path)这个执行器体现了“防御性编程”思想在执行前验证输入、在执行前备份、在执行后验证结果、在失败时自动回滚。这是构建可信赖AI智能体的基石。4. 完整工作流演示从需求到代码的闭环让我们通过一个完整的例子看看mini-cc是如何工作的。假设我们的项目是一个简单的Flask web应用用户提出需求“在项目里添加一个/health端点返回JSON{“status”: “ok”}。”第一轮循环协调层接收指令。上下文管理器扫描项目发现app.py是主应用文件将其摘要包含现有的路由定义作为上下文。规划层模型收到任务和上下文。它“思考”后认为第一步应该是读取app.py来了解现有结构。输出{action: read_file, path: app.py}。执行层FileEditor执行读取操作返回app.py的完整内容。协调层将读取到的文件内容作为“观察结果”连同原始任务再次提交给规划层。第二轮循环规划层模型现在“看到”了app.py的内容。它分析后认为需要在合适的位置比如在其他路由定义之后添加一个新的路由函数。输出{action: edit_file, path: app.py, operation: insert_after_line, line: 25, content: app.route(/health)\ndef health_check():\n return jsonify({\status\: \ok\})}。执行层FileEditor安全地在第25行后插入代码段。协调层收到编辑成功的观察结果再次询问规划层下一步。第三轮循环规划层模型认为代码已添加可以运行一个简单的测试来验证。输出{action: run_command, command: cd /path/to/project python -m pytest tests/test_health.py -xvs}。执行层CommandRunner执行该测试命令。假设测试文件不存在命令返回错误。协调层将错误信息“测试文件未找到”作为观察结果反馈。第四轮循环规划层模型“观察”到测试失败是因为缺少测试文件。它决定先创建测试文件。输出{action: edit_file, path: tests/test_health.py, operation: create, content: import json\ndef test_health_endpoint(client):\n response client.get(/health)\n assert response.status_code 200\n data json.loads(response.data)\n assert data[status] ok}。执行层创建文件并写入测试代码。协调层继续循环可能引导模型运行测试、安装缺失的依赖如pytest-flask等直到测试通过。最终轮规划层判断所有任务已完成输出{action: final_answer, answer: 已成功在 app.py 中添加 /health 端点并创建了对应的测试文件 tests/test_health.py。测试已运行通过。}。协调层收到此指令结束主循环向用户呈现最终答案。这个过程清晰地展示了智能体如何通过多轮“感知-规划-行动”的迭代将一个高层需求分解为一系列安全的原子操作并最终完成。5. 进阶优化与扩展方向一个能跑通的mini-cc只是起点。要让它在实际项目中更可靠、更强大还需要考虑以下优化和扩展点。5.1 提升规划可靠性思维链与验证循环大模型的单次规划可能出错。我们可以引入更复杂的机制思维链提示在提示词中要求模型先输出推理过程用“Thought:”标注再输出行动指令。这样在调试时我们可以查看其“思考过程”更容易定位问题。子目标分解对于复杂任务规划层可以先输出一个子目标列表然后逐个击破。例如任务“实现用户登录功能”可分解为【1. 检查现有用户模型】、【2. 创建登录路由】、【3. 实现密码验证】、【4. 生成JWT令牌】、【5. 编写测试】。结果验证在执行一个行动后强制模型先验证结果是否合理。例如在编辑文件后紧接着自动执行一个read_file行动让模型确认修改是否正确然后再进行下一步。5.2 扩展行动工具箱更多实用工具基础的原子操作可以扩展让智能体能力更强git_operation执行git add,commit,push等操作让智能体能自主管理版本。browser_operation需谨慎在安全沙箱内打开网页、查找文档。这需要集成类似 Playwright 的工具。ask_for_clarification当任务模糊时智能体可以主动向用户提问而不是盲目猜测。这通过输出一个特殊的行动指令来实现协调层会暂停循环并等待用户输入。5.3 工程化考量状态管理、持久化与并发状态持久化将智能体的运行状态对话历史、已执行步骤、当前目标保存到数据库或文件支持中断后恢复。这对于执行耗时长的任务至关重要。并发与异步多个执行器可以并行工作如同时运行多个独立的测试。协调层需要管理好任务队列和依赖关系。成本与延迟优化大模型API调用是主要成本和延迟来源。可以通过缓存常见的规划结果、使用更小更快的模型处理简单步骤、合并多个小操作等方式进行优化。6. 常见问题与实战避坑指南在实际构建和运行mini-cc这类智能体时你会遇到一些典型问题。以下是我踩过坑后总结的经验。6.1 模型不按格式输出怎么办这是最常见的问题。除了使用API的JSON模式一个实用的后备方案是“解析-重试”循环。def get_structured_action(model_response, max_retries3): for i in range(max_retries): try: # 尝试1直接解析 action json.loads(model_response) if self._validate_action(action): return action except json.JSONDecodeError: # 尝试2提取可能被json 包裹的代码块 import re match re.search(r(?:json)?\n(.*?)\n, model_response, re.DOTALL) if match: try: action json.loads(match.group(1)) if self._validate_action(action): return action except: pass # 尝试3用另一个更便宜的模型如GPT-3.5进行修复 if i max_retries - 1: model_response self._call_model_to_fix_json(model_response) raise Exception(Failed to get valid structured action after retries.)6.2 智能体陷入死循环或无效操作有时模型会卡在“读取A文件 - 读取B文件 - 又读回A文件”的循环中或者不断执行无关的搜索。解决方法设置最大循环次数比如限制为20步超过则强制终止防止无限消耗资源。引入“操作历史”到上下文在提示词中明确告诉模型最近已执行的5个步骤让它避免重复。定义明确的终止条件除了final_answer还可以定义give_up行动让模型在遇到无法解决的问题时主动放弃并说明原因。6.3 文件编辑的行号定位不准大模型可能错误判断插入代码的行号。缓解策略使用模糊定位除了精确行号支持基于函数名或代码模式的定位。例如操作可以是“insert_after”: “def login():”由执行器在文件中搜索该模式并确定行号。编辑后立即验证如前所述强制在每次编辑后跟随一个read_file行动让模型自我检查。如果发现错误可以输出一个edit_file行动来撤销或修正。6.4 安全风险控制这是重中之重。除了在执行器层面做检查还需要运行在隔离环境强烈建议在Docker容器或虚拟机中运行智能体限制其对主机系统的访问权限。命令白名单机制CommandRunner必须维护一个允许的命令列表并考虑参数过滤。例如允许npm install package但禁止npm install后接一个可疑的URL。人工审核环节对于关键操作如生产环境部署、删除文件可以设计一个require_human_approval行动暂停自动执行等待用户确认。构建mini-cc的过程本质上是在用代码定义一种与AI协作的新范式。它不是一个完美的最终产品而是一个理解智能体内部运作原理的绝佳透镜。通过亲手实现这个精简的闭环你会对如何设计提示词、如何保障执行安全、如何调试AI的行为有更深刻的理解。这远比单纯调用一个现成的、 opaque 的API要有价值得多。当你能够清晰地看到“思考”和“行动”是如何通过代码连接起来时你也就获得了定制和创造更强大、更专属的AI工具的能力。