
【CSDN 编者按】当 Claude Code、Devin 这些 AI 编程工具被神话成“下一代程序员”时,很多人以为背后是某种高不可攀的黑科技。但本文作者用不到 200 行 Python 代码,亲手拆解了一个最小可用的 Coding Agent,让开发者可以直观地看到:所谓“会读代码、会改项目”的智能体,本质只是一个围绕 LLM 的工具调用循环。
原文链接:https://www.mihaileric.com/The-Emperor-Has-No-Clothes/
现在的 AI 编程助手,用起来简直像魔法。
你随口用几句不太通顺的英语描述需求,它就能自动读代码、改项目、写出能跑的功能模块。Claude Code、Cursor、Devin,看起来都像是掌握了某种神秘黑科技。
但真相是:这些工具的核心,一点也不神秘——不到 200 行的 Python 代码,就能复刻一个可用的 AI 编程 Agent。
下面,让我们从零开始,亲手实现一个「会改代码的 LLM」。

在写代码前,先搞清楚:你在用 Claude Code 时,后台究竟发生了什么?
本质上,它就是大语言模型(LLM)+ 工具库的对话循环,具体步骤只有五步:
整个过程里,LLM 自始至终都没有直接操作你的文件系统,它只负责下达指令,真正干活的是你写的本地代码。

一个能用的代码助手,底层只需要三个核心功能,再多的功能都只是锦上添花:
没错,就是这么简单。像 Claude Code 这样的商用产品,还会额外集成 grep 检索、bash 命令执行、网页搜索等功能,但对我们来说,这三个工具就足够实现核心能力了。

import inspectimport jsonimport osimport anthropicfrom dotenv import load_dotenvfrom pathlib import Pathfrom typing import Any, Dict, List, Tupleload_dotenv()claude_client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
YOU_COLOR = "\u001b[94m"ASSISTANT_COLOR = "\u001b[93m"RESET_COLOR = "\u001b[0m"
def resolve_abs_path(path_str: str) -> Path:path = Path(path_str).expanduser()if not path.is_absolute():path = (Path.cwd() / path).resolve()return path

注意:工具函数的文档字符串(docstring)一定要写清楚,LLM 会根据这些描述判断该调用哪个工具、怎么传参——这是让 Agent 能正常工作的关键。
功能最简单的工具,传入文件名,返回文件的完整内容。
def read_file_tool(filename: str) -> Dict[str, Any]:full_path = resolve_abs_path(filename)with open(str(full_path), "r") as f:content = f.read()return {"file_path": str(full_path),"content": content}
返回字典格式,是为了给 LLM 传递结构化的执行结果,方便它理解。
帮 LLM 搞清楚项目结构,实现“导航”功能。
def list_files_tool(path: str) -> Dict[str, Any]:full_path = resolve_abs_path(path)all_files = []for item in full_path.iterdir():all_files.append({"filename": item.name,"type": "file" if item.is_file() else "dir"})return {"path": str(full_path),"files": all_files}
这是三个工具里最复杂的一个,但逻辑依然清晰,它主要处理两种场景:
old_str 参数为空时:创建新文件old_str 参数不为空时:替换文件中第一次出现的 old_str 为 new_strdef edit_file_tool(path: str, old_str: str, new_str: str) -> Dict[str, Any]:full_path = resolve_abs_path(path)if old_str == "":full_path.write_text(new_str, encoding="utf-8")return {"path": str(full_path), "action": "created_file"}original = full_path.read_text(encoding="utf-8")if original.find(old_str) == -1:return {"path": str(full_path), "action": "old_str not found"}edited = original.replace(old_str, new_str, 1)full_path.write_text(edited, encoding="utf-8")return {"path": str(full_path), "action": "edited"}
商用 IDE 的代码助手会有更复杂的容错逻辑,但这个极简版本足以验证核心原理。

我们需要一个“工具注册表”,把工具名称和对应的函数绑定起来,方便后续调用。
TOOL_REGISTRY = {"read_file": read_file_tool,"list_files": list_files_tool,"edit_file": edit_file_tool}

LLM 不会天生就知道怎么用我们的工具,我们需要通过系统提示词,把工具的名称、功能、参数格式告诉它。
我们先写两个辅助函数,从工具的函数签名和文档字符串里,自动生成工具说明:
def get_tool_str_representation(tool_name: str) -> str:tool = TOOL_REGISTRY[tool_name]return f"""Name: {tool_name}Description: {tool.__doc__}Signature: {inspect.signature(tool)}"""def get_full_system_prompt():tool_str_repr = ""for tool_name in TOOL_REGISTRY:tool_str_repr += "TOOL\n===" + get_tool_str_representation(tool_name)tool_str_repr += f"\n{'='*15}\n"return SYSTEM_PROMPT.format(tool_list_repr=tool_str_repr)
SYSTEM_PROMPT = """You are a coding assistant whose goal it is to help us solve coding tasks.You have access to a series of tools you can execute. Here are the tools you can execute:{tool_list_repr}When you want to use a tool, reply with exactly one line in the format: 'tool: TOOL_NAME({{JSON_ARGS}})' and nothing else.Use compact single-line JSON with double quotes. After receiving a tool_result(...) message, continue the task.If no tool is needed, respond normally."""

当 LLM 返回内容后,我们需要判断它是不是在请求调用工具。这个函数的作用就是从 LLM 的回复里,提取出工具名称和对应的参数。
def extract_tool_invocations(text: str) -> List[Tuple[str, Dict[str, Any]]]:"""Return list of (tool_name, args) requested in 'tool: name({...})' lines.The parser expects single-line, compact JSON in parentheses."""invocations = []for raw_line in text.splitlines():line = raw_line.strip()if not line.startswith("tool:"):continuetry:after = line[len("tool:"):].strip()name, rest = after.split("(", 1)name = name.strip()if not rest.endswith(")"):continuejson_str = rest[:-1].strip()args = json.loads(json_str)invocations.append((name, args))except Exception:continuereturn invocations

写一个简单的封装函数,负责把对话历史传给 LLM,并获取回复。
def execute_llm_call(conversation: List[Dict[str, str]]):system_content = ""messages = []for msg in conversation:if msg["role"] == "system":system_content = msg["content"]else:messages.append(msg)response = claude_client.messages.create(model="claude-sonnet-4-20250514",max_tokens=2000,system=system_content,messages=messages)return response.content[0].text

这一步是把前面所有的模块串起来,实现 Agent 的核心工作流,也是“魔法”发生的地方。
def run_coding_agent_loop():print(get_full_system_prompt())conversation = [{"role": "system","content": get_full_system_prompt()}]while True:try:user_input = input(f"{YOU_COLOR}You:{RESET_COLOR}:")except (KeyboardInterrupt, EOFError):breakconversation.append({"role": "user","content": user_input.strip()})while True:assistant_response = execute_llm_call(conversation)tool_invocations = extract_tool_invocations(assistant_response)if not tool_invocations:print(f"{ASSISTANT_COLOR}Assistant:{RESET_COLOR}: {assistant_response}")conversation.append({"role": "assistant","content": assistant_response})breakfor name, args in tool_invocations:tool = TOOL_REGISTRY[name]resp = ""print(name, args)if name == "read_file":resp = tool(args.get("filename", "."))elif name == "list_files":resp = tool(args.get("path", "."))elif name == "edit_file":resp = tool(args.get("path", "."),args.get("old_str", ""),args.get("new_str", ""))conversation.append({"role": "user","content": f"tool_result({json.dumps(resp)})"})
这个主循环的逻辑可以拆解为两层:
外层循环:获取用户输入,添加至对话内容;
内层循环:调用大型语言模型,检测工具调用需求;
→若无需工具,输出响应并终止内层循环;
→若需工具,执行工具操作,将结果添加至对话内容,循环继续。
内层循环持续进行,直至 LLM 响应时不再请求任何工具。这使 Agent 能够串联多个工具调用(例如:读取文件→编辑文件→确认编辑)。

加上主函数入口,运行我们的代码助手:
if __name__ == "__main__":run_coding_agent_loop()
现在,你就可以进行这样的对话了:
你:创建一个名为 hello.py 的新文件,并在其中实现"Hello World"功能
AI 助手调用 edit_file 函数,参数为 path="hello.py",old_str="",new_str="print('Hello World')"
AI 助手:完成!已创建包含 Hello World 实现的 hello.py 文件。
或者,还可以进行多步骤交互:
你:编辑 hello.py 并添加一个乘法函数
AI 助手调用 read_file 查看当前内容,再调用 edit_file 添加函数
AI 助手:已在 hello.py 中添加乘法函数

我们的代码只有 200 行左右,但已经实现了代码助手的核心逻辑。商用产品 Claude Code 之所以更强大,是因为它在这个基础上做了这些优化:
但核心工作流完全一致:LLM 决策 → 本地工具执行 → 结果反馈 → 继续决策。