你本可以发明 Claude Code

  • dabit3
  • 发布于 4天前
  • 阅读 37

本文从零开始,逐步构建了 Claude Code 的架构,展示了如何从第一性原理出发,利用终端、LLM API 构建 AI Agent。文章详细介绍了构建 AI Agent 的核心概念,如Agent Loop、工具的使用、权限控制、上下文管理和项目特定配置,并提供了一个简化的 Claude Code 实现示例。

nader dabit @dabit3 Image

你本可以发明 Claude Code

复杂性来自于处理边界情况、构建良好的用户体验以及与实际开发工作流程集成。

在这篇文章中,我将从头开始,逐步构建 Claude Code 的架构,展示如何仅使用终端、LLM API 和使 AI 真正有用的愿望,从第一性原理出发自己发明它。

最终目标:学习强大的代理如何工作,以便你可以构建自己的代理

首先,让我们确定要解决的问题。

当你在浏览器中使用 ChatGPT 或 Claude 时,你正在进行大量的手工劳动:

  • 将代码从聊天复制粘贴到文件中

  • 自己运行命令,然后复制错误信息

  • 通过上传文件或粘贴内容来提供上下文

  • 手动迭代修复-测试-调试循环

你本质上是在充当 AI 的双手。 AI 思考;你执行。

如果 AI 也能执行呢?

想象一下告诉 AI:“修复 auth.py 中的 bug”,然后走开。当你回来时,bug 已经修复。 AI 读取了文件,理解了它,尝试了一种修复方法,运行了测试,发现它们失败了,尝试了另一种方法,最终成功了。

这就是代理所做的事情。它是一个可以执行以下操作的 AI:

  1. 在现实世界中采取行动(读取文件、运行命令)

  2. 观察结果

  3. 决定下一步做什么

  4. 重复直到任务完成

让我们从头开始构建一个。

最简单的代理

让我们从绝对最小值开始:一个可以运行单个 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。这要好得多,因为:

  1. 类型安全:AI 确切地知道每个工具接受哪些参数

  2. 显式操作:读取文件是一个 read_file 调用,而不是 cat

  3. 受控的表面积:我们决定存在哪些工具

尝试一下:

> 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 可以:

  1. glob("**/*.py") → 查找所有 Python 文件

  2. grep("def authenticate", "src/") → 查找身份验证代码

  3. read_file("src/auth.py") → 读取相关文件

  4. 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 编码代理的核心是这个循环:

  1. 设置(运行一次)

    • 加载带有工具描述、行为准则和项目上下文的系统提示(CLAUDE.md

    • 初始化一个空的对话历史记录

  2. 代理循环(重复直到完成)

    • 将对话历史记录发送到 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 脚本开始,我们发现:

  1. 代理循环:AI 决定 → 执行 → 观察 → 重复

  2. 结构化工具:比原始 bash 更好,更安全、更精确

  3. 外科手术式编辑:str_replace 胜过完整的文件重写

  4. 搜索工具:让 AI 探索代码库

  5. 上下文管理:压缩和委派处理长时间任务

  6. 项目知识:CLAUDE.md 提供特定于项目的上下文

这些中的每一个都源于一个实际问题:

  • “如何让 AI 做更多的事情?” → 代理循环

  • “如何防止它破坏我的系统?” → 权限系统

  • “如何使编辑高效?” → str_replace 工具

  • “它如何找到它不知道的代码?” → 搜索工具

  • “当上下文耗尽时会发生什么?” → 压缩

  • “它如何知道我的项目的约定?” → CLAUDE.md 这就是你本可以发明 Claude Code 的方式。 核心思想很简单。

再次 - 复杂性来自于处理边界情况、构建良好的用户体验以及与实际开发工作流程集成。

下一步

如果你想构建自己的代理:

  1. 从简单的开始:一个具有 2-3 个工具的基本代理循环

  2. 逐步添加工具:每个新功能都应解决一个实际问题

  3. 优雅地处理错误:工具会失败;你的代理应该恢复

  4. 在实际任务上进行测试:边界情况会告诉你缺少什么

  5. 考虑使用 Claude 代理 SDK:为什么要重新发明轮子?

软件开发的未来是实际可以做事情的代理。 现在我们知道它们是如何工作的了!

资源:

如果你有兴趣构建可验证的代理,请查看我们在

@eigencloud

这里

所做的工作。

  • 原文链接: x.com/dabit3/status/2009...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
dabit3
dabit3
江湖只有他的大名,没有他的介绍。