1. 项目概述:250行Python实现的极简AI Agent
最近在GitHub上看到一个很有意思的项目 - Nano Code,这是一个仅用250行Python代码实现的极简版Claude Code替代方案。作为一名长期关注AI应用开发的工程师,我对这类轻量级实现特别感兴趣,于是花了几天时间深入研究并复现了这个项目。
这个项目的核心价值在于:它用最精简的代码(单文件、零第三方依赖)完整演示了一个LLM Agent的核心工作流程。不同于那些需要复杂框架的AI应用,Nano Code的代码量甚至比很多博客文章还要少,但功能却相当完整。我在本地实测后发现,它确实能够完成文件操作、代码编辑等基础开发任务。
提示:虽然项目命名为"Nano Code",但它的设计理念适用于任何需要轻量级AI助手的场景。如果你正在寻找一个不依赖复杂框架的AI集成方案,这个实现思路值得参考。
2. 核心原理与工具定义
2.1 工具系统设计
Nano Code的核心创新点在于其极简的工具系统设计。为了让AI具备基础开发能力,作者精心设计了六个核心工具函数:
python复制TOOLS = {
"read": ("Read file with line numbers", {"path": "string", "offset": "number?", "limit": "number?"}, read),
"write": ("Write content to file", {"path": "string", "content": "string"}, write),
"edit": ("Replace text in file", {"path": "string", "old": "string", "new": "string", "all": "boolean?"}, edit),
"glob": ("Find files by pattern", {"pattern": "string"}, glob),
"grep": ("Search text in files", {"pattern": "string", "paths": "string[]"}, grep),
"bash": ("Execute shell command", {"command": "string"}, bash)
}
每个工具定义包含三个关键部分:
- 工具描述 - 用自然语言说明功能
- 参数规范 - 定义输入参数的名称和类型
- 实现函数 - 实际执行操作的Python函数
这种设计巧妙地利用了LLM对结构化数据的理解能力。当AI模型(如Claude 3.5)看到这些定义时,它能自动理解每个工具的用途、参数要求,并在适当场景下调用它们。
2.2 工具实现细节
以最常用的read和write工具为例,我们来看具体实现:
python复制def read(path: str, offset: int = 0, limit: int = 100) -> str:
"""读取文件内容并添加行号"""
try:
with open(path, 'r') as f:
lines = f.readlines()[offset:offset+limit]
return ''.join(f"{i+offset+1}: {line}" for i, line in enumerate(lines))
except Exception as e:
return f"Error reading {path}: {str(e)}"
def write(path: str, content: str) -> str:
"""写入内容到文件"""
try:
with open(path, 'w') as f:
f.write(content)
return f"Successfully wrote to {path}"
except Exception as e:
return f"Error writing to {path}: {str(e)}"
这些实现有几个值得注意的设计细节:
- 错误处理:每个工具都包含try-catch块,确保单个工具失败不会导致整个系统崩溃
- 用户友好:
read工具自动添加行号,方便AI引用特定代码行 - 安全限制:
read默认限制返回100行,防止意外读取大文件
3. Agent工作流程实现
3.1 主循环结构
Nano Code的核心是一个简洁的REPL(Read-Eval-Print Loop)循环:
python复制def main():
print("Nano Code - 输入'quit'退出")
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
while True:
user_input = input("> ")
if user_input.lower() == 'quit':
break
messages.append({"role": "user", "content": user_input})
response = call_llm(messages)
if is_tool_call(response):
tool_name, params = parse_tool_call(response)
result = call_tool(tool_name, params)
messages.append({"role": "tool", "content": result})
else:
print(response)
messages.append({"role": "assistant", "content": response})
这个循环实现了标准Agent工作流:
- 接收用户输入
- 调用LLM获取响应
- 判断是否需要工具调用
- 执行工具并收集结果
- 将结果反馈给LLM进行下一步
3.2 工具调用解析
判断和解析工具调用的逻辑是这个项目的精华所在:
python复制def is_tool_call(response: str) -> bool:
"""检测响应是否包含工具调用"""
return response.strip().startswith("<tool_call>")
def parse_tool_call(response: str) -> tuple[str, dict]:
"""解析工具调用指令"""
try:
tool_part = response.split("<tool_call>")[1].split("</tool_call>")[0]
tool_name = tool_part.split("(")[0].strip()
params_str = tool_part.split("(")[1].rsplit(")", 1)[0]
params = eval(f"dict({params_str})") # 注意:实际项目应使用更安全的解析方式
return tool_name, params
except Exception as e:
raise ValueError(f"Invalid tool call format: {str(e)}")
注意:实际项目中应避免使用eval(),这里仅为演示简洁性。生产环境建议使用ast.literal_eval或专用解析库。
4. 系统提示词设计
4.1 核心提示词结构
Nano Code的成功很大程度上依赖于精心设计的系统提示词:
python复制SYSTEM_PROMPT = """
你是一个高效的编程助手,可以使用以下工具完成任务:
{tools_doc}
规则:
1. 严格按<tool_call>工具名(参数)</tool_call>格式调用工具
2. 保持操作简洁高效
3. 一次只执行一个明确的操作
4. 文件路径必须是相对当前目录的
5. 修改代码前必须先确认内容
当前工作目录:{cwd}
"""
这个提示词有几个关键设计点:
- 明确工具使用规范
- 强调安全操作原则
- 提供上下文信息(当前目录)
- 限制操作粒度(一次一个操作)
4.2 提示词工程技巧
在实际使用中,我发现这些提示词优化技巧特别有效:
- 工具文档生成:动态生成工具说明确保一致性
python复制def generate_tools_doc():
return "\n".join(
f"- {name}: {desc}\n 参数: {params}"
for name, (desc, params, _) in TOOLS.items()
)
- 上下文更新:每次工具调用后自动更新当前目录显示
python复制def update_cwd_in_prompt():
global SYSTEM_PROMPT
SYSTEM_PROMPT = SYSTEM_PROMPT.replace(
f"当前工作目录:{old_cwd}",
f"当前工作目录:{os.getcwd()}"
)
- 错误处理提示:当工具调用失败时自动补充诊断建议
5. 实战应用与优化建议
5.1 典型使用场景
经过实际测试,Nano Code特别适合以下场景:
- 自动化代码审查
bash复制> 检查main.py是否有语法错误
<tool_call>bash(command="python -m py_compile main.py")</tool_call>
> 结果:SyntaxError: invalid syntax (main.py, line 42)
- 批量文件操作
bash复制> 将所有测试文件中的"assertEqual"改为"assert"
<tool_call>grep(pattern="assertEqual", paths=["tests/"])</tool_call>
... [找到匹配文件] ...
<tool_call>edit(path="tests/test_api.py", old="assertEqual", new="assert")</tool_call>
- 快速原型开发
bash复制> 创建一个简单的Flask应用
<tool_call>write(path="app.py", content="from flask import Flask\napp = Flask(__name__)\n\n@app.route('/')\ndef home():\n return 'Hello World'")</tool_call>
<tool_call>bash(command="python app.py")</tool_call>
5.2 性能优化技巧
在长期使用中,我总结了这些优化经验:
- 工具调用缓存:对read操作添加缓存机制
python复制from functools import lru_cache
@lru_cache(maxsize=100)
def cached_read(path: str, offset: int, limit: int) -> str:
return read(path, offset, limit)
- 并发工具执行:使用线程池并行独立操作
python复制from concurrent.futures import ThreadPoolExecutor
def parallel_call(tools: list[tuple[str, dict]]):
with ThreadPoolExecutor() as executor:
results = list(executor.map(
lambda t: call_tool(t[0], t[1]),
tools
))
return results
- 操作历史记录:保存完整的会话历史便于审计
python复制import json
import time
def save_session(messages: list[dict], session_id: str = None):
session_id = session_id or str(int(time.time()))
with open(f"session_{session_id}.json", 'w') as f:
json.dump(messages, f, indent=2)
6. 安全增强与实践建议
6.1 安全防护措施
原始实现存在一些安全隐患,建议添加这些防护:
- 路径安全校验
python复制def validate_path(path: str) -> bool:
abs_path = os.path.abspath(path)
allowed_dir = os.path.abspath(".")
return abs_path.startswith(allowed_dir)
- 命令白名单
python复制ALLOWED_COMMANDS = {
"python": ["-m", "pip"],
"git": ["clone", "pull", "status"],
"ls": ["-l"],
}
def validate_command(cmd: str) -> bool:
parts = cmd.split()
if not parts: return False
return parts[0] in ALLOWED_COMMANDS and all(
part in ALLOWED_COMMANDS[parts[0]]
for part in parts[1:]
)
- 资源使用限制
python复制import resource
def set_limits():
# 限制CPU时间(秒)
resource.setrlimit(resource.RLIMIT_CPU, (1, 1))
# 限制内存(MB)
resource.setrlimit(resource.RLIMIT_AS, (256*1024*1024, 256*1024*1024))
6.2 生产环境扩展建议
如果需要用于更严肃的场景,考虑这些扩展方向:
- 用户权限系统
python复制USER_ROLES = {
"guest": ["read", "glob"],
"developer": ["read", "write", "edit", "glob"],
"admin": ALL_TOOLS
}
def check_permission(user: str, tool: str) -> bool:
return tool in USER_ROLES.get(user, [])
- 操作审计日志
python复制def log_operation(user: str, tool: str, params: dict):
with open("audit.log", "a") as f:
f.write(f"{time.ctime()} | {user} | {tool} | {params}\n")
- 会话状态管理
python复制class SessionState:
def __init__(self):
self.cwd = os.getcwd()
self.env = os.environ.copy()
self.history = []
def cd(self, new_dir: str):
if os.path.isdir(new_dir):
self.cwd = new_dir
return True
return False
这个极简实现最令人惊喜的是,它用如此少的代码就构建了一个功能完整的AI Agent框架。虽然不适合直接用于生产环境,但它清晰地展示了LLM Agent的核心原理,是学习AI系统设计的绝佳教材。我在项目中添加了类型注解和单元测试后,代码量仍然保持在300行以内,充分证明了Python的表达能力。