v0: Bash 就是你需要的一切
终极简化:约 50 行代码,1 个工具,完整的智能体能力。
在构建了 v1、v2 和 v3 之后,一个问题浮现了:智能体的本质是什么?
v0 通过向后退来回答这个问题——剥离一切,直到只剩下核心。
核心洞察
Unix 哲学:一切皆文件,一切皆可管道。Bash 是通往这个世界的网关:
| 你需要 | Bash 命令 |
|---|---|
| 读取文件 | cat, head, grep |
| 写入文件 | echo '...' > file |
| 搜索 | find, grep, rg |
| 执行 | python, npm, make |
| 子智能体 | python v0_bash_agent.py "任务" |
最后一行是关键洞察:通过 bash 调用自身实现子智能体。不需要任务工具,不需要智能体注册表——只需要递归。
完整代码
#!/usr/bin/env python
from anthropic import Anthropic
import subprocess, sys, os
client = Anthropic(api_key="your-key", base_url="...")
TOOL = [{
"name": "bash",
"description": """执行 shell 命令。模式:
- 读取:cat/grep/find/ls
- 写入:echo '...' > file
- 子智能体:python v0_bash_agent.py '任务描述'""",
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}
}]
SYSTEM = f"CLI 智能体位于 {os.getcwd()}。使用 bash。为复杂任务生成子智能体。"
def chat(prompt, history=[]):
history.append({"role": "user", "content": prompt})
while True:
r = client.messages.create(model="...", system=SYSTEM, messages=history, tools=TOOL, max_tokens=8000)
history.append({"role": "assistant", "content": r.content})
if r.stop_reason != "tool_use":
return "".join(b.text for b in r.content if hasattr(b, "text"))
results = []
for b in r.content:
if b.type == "tool_use":
out = subprocess.run(b.input["command"], shell=True, capture_output=True, text=True, timeout=300)
results.append({"type": "tool_result", "tool_use_id": b.id, "content": out.stdout + out.stderr})
history.append({"role": "user", "content": results})
if __name__ == "__main__":
if len(sys.argv) > 1:
print(chat(sys.argv[1])) # 子智能体模式
else:
h = []
while (q := input(">> ")) not in ("q", ""):
print(chat(q, h))
这就是整个智能体。约 50 行代码。
子智能体如何工作
主智能体
└─ bash: python v0_bash_agent.py "分析架构"
└─ 子智能体(独立进程,全新历史)
├─ bash: find . -name "*.py"
├─ bash: cat src/main.py
└─ 通过 stdout 返回摘要
进程隔离 = 上下文隔离
- 子进程有自己的
history=[] - 父进程捕获 stdout 作为工具结果
- 递归调用支持无限嵌套
v0 牺牲了什么
| 功能 | v0 | v3 |
|---|---|---|
| 智能体类型 | 无 | explore/code/plan |
| 工具过滤 | 无 | 白名单 |
| 进度显示 | 纯 stdout | 内联更新 |
| 代码复杂度 | 约 50 行 | 约 450 行 |
v0 证明了什么
复杂能力源于简单规则:
- 一个工具就够了 — Bash 是通往一切的网关
- 递归 = 层级 — 自调用实现子智能体
- 进程 = 隔离 — 操作系统提供上下文分离
- 提示 = 约束 — 指令塑造行为
核心模式永远不变:
while True:
response = model(messages, tools)
if response.stop_reason != "tool_use":
return response.text
results = execute(response.tool_calls)
messages.append(results)
其他一切——待办事项、子智能体、权限——都是围绕这个循环的细化。
学习笔记
重点关注什么
将 v0_bash_agent.py 作为一个小型可运行的编码智能体模型来阅读。文件虽小,但已经包含了核心架构:
LLM + 一个工具 + 反馈循环 = 智能体
不要一开始就记住每一行。首先理解五个移动部分:
client和MODELTOOLSYSTEMchat()__main__代码块
这五个部分足以解释整个文件。
1. 客户端和模型
在顶部,程序加载环境变量并创建 Anthropic 客户端:
load_dotenv()
client = Anthropic(
api_key=os.getenv("ANTHROPIC_API_KEY"),
base_url=os.getenv("ANTHROPIC_BASE_URL")
)
MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
这还不是智能体。这只是到模型 API 的连接。
智能体行为来自主机程序发送给模型的内容:
系统提示 + 消息 + 工具
2. 唯一的工具:bash
v0 只暴露一个工具:
TOOL = [{
"name": "bash",
"description": """执行 shell 命令。常见模式:
- 读取:cat/head/tail, grep/find/rg/ls, wc -l
- 写入:echo '内容' > file, sed -i 's/旧/新/g' file
- 子智能体:python v0_bash_agent.py '任务描述' ...""",
...
}]
工具模式告诉模型:
- 工具名称是
bash - 输入必须包含一个
command字符串 - 允许常见的命令模式
- 智能体可以将自己作为子智能体启动
模型不执行 shell 命令。它只请求工具调用。 Python 主机执行命令。
模型:请运行 {"command": "find . -name '*.py'"}
主机:运行 subprocess.run(...)
主机:将 stdout/stderr 作为 tool_result 返回
3. 系统提示教授操作规则
SYSTEM 告诉模型在这个 CLI 环境中如何表现:
SYSTEM = f"""你是位于 {os.getcwd()} 的 CLI 智能体。使用 bash 命令解决问题。
规则:
- 优先使用工具而非文字。先行动,事后简要解释。
- 读取文件:cat, grep, find, rg, ls, head, tail
- 写入文件:echo '...' > file, sed -i, 或 cat << 'EOF' > file
- 子智能体:对于复杂的子任务,生成一个子智能体...
"""
这很重要,因为 bash 的范围非常广。提示将这个广泛的工具窄化为有用的编码智能体行为。
可以这样理解:
TOOL 说明模型可以调用什么。
SYSTEM 说明如何以及何时使用它。
4. 整个智能体就是 chat()
chat() 函数是智能体循环:
def chat(prompt, history=None):
history.append({"role": "user", "content": prompt})
while True:
response = client.messages.create(...)
history.append({"role": "assistant", "content": content})
if response.stop_reason != "tool_use":
return final_text
results = execute_tool_calls(...)
history.append({"role": "user", "content": results})
这个循环只有一个工作:不断给模型观察结果,直到模型停止请求工具。
5. 为什么工具结果必须返回 history
这一行是反馈循环:
history.append({"role": "user", "content": results})
如果程序只打印命令输出到终端,人类会看到它,但模型不会。
对于智能体,每个动作都需要一个观察:
思考 → 工具调用 → 工具结果 → 下一个思考
这就是为什么工具结果被追加到 history 并发回给模型。
6. 交互模式 vs 子智能体模式
在底部,文件有两种模式:
if len(sys.argv) > 1:
print(chat(sys.argv[1]))
else:
history = []
while True:
query = input(">> ")
print(chat(query, history))
交互模式保持一个共享的历史:
人类 → 智能体 → 人类 → 智能体
子智能体模式运行一个任务然后退出:
python v0_bash_agent.py "探索 src/ 并摘要"
这使得递归成为可能。
7. v0 中的子智能体如何工作
v0 没有 Task 工具,也没有智能体注册表。子智能体之所以工作,是因为唯一的工具足够强大,可以启动另一个进程:
python v0_bash_agent.py "分析架构"
那个子进程有一个全新的 history。父进程只接收子进程的 stdout 作为 bash 工具结果。
父进程历史
-> bash: python v0_bash_agent.py "分析架构"
-> 子进程历史开始为空
-> 子进程用 bash 探索
-> 子进程打印最终摘要
-> 父进程接收摘要作为 tool_result
这几乎免费地提供了上下文隔离:
进程隔离 = 上下文隔离
8. 为什么 Bash 对 v0 来说足够了
Bash 是通往许多能力的网关:
| 需要 | Bash 模式 |
|---|---|
| 列出文件 | find, ls, rg --files |
| 读取文件 | cat, head, sed -n |
| 搜索 | grep, rg |
| 运行测试 | pytest, npm test, make test |
| 编辑文件 | shell 重定向, sed, heredocs |
| 生成子智能体 | python v0_bash_agent.py "任务" |
这就是 v0 的教训:一个单一的通用工具可以创造出令人惊讶的强大智能体行为。
9. v0 还没有教什么
v0 是有意地不安全和最小化的。不要把它误认为是生产环境的框架。
缺失的部分包括:
- 命令审批
- 文件系统沙箱
- 网络限制
- 结构化文件编辑工具
- 待办事项跟踪
- 类型化子智能体
- 技能加载
- 健壮的错误处理
这些是后来添加的,因为 v0 的重点是看到最小可能的智能体循环。
10. 安全边界
有风险的行是:
subprocess.run(cmd, shell=True, ...)
在无限制的 shell 访问下,模型可以请求破坏性的命令。真正的编码智能体添加了沙箱、审批门、命令策略和审计日志。
对于学习来说,关键的收获不是"给智能体 shell 访问权限"。收获是:
智能体 = 模型决策 + 主机执行的工具 + 返回的观察结果
学习检查
阅读代码后,确保你能回答:
- 单个工具在哪里定义?
- 工具模式需要什么作为输入?
- 程序在哪里调用模型?
- 工具结果在哪里追加回历史?
- 为什么
stop_reason != "tool_use"结束循环? python v0_bash_agent.py "任务"如何创建子智能体?- v0 缺少哪些安全控制?
完整源码
#!/usr/bin/env python
"""
v0_bash_agent.py - Mini Claude Code:Bash 就是你需要的一切(核心约 50 行)
核心理念:"Bash 就是你需要的一切"
====================================
这是编码智能体的终极简化。在构建了 v1-v3 之后,
我们问:智能体的本质是什么?
答案是:一个工具(bash)+ 一个循环 = 完整的智能体能力。
为什么 Bash 足够:
----------------
Unix 哲学说一切皆文件,一切皆可管道。
Bash 是通往这个世界的网关:
| 你需要 | Bash 命令 |
|---------------|----------------------------------------|
| 读取文件 | cat, head, tail, grep |
| 写入文件 | echo '...' > file, cat << 'EOF' > file |
| 搜索 | find, grep, rg, ls |
| 执行 | python, npm, make, 任何命令 |
| **子智能体** | python v0_bash_agent.py "任务" |
最后一行是关键洞察:通过 bash 调用自身实现子智能体!
不需要 Task 工具,不需要智能体注册表——只需要通过进程生成的递归。
子智能体如何工作:
----------------
主智能体
|-- bash: python v0_bash_agent.py "分析架构"
|-- 子智能体(独立进程,全新历史)
|-- bash: find . -name "*.py"
|-- bash: cat src/main.py
|-- 通过 stdout 返回摘要
进程隔离 = 上下文隔离:
- 子进程有自己的 history=[]
- 父进程捕获 stdout 作为工具结果
- 递归调用支持无限嵌套
用法:
# 交互模式
python v0_bash_agent.py
# 子智能体模式(由父智能体调用或直接调用)
python v0_bash_agent.py "探索 src/ 并摘要"
"""
from anthropic import Anthropic
from dotenv import load_dotenv
import subprocess
import sys
import os
# 从 .env 文件加载环境变量
load_dotenv()
# 使用来自环境的凭证初始化 API 客户端
client = Anthropic(
api_key=os.getenv("ANTHROPIC_API_KEY"),
base_url=os.getenv("ANTHROPIC_BASE_URL")
)
MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
# 做一切的那一个工具
# 注意描述如何教模型常见模式以及如何生成子智能体
TOOL = [{
"name": "bash",
"description": """执行 shell 命令。常见模式:
- 读取:cat/head/tail, grep/find/rg/ls, wc -l
- 写入:echo '内容' > file, sed -i 's/旧/新/g' file
- 子智能体:python v0_bash_agent.py '任务描述'(生成隔离的智能体,返回摘要)""",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"]
}
}]
# 系统提示教模型如何有效地使用 bash
# 注意子智能体指导——这就是我们如何获得分层任务分解
SYSTEM = f"""你是位于 {os.getcwd()} 的 CLI 智能体。使用 bash 命令解决问题。
规则:
- 优先使用工具而非文字。先行动,事后简要解释。
- 读取文件:cat, grep, find, rg, ls, head, tail
- 写入文件:echo '...' > file, sed -i, 或 cat << 'EOF' > file
- 子智能体:对于复杂的子任务,生成一个子智能体以保持上下文干净:
python v0_bash_agent.py "探索 src/ 并摘要架构"
何时使用子智能体:
- 任务需要读取许多文件(隔离探索)
- 任务是独立且自包含的
- 你想避免用中间细节污染当前对话
子智能体隔离运行,只返回其最终摘要。"""
def chat(prompt, history=None):
"""
完整的智能体循环在一个函数中。
这是所有编码智能体共享的核心模式:
while not done:
response = model(messages, tools)
if no tool calls: return
execute tools, append results
参数:
prompt: 用户的请求
history: 对话历史(可变,在交互模式下跨调用共享)
返回:
模型的最终文本响应
"""
if history is None:
history = []
history.append({"role": "user", "content": prompt})
while True:
# 步骤 1:用工具调用模型
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=history,
tools=TOOL,
max_tokens=8000
)
# 步骤 2:构建助手消息内容(保留文本和 tool_use 块)
content = []
for block in response.content:
if hasattr(block, "text"):
content.append({"type": "text", "text": block.text})
elif block.type == "tool_use":
content.append({
"type": "tool_use",
"id": block.id,
"name": block.name,
"input": block.input
})
history.append({"role": "assistant", "content": content})
# 步骤 3:如果模型没有调用工具,我们完成了
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if hasattr(b, "text"))
# 步骤 4:执行每个工具调用并收集结果
results = []
for block in response.content:
if block.type == "tool_use":
cmd = block.input["command"]
print(f"\033[33m$ {cmd}\033[0m") # 命令用黄色
try:
out = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
timeout=300,
cwd=os.getcwd()
)
output = out.stdout + out.stderr
except subprocess.TimeoutExpired:
output = "(300秒后超时)"
print(output or "(空)")
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output[:50000] # 截断非常长的输出
})
# 步骤 5:追加结果并继续循环
history.append({"role": "user", "content": results})
if __name__ == "__main__":
if len(sys.argv) > 1:
# 子智能体模式:执行任务并打印结果
# 这就是父智能体如何通过 bash 生成子进程
print(chat(sys.argv[1]))
else:
# 交互 REPL 模式
history = []
while True:
try:
query = input("\033[36m>> \033[0m") # 青色提示
except (EOFError, KeyboardInterrupt):
break
if query in ("q", "exit", ""):
break
print(chat(query, history))
Bash 就是你需要的一切。