本文从零开始,逐步构建了 Claude Code 的架构,展示了如何从第一性原理出发,利用终端、LLM API 构建 AI Agent。文章详细介绍了构建 AI Agent 的核心概念,如Agent Loop、工具的使用、权限控制、上下文管理和项目特定配置,并提供了一个简化的 Claude Code 实现示例。
你本可以发明 Claude Code
复杂性来自于处理边界情况、构建良好的用户体验以及与实际开发工作流程集成。
在这篇文章中,我将从头开始,逐步构建 Claude Code 的架构,展示如何仅使用终端、LLM API 和使 AI 真正有用的愿望,从第一性原理出发自己发明它。
最终目标:学习强大的代理如何工作,以便你可以构建自己的代理
首先,让我们确定要解决的问题。
当你在浏览器中使用 ChatGPT 或 Claude 时,你正在进行大量的手工劳动:
将代码从聊天复制粘贴到文件中
自己运行命令,然后复制错误信息
通过上传文件或粘贴内容来提供上下文
手动迭代修复-测试-调试循环
你本质上是在充当 AI 的双手。 AI 思考;你执行。
如果 AI 也能执行呢?
想象一下告诉 AI:“修复 auth.py 中的 bug”,然后走开。当你回来时,bug 已经修复。 AI 读取了文件,理解了它,尝试了一种修复方法,运行了测试,发现它们失败了,尝试了另一种方法,最终成功了。
这就是代理所做的事情。它是一个可以执行以下操作的 AI:
在现实世界中采取行动(读取文件、运行命令)
观察结果
决定下一步做什么
重复直到任务完成
让我们从头开始构建一个。
最简单的代理
让我们从绝对最小值开始:一个可以运行单个 bash 命令的 AI。
用法
> fix_bug("ls -l")
total 16
-rw-r--r-- 1 ubuntu staff 823 Jan 11 22:34 README.md
-rw-r--r-- 1 ubuntu staff 133 Jan 11 22:34 main.py
这...不是很有用。 AI 可以提出一个命令,然后你又得手动完成所有事情。
但这里有一个关键的见解:如果我们把它放在一个循环中会怎样?
目标:创建代理循环
所有 AI 代理背后的基本见解是代理循环:
让我们准确地实现这一点。 AI 需要告诉我们:
要采取什么行动
是否完成
我们将使用一个简单的 JSON 格式:
{
"action": "bash",
"command": "ls -l",
"done": false
}
现在我们有一些可以实际迭代的东西:
> fix_bug("Fix the linting errors")
AI: {"action": "bash", "command": "pylint main.py", "done": false}
OUTPUT: main.py:1:0: C0301: Line too long (133/100) (line-too-long)
AI: {"action": "bash", "command": "autopep8 main.py", "done": false}
OUTPUT:
AI: {"action": "done", "done": true}
AI 运行了两个命令,然后告诉我们它完成了。我们创建了一个代理循环!
但是等等!我们正在执行任意命令,没有安全检查。 AI 可以 rm -rf /,我们会盲目地执行它。
目标:添加权限控制
让我们为危险操作添加人工参与的环节。首先,我们定义一个函数,用安全检查包装命令执行:
def eval_with_approval(command):
# Ask user for approval
approval = input(f"Approve command? {command} (y/n): ")
if approval.lower() == 'y':
return eval(command)
else:
return "Command denied"
然后,在代理循环内部,我们将直接的 eval 调用替换为我们的新函数:
action = response["action"]
if action == "bash":
command = response["command"]
observation = eval_with_approval(command)
就是这样!该函数位于 AI 的请求和实际执行之间,让你有机会阻止危险命令。当被拒绝时,你可以将此反馈给 AI,以便它可以尝试不同的方法。
尝试一下:
> fix_bug("Delete all the files")
AI: {"action": "bash", "command": "rm -rf /", "done": false}
Approve command? rm -rf / (y/n): n
AI: {"action": "done", "done": true}
键入 y 以允许删除,或键入 n 以阻止它。
这是权限系统的开始。 Claude Code 在此基础上更进一步:
特定于工具的权限(文件编辑与 bash 命令)
基于模式的允许列表(Bash(npm test:*) 允许任何 npm test 命令)
会话级别的“接受所有”模式,用于当你信任 AI 时
关键的见解:人应该能够控制 AI 可以做什么,但要有足够的粒度,使其不会令人讨厌。
目标:超越 bash - 添加工具
运行 bash 命令功能强大,但它也是:
危险:无限制地访问系统
效率低下:读取文件不应产生子进程
不精确:输出解析很脆弱
如果我们给 AI 提供结构化的工具会怎样?
我们将在这里切换到 Python,因为它能更干净地处理 JSON 和 API 调用:
tools = {
"read_file": lambda path: open(path).read(),
"write_file": lambda path, content: open(path, 'w').write(content)
}
def run_agent(task):
prompt = f"You can use these tools: {tools.keys()}"
while True:
response = anthropic.completions.create(
model="claude-2",
max_tokens_to_sample=1000,
prompt=f"{prompt}\nUser: {task}\nAI:",
stop_sequences = ["\nUser:"]
).completion
try:
action = extract_json(response)
if action["action"] == "read_file":
content = tools["read_file"](action["path"])
prompt += f"\nFile content: {content}"
elif action["action"] == "write_file":
tools["write_file"](action["path"], action["content"])
prompt += "\nWrote file"
elif action["action"] == "done":
return action["answer"]
except:
prompt += "\nError. Please try again."
现在我们正在使用 Anthropic 的原生工具使用 API。这要好得多,因为:
类型安全:AI 确切地知道每个工具接受哪些参数
显式操作:读取文件是一个 read_file 调用,而不是 cat
受控的表面积:我们决定存在哪些工具
尝试一下:
> run_agent("List the files in the directory")
AI: {"action": "read_file", "path": "ls -l"}
OUTPUT: Error. Please try again.
AI: {"action": "read_file", "path": "README.md"}
OUTPUT: Claude > ChatGPT
AI: {"action": "done", "answer": "Files: README.md"}
目标:使编辑精确
我们的 write_file 工具存在一个问题:它会替换整个文件。如果 AI 对一个 1000 行的文件进行了小的更改,它必须输出所有 1000 行。这是:
昂贵:更多的输出 tokens = 更多的成本
容易出错:AI 可能会意外删除行
缓慢:生成这么多文本需要时间
如果我们有一个用于外科手术式编辑的工具会怎样?
实现:
def str_replace(path, old, new):
content = open(path).read()
assert old in content # prevent mass replacement
content = content.replace(old, new)
open(path, 'w').write(content)
tools = {
"read_file": lambda path: open(path).read(),
"write_file": lambda path, content: open(path, 'w').write(content),
"str_replace": str_replace
}
这正是 Claude Code 的 str_replace 工具的工作方式。对唯一性的要求可能看起来很烦人,但它实际上是一个功能:
迫使 AI 包含足够的上下文以消除歧义
创建一个自然的差异,便于人类审查
防止意外的大量替换
目标:搜索代码库
到目前为止,我们的代理可以读取它知道的文件。但是像“找到身份验证 bug 的位置”这样的任务呢?
AI 需要搜索代码库。让我们为此添加工具。
import glob, subprocess
tools = {
"read_file": lambda path: open(path).read(),
"write_file": lambda path, content: open(path, 'w').write(content),
"str_replace": str_replace,
"glob": glob.glob
"grep": lambda pattern, path: subprocess.check_output(['grep', pattern, path]).decode('utf-8')
}
现在 AI 可以:
glob("**/*.py") → 查找所有 Python 文件
grep("def authenticate", "src/") → 查找身份验证代码
read_file("src/auth.py") → 读取相关文件
edit_file(...) → 修复 bug
这就是模式:为 AI 提供探索工具,它可以导航以前从未见过的代码库。
目标:上下文管理
以下是你将很快遇到的问题:上下文窗口是有限的。
如果你在一个大型代码库上工作,对话可能如下所示:
用户:“修复身份验证中的 bug”
AI:读取 10 个文件,运行 20 个命令,尝试 3 种方法
...对话现在有 100,000 个 tokens
AI:耗尽上下文并开始忘记早期信息
我们该如何处理这个问题?
选项 1:摘要(压缩)
当上下文变得太长时,总结发生了什么:
conversation = summarize(conversation)
选项 2:子代理(委派)
对于复杂的任务,生成一个具有自己上下文的子代理:
sub_agent = Agent(context=smaller_context)
sub_agent.run(focused_task)
这就是 Claude Code 具有子代理概念的原因:专门的代理在其自己的上下文中处理集中的任务,仅返回结果。
目标:系统提示
我们一直在忽略一些重要的事情:AI 如何知道如何表现?
系统提示是你编码的地方:
AI 的身份和能力
工具使用指南
特定于项目的上下文
行为规则
以下是一个简化的版本,说明了 Claude Code 的有效性:
You are Claude Code, an AI coding assistant.
- Use the tools to understand the codebase, then write code to solve the task.
- Start by listing the files in the directory.
- When writing code, be concise.
但这里有一个问题:如果项目有特定的约定怎么办?如果团队使用特定的测试框架,或者具有非标准的目录结构怎么办?
目标:特定于项目的上下文(CLAUDE.md)
Claude Code 使用 CLAUDE.md 解决了这个问题 - 项目根目录下的一个文件,该文件会自动包含在上下文中:
# Project: FancyProject
This project uses pytest for testing. Run tests with: pytest tests/.
The source code is in the src/ directory.
The authentication code is in src/auth.py.
现在 AI 知道:
如何为此项目运行测试
在哪里可以找到东西
要遵循哪些约定
要注意哪些已知的陷阱
这是 Claude Code 最强大的功能之一:随代码一起传递的项目知识。
将它们放在一起
让我们看看我们构建了什么。 AI 编码代理的核心是这个循环:
设置(运行一次)
加载带有工具描述、行为准则和项目上下文的系统提示(CLAUDE.md)
初始化一个空的对话历史记录
代理循环(重复直到完成)
将对话历史记录发送到 LLM
LLM 决定:使用工具或回复用户
如果使用工具:
LLM 选择一个工具并提供参数
执行该工具并观察结果
将结果添加到对话历史记录
如果最终答案:
就是这样。从我们的 50 行 bash 脚本到 Claude Code,每个 AI 编码代理都遵循这种模式。
现在让我们构建一个完整的、可工作的 mini-Claude Code,你可以实际使用它。它结合了我们所学的一切:代理循环、结构化工具、权限检查和一个交互式 REPL:
import anthropic, json, os, glob, subprocess
# -----------------------------------------------------------------------
# CONFIG
# -----------------------------------------------------------------------
MODEL_NAME = "claude-2"
SYSTEM_PROMPT = """
You are a coding assistant. You can use tools to read, write, and modify files.
"""
# -----------------------------------------------------------------------
# TOOLS
# -----------------------------------------------------------------------
def read_file(path):
print(f"READ: {path}")
try: return open(path).read()
except: return "Error: Could not read file."
def write_file(path, content):
print(f"WRITE: {path}")
with open(path, 'w') as f:
f.write(content)
return "Wrote file successfully."
def str_replace(path, old, new):
print(f"REPLACE: {path}")
content = open(path).read()
assert old in content # prevent mass replacement
content = content.replace(old, new)
open(path, 'w').write(content)
return "Replaced text successfully."
def run_command(command):
print(f"RUN: {command}")
try:
result = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).decode('utf-8')
return result
except subprocess.CalledProcessError as e:
return f"Error: {e.output.decode('utf-8')}"
TOOLS = {
"read_file": read_file,
"write_file": write_file,
"str_replace": str_replace,
"run_command": run_command,
"glob": glob.glob,
}
# -----------------------------------------------------------------------
# AGENT
# -----------------------------------------------------------------------
def run_agent(task, conversation):
prompt = f"""{SYSTEM_PROMPT}
You have access to these tools: {TOOLS.keys()}.
To use a tool, specify the tool name and parameters in JSON.
For example: {{"tool": "read_file", "path": "my_file.txt"}}
Here is the conversation history:
{conversation}
Now, respond to the user.
User: {task}
AI: """
response = anthropic.completions.create(
model=MODEL_NAME,
max_tokens_to_sample=2000,
prompt=prompt,
stop_sequences = ["User:"]
).completion
try:
action = json.loads(response)
tool_name = action["tool"]
tool = TOOLS[tool_name]
params = {k: v for k, v in action.items() if k != "tool"} # remove "tool"
observation = tool(**params)
conversation += f"User: {task}\nAI: {response}\nObservation: {observation}\n"
return observation, conversation
except Exception as e:
return f"I don't understand. Error: {e}", conversation
# -----------------------------------------------------------------------
# MAIN LOOP
# -----------------------------------------------------------------------
if __name__ == "__main__":
os.environ["ANTHROPIC_API_KEY"] = input("Please enter your Anthropic API key: ")
conversation = ""
while True:
task = input("Enter your task: ")
observation, conversation = run_agent(task, conversation)
print(observation)
将其另存为 mini-claude-code.py 并运行它:
python mini-claude-code.py
这是一个会话的样子:
Enter your task: List the files in the current directory
RUN: ls -l
total 24
-rw-r--r-- 1 ubuntu ubuntu 3753 Jan 12 00:49 mini-claude-code.py
-rw-r--r-- 1 ubuntu ubuntu 144 Jan 12 00:25 README.md
...
Enter your task: Read the contents of mini-claude-code.py
READ: mini-claude-code.py
import anthropic, json, os, glob, subprocess
...
Enter your task: Replace MODEL_NAME with claude-v1
REPLACE: mini-claude-code.py
Replaced text successfully.
Enter your task: done
Okay, I'm done!
这是一个在约 150 行代码中工作的 mini Claude Code 克隆。它具有:
交互式 REPL:在提示之间保持对话上下文
多个工具:读取、写入、列出文件、运行命令
权限检查:在写入文件或运行危险命令之前询问
对话记忆:每个后续步骤都建立在先前的上下文之上
这本质上是 Claude Code 所做的,加上:
一个精致的终端 UI
完善的权限系统
当对话变长时进行上下文压缩
用于复杂任务的子代理委派
用于自定义自动化的Hook
与 git 和其他开发工具集成
Claude 代理 SDK
如果你想在此基础上进行构建,而无需重新发明轮子,Anthropic 提供了 Claude 代理 SDK。 它是为 Claude Code 提供支持的同一引擎,以库的形式公开。
以下是使用 SDK 的简单代理的样子:
from claude_agent import ClaudeAgent
agent = ClaudeAgent(
model_name="claude-2",
system_prompt="You are a helpful coding assistant."
)
response = agent.run(task="List the files in the current directory")
print(response)
SDK 处理:
代理循环(因此你不必这样做)
所有内置工具(读取、写入、编辑、Bash、Glob、Grep 等)
权限管理
上下文跟踪
子代理协调
我们学到了什么
从一个简单的 bash 脚本开始,我们发现:
代理循环:AI 决定 → 执行 → 观察 → 重复
结构化工具:比原始 bash 更好,更安全、更精确
外科手术式编辑:str_replace 胜过完整的文件重写
搜索工具:让 AI 探索代码库
上下文管理:压缩和委派处理长时间任务
项目知识:CLAUDE.md 提供特定于项目的上下文
这些中的每一个都源于一个实际问题:
“如何让 AI 做更多的事情?” → 代理循环
“如何防止它破坏我的系统?” → 权限系统
“如何使编辑高效?” → str_replace 工具
“它如何找到它不知道的代码?” → 搜索工具
“当上下文耗尽时会发生什么?” → 压缩
“它如何知道我的项目的约定?” → CLAUDE.md 这就是你本可以发明 Claude Code 的方式。 核心思想很简单。
再次 - 复杂性来自于处理边界情况、构建良好的用户体验以及与实际开发工作流程集成。
下一步
如果你想构建自己的代理:
从简单的开始:一个具有 2-3 个工具的基本代理循环
逐步添加工具:每个新功能都应解决一个实际问题
优雅地处理错误:工具会失败;你的代理应该恢复
在实际任务上进行测试:边界情况会告诉你缺少什么
考虑使用 Claude 代理 SDK:为什么要重新发明轮子?
软件开发的未来是实际可以做事情的代理。 现在我们知道它们是如何工作的了!
资源:
如果你有兴趣构建可验证的代理,请查看我们在
所做的工作。
- 原文链接: x.com/dabit3/status/2009...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!