你的代码 80% 可以由 AI 写——手把手教你搭建 Coding Agent
从零搭建 AI Coding Agent 的完整教程,涵盖 LLM 调用、Tool Use、对话管理和 Agent 主循环四大模块,200 行 Python 代码实现自动写代码的智能助手。

一、你的代码,真的必须全部手写吗?
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 API |
| Tool 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_factory=list)
@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(timeout=120)
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": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json=payload
)
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_name=func["name"],
tool_args=json.loads(func["arguments"]),
call_id=tc["id"]
))
return response
这里有几个工程细节值得注意。temperature=0.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(encoding="utf-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(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-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, shell=True, cwd=cwd,
capture_output=True, text=True, timeout=30
)
out = result.stdout[:4000] # 防止输出过长
err = result.stderr[:2000]
rc = f"exit_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] 命令超时(30s)"
run_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_ascii=False)})")
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_ascii=False)
}
}]
})
self.messages.append({
"role": "tool",
"tool_call_id": tc.call_id,
"content": result
})
return "[Warning] 达到最大步数限制,任务可能未完成"
几个关键设计决策: - max_steps=15 是经验值,太少完不成复杂任务,太多容易在修复循环中打转 - 每个工具调用的结果都完整记录到对话历史,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_key=api_key,
base_url=os.getenv("LLM_BASE_URL", "https://api.deepseek.com/v1"),
model=os.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(errors="ignore").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 环境变量即可运行。

更多推荐



所有评论(0)