你的代码 80% 可以由 AI 写——手把手教你搭建 Coding Agent
一、你的代码真的必须全部手写吗2026 年 5 月YC 孵化的 Runtime 拿到了新一轮融资——他们做的事情很简单让团队里每个人都有一个能用自然语言写代码的 AI Agent。HuggingFace 上关于 Coding Agent 的论文也密集涌现——SaaSBench 测试 Agent 在企业级 SaaS 开发中的表现SpecBench 专门检测 Agent 的奖励欺骗行为。这些信号指向同一件事Coding Agent 正在从实验室走向工程实践。但市面上的 Coding Agent 产品Copilot、Cursor、Devin要么闭源要么贵得离谱。本文带你用200 行 Python从零搭建一个能写代码、能调试、能读文件的 AI Coding Agent。不需要任何框架只需要一个 LLM API Key。先看最终效果Agent 收到在当前目录创建一个 Flask REST API提供用户 CRUD 接口的指令后会自动规划文件结构、生成代码、甚至补上单元测试。二、Coding Agent 的核心架构一个可工作的 Coding Agent 由四个模块组成模块职责关键技术LLM 引擎理解指令、生成代码、决策GPT/Claude/DeepSeek APITool Use 层读写文件、执行命令、搜索Function Calling上下文管理记忆历史、截断策略、文件索引Token 窗口管理Agent 循环观察→思考→行动→观察ReAct 范式核心逻辑可以用 15 行伪代码表达class CodingAgent: def __init__(self, llm, tools): self.llm llm # LLM 客户端 self.tools tools # 工具集合 {name: func} self.messages [] # 对话历史 def run(self, task: str) - str: self.messages.append({role: user, content: task}) while True: response self.llm.chat(self.messages, self.tools) if response.is_final: # LLM 决定结束 return response.content result self.tools[response.tool_name](response.tool_args) self.messages.append(response.as_tool_result(result))这就是 Agent 的骨架。下面我们逐一实现每个模块。三、第一步LLM 客户端封装先从最基础的 LLM 调用开始。我们使用 OpenAI 兼容接口方便替换任何模型。import json import httpx from typing import Optional from dataclasses import dataclass, field dataclass class ToolCall: LLM 返回的工具调用请求 tool_name: str tool_args: dict call_id: str dataclass class LLMResponse: LLM 响应封装 content: Optional[str] None # 纯文本回复 tool_calls: list[ToolCall] field(default_factorylist) property def is_final(self) - bool: 没有工具调用 LLM 认为任务完成 return len(self.tool_calls) 0 class LLMClient: OpenAI 兼容的 LLM 客户端 def __init__(self, api_key: str, base_url: str https://api.deepseek.com/v1, model: str deepseek-chat): self.api_key api_key self.base_url base_url self.model model self.client httpx.Client(timeout120) def chat(self, messages: list, tools: list[dict]) - LLMResponse: 发送消息返回 LLMResponse payload { model: self.model, messages: messages, tools: tools, temperature: 0.3, # 代码任务降低温度 } resp self.client.post( f{self.base_url}/chat/completions, headers{ Authorization: fBearer {self.api_key}, Content-Type: application/json }, jsonpayload ) data resp.json() choice data[choices][0][message] response LLMResponse() response.content choice.get(content, ) for tc in choice.get(tool_calls, []): func tc[function] response.tool_calls.append(ToolCall( tool_namefunc[name], tool_argsjson.loads(func[arguments]), call_idtc[id] )) return response这里有几个工程细节值得注意。temperature0.3是因为代码生成任务需要确定性太高的随机性会导致语法错误。工具调用使用了 OpenAI 标准的tools参数格式这意味着你可以无缝切换到 GPT-4、Claude 或任何兼容模型。四、第二步Tool Use——给 Agent 装上手脚Tool Use 是 Coding Agent 区别于普通 Chatbot 的关键。普通 Chatbot 只能说代码Agent 能写到文件、能跑命令、能读报错。先定义工具的数据结构再实现具体工具import subprocess import os from pathlib import Path # ──── 工具定义OpenAI Function Calling 格式──── TOOL_DEFINITIONS [ { type: function, function: { name: read_file, description: 读取文件内容。用于理解现有代码结构。, parameters: { type: object, properties: { path: {type: string, description: 相对或绝对路径} }, required: [path] } } }, { type: function, function: { name: write_file, description: 创建或覆盖文件。传入文件路径和完整内容。, parameters: { type: object, properties: { path: {type: string, description: 文件路径}, content: {type: string, description: 文件完整内容} }, required: [path, content] } } }, { type: function, function: { name: run_command, description: 执行 Shell 命令并返回 stdout/stderr。用此工具运行测试、安装依赖、检查语法。, parameters: { type: object, properties: { command: {type: string, description: 要执行的命令}, cwd: {type: string, description: 工作目录默认为当前目录} }, required: [command] } } }, { type: function, function: { name: list_files, description: 列出目录中的文件和子目录。, parameters: { type: object, properties: { path: {type: string, description: 目录路径默认当前目录} }, required: [] } } } ] # ──── 工具实现 ──── class FileTools: 文件系统操作工具集 staticmethod def read_file(path: str) - str: p Path(path) if not p.exists(): return f[Error] 文件不存在: {path} content p.read_text(encodingutf-8) # 超长文件截断避免撑爆 token 窗口 if len(content) 8000: content content[:8000] \n... (文件过长已截断) return content staticmethod def write_file(path: str, content: str) - str: p Path(path) p.parent.mkdir(parentsTrue, exist_okTrue) p.write_text(content, encodingutf-8) return f[OK] 已写入: {path} ({len(content)} 字符) staticmethod def list_files(path: str .) - str: p Path(path) if not p.exists(): return f[Error] 目录不存在: {path} items [] for entry in sorted(p.iterdir()): tag [DIR] if entry.is_dir() else [FILE] size entry.stat().st_size if entry.is_file() else 0 items.append(f {tag} {entry.name} ({size:,} bytes) if tag [FILE] else f {tag} {entry.name}/) return \n.join(items) if items else (空目录) staticmethod def run_command(command: str, cwd: str .) - str: try: result subprocess.run( command, shellTrue, cwdcwd, capture_outputTrue, textTrue, timeout30 ) out result.stdout[:4000] # 防止输出过长 err result.stderr[:2000] rc fexit_code{result.returncode} return f{rc}\n[stdout]\n{out}\n[stderr]\n{err} if err else f{rc}\n[stdout]\n{out} except subprocess.TimeoutExpired: return [Error] 命令超时30srun_command是最强大的工具——Agent 可以用它安装依赖、运行 linter、执行测试甚至通过报错信息自我修正代码。30 秒超时保护了死循环而输出截断保护了上下文窗口。五、第三步Agent 主循环——ReAct 范式落地前两步准备好了 LLM 和工具现在把它们串起来。核心是一个 while 循环LLM 决定调用哪个工具 → 执行工具 → 结果反馈给 LLM → 继续或结束。from typing import Callable class CodingAgent: AI Coding Agent 主类 SYSTEM_PROMPT 你是一个 AI 编程助手能读写文件、执行命令来完成任务。 工作流程 1. 分析用户需求确定需要创建/修改哪些文件 2. 使用 read_file 了解现有代码避免重复造轮子 3. 使用 write_file 创建或修改代码文件 4. 使用 run_command 运行测试、检查语法 5. 如果命令报错分析错误并修正代码重新执行 原则 - 先读后写修改前一定先 read_file - 小步快跑每次只写一个文件写完立刻用 run_command 验证 - 遇到错误不要猜用 run_command 看真实的报错信息 - 代码要有适当的注释和错误处理 def __init__(self, llm: LLMClient, working_dir: str .): self.llm llm self.working_dir working_dir self.tools: dict[str, Callable] { read_file: lambda **kw: FileTools.read_file(**kw), write_file: lambda **kw: FileTools.write_file(**kw), run_command: lambda **kw: FileTools.run_command(**kw), list_files: lambda **kw: FileTools.list_files(**kw), } self.messages: list[dict] [ {role: system, content: self.SYSTEM_PROMPT} ] self.max_steps 15 # 最多 15 轮防止无限循环 def run(self, task: str) - str: 执行编程任务返回最终结果 self.messages.append({role: user, content: task}) step 0 while step self.max_steps: step 1 print(f\n{*50}\n Step {step}\n{*50}) # ── 调用 LLM ── response self.llm.chat(self.messages, TOOL_DEFINITIONS) # LLM 决定直接回复 任务完成 if response.is_final: self.messages.append({ role: assistant, content: response.content }) return response.content or 任务完成 # ── 执行工具调用 ── for tc in response.tool_calls: print(f {tc.tool_name}({json.dumps(tc.tool_args, ensure_asciiFalse)})) try: result self.tools[tc.tool_name](**tc.tool_args) except Exception as e: result f[Exception] {type(e).__name__}: {e} # 裁剪结果避免 token 爆炸 preview result[:300].replace(\n, \\n) print(f ✅ 结果: {preview}...) # 把工具调用和结果追加到对话历史 self.messages.append({ role: assistant, content: None, tool_calls: [{ id: tc.call_id, type: function, function: { name: tc.tool_name, arguments: json.dumps(tc.tool_args, ensure_asciiFalse) } }] }) self.messages.append({ role: tool, tool_call_id: tc.call_id, content: result }) return [Warning] 达到最大步数限制任务可能未完成几个关键设计决策 -max_steps15是经验值太少完不成复杂任务太多容易在修复循环中打转 - 每个工具调用的结果都完整记录到对话历史LLM 能看到完整的执行链 - 异常捕获在最外层单个工具失败不会让 Agent 崩溃六、第四步跑起来最后写入口函数把所有零件组装好import os def create_agent(api_key: str None) - CodingAgent: 工厂函数创建配置好的 Coding Agent api_key api_key or os.getenv(DEEPSEEK_API_KEY) if not api_key: raise RuntimeError(请设置 DEEPSEEK_API_KEY 或传入 api_key 参数) llm LLMClient( api_keyapi_key, base_urlos.getenv(LLM_BASE_URL, https://api.deepseek.com/v1), modelos.getenv(LLM_MODEL, deepseek-chat) ) return CodingAgent(llm, working_dir.) if __name__ __main__: agent create_agent() task 在当前目录下创建一个 Flask REST API 项目要求 1. 提供 /users 路由支持 GET列表和 POST创建 2. 使用 SQLite 存储字段 id/name/email/created_at 3. 包含一个简单的测试脚本 test_api.py 4. 创建 requirements.txt result agent.run(task) print(f\n{*60}) print(f 最终结果:\n{result})执行效果Agent 会先list_files了解当前目录然后依次write_file创建app.py→requirements.txt→test_api.py每写完一个文件就用run_command验证语法最后用run_command跑一遍测试确认 API 可用。七、进阶方向这个 200 行的 Agent 是一个可扩展的骨架。以下是三个实用的进阶方向1. 增加代码搜索工具Agent 经常需要搜索特定函数或类的用法。给TOOL_DEFINITIONS添加一个grep_code工具def grep_code(pattern: str, path: str .) - str: 在代码文件中搜索模式匹配的行 import re results [] for f in Path(path).rglob(*.py): for i, line in enumerate(f.read_text(errorsignore).splitlines(), 1): if re.search(pattern, line): results.append(f{f}:{i}: {line.strip()}) return \n.join(results[:50]) # 最多返回 50 条2. 增加 Git 感知能力让 Agent 在修改代码前自动创建分支完成后再提交。这需要在write_file前后加入 git 操作让每一次修改都可追溯、可回滚。3. 引入 Diff 编辑模式当前write_file需要传完整文件内容大文件会浪费大量 token。改用 unified diff 格式Agent 只传变更部分大幅降低 token 消耗。OpenAI 的apply_patch思路值得参考。八、关键要点回顾本文从零搭建的 Coding Agent 虽然只有 200 行但涵盖了生产级 Agent 的核心要素LLM 调用封装、Tool Use 函数定义、对话历史管理、以及基于 ReAct 范式的执行循环。实际使用中这个 Agent 能完成文件创建、代码生成、语法检查、测试运行的全自动化流程。如果你手头有重复性的编码任务创建项目骨架、生成 CRUD 接口、写单元测试不妨让 Agent 先跑一轮你只需要 review 和微调。完整代码已整理在同一个 Python 文件里复制保存后设置DEEPSEEK_API_KEY环境变量即可运行。