1. 项目概述
在人工智能领域,构建自主决策的智能代理(Agent)已成为当前研究的热点。本文将从零开始,逐步构建一个基于大语言模型(LLM)的智能代理系统,实现从简单的Bash命令执行到复杂技能调用的完整演进过程。这个项目特别适合对AI代理开发感兴趣的开发者,无论是想了解基础原理还是希望构建自己的代理系统,都能从中获得实用价值。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心设计思路
2.1 AI Agent的基本构成
一个完整的AI Agent由三个核心要素构成:
-
模型(LLM):作为智能体的"大脑",负责推理和决策。模型的能力决定了Agent的下限,但通过合理的工具和指令设计,即使是相对较弱的模型也能构建出功能强大的Agent。
-
工具(Tools):Agent可调用的外部函数或API,扩展了模型的能力边界。工具的设计直接影响Agent能完成的任务范围。
-
指令(Instructions):定义Agent行为的明确指导方针和安全策略,确保Agent的行为符合预期。
这三个要素共同决定了Agent的能力上限。在实际开发中,我们需要在模型能力、工具丰富度和指令精确度之间找到平衡点。
2.2 Tool Calling机制
现代大语言模型普遍支持Tool Calling协议,这是一种标准化的工具调用方式。其核心是通过定义清晰的工具schema,让模型知道何时以及如何调用工具。一个典型的工具定义如下:
json复制{
"name": "my_function_name",
"description": "The description of my function",
"input_schema": {
"type": "object",
"properties": {
"query": {
"description": "The search query to perform."
}
},
"required": ["query"]
}
}
当模型决定调用工具时,会返回结构化的调用请求:
json复制{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835123",
"name": "my_function_name",
"input": {
"query": "Latest developments in quantum computing"
}
}
这种标准化的交互方式使得不同模型和工具之间能够无缝协作,为构建复杂的Agent系统奠定了基础。
3. 版本演进路线
3.1 从简单到复杂的演进路径
我们设计了从简单到复杂的五个版本演进路线:
-
v0: Shell作为基础工具:以Bash命令作为最基础的工具集,构建最简单的Agent原型。
-
v1: 模型即代理:赋予模型自主决策权,让它决定何时调用哪些工具。
-
v2: 结构化规划与Todo:引入任务管理系统,让Agent能够更好地规划复杂任务。
-
v3: 子代理:实现代理的分层结构,允许主代理创建和管理子代理。
-
v4: Skills:将常用功能封装为可复用的技能,提高开发效率。
这种渐进式的设计方法让我们能够逐步解决Agent开发中的各种挑战,同时保持代码的简洁性和可维护性。
3.2 Agent的核心原理
无论版本如何演进,Agent的核心工作原理始终是:
code复制感知 -> 认知 -> 执行 -> 反馈 -> 感知...
这个循环不断重复,构成了Agent的基本行为模式。我们的每个版本实现都是对这个核心原理的不同程度的扩展和优化。
4. v0: Shell作为基础工具
4.1 设计理念
v0版本采用最简设计,仅使用Bash命令作为工具集。这种设计有以下几个优点:
- 通用性强:几乎所有操作系统都支持Bash或类似的Shell环境。
- 功能全面:通过Bash可以间接调用系统上的任何工具(如curl、git、python等)。
- 简单可靠:不需要复杂的抽象层,直接利用操作系统提供的功能。
4.2 工具集设计
v0版本定义了以下基础工具:
| 工具功能 | Bash命令示例 |
|---|---|
| 读文件 | cat file.txt, head -n 20 file.py, grep "TODO" -n -r . |
| 写文件 | echo '...' > file, cat << 'EOF' > main.py |
| 搜索/导航 | find . -name "*.py", ls -R, rg "keyword" . |
这些基础工具已经能够完成许多常见的文件操作任务。
4.3 系统架构
v0的系统架构极其简单:
- 核心循环:模型 → 工具调用 → 工具结果 → 模型
- 无额外抽象:不引入Task/Plan/Registry等复杂概念,完全依赖自然语言+Bash+递归实现功能
这种极简设计虽然功能有限,但作为起点非常合适,能够快速验证核心概念。
4.4 代码实现
以下是v0的核心代码实现:
python复制import sys
import os
import traceback
from llm_factory import LLMFactory, LLMChatAdapter
from util.mylog import logger
from utils import run_bash, BASH_TOOLS
# 初始化API客户端
llm = LLMFactory.create(
model_type="openai",
model_name="deepseek-v3.2",
temperature=0.0,
max_tokens=8192
)
client = LLMChatAdapter(llm)
# 系统提示词
SYSTEM = f"""你是一个位于 {os.getcwd()} 的 CLI 代理,系统为 {sys.platform}。使用 bash 命令解决问题。
## 规则:
- 优先使用工具而不是文字描述。先行动,后简要解释。
- 读取文件:cat, grep, find, rg, ls, head, tail
- 写入文件:echo '...' > file, sed -i, 或 cat << 'EOF' > file
- 避免危险操作,如 rm -rf等删除或者清理文件, 或格式化挂载点,或对系统文件进行写操作
## 要求
- 不使用其他工具,仅使用 bash 命令或者 shell 脚本
- 子代理可以通过生成 shell 代码执行
- 如果当前任务超过 bash 的处理范围,则终止不处理"""
def chat(prompt, history=None, max_steps=10):
if history is None:
history = []
# 添加系统提示词
if not any(msg.get("role") == "system" for msg in history):
history.insert(0, {"role": "system", "content": SYSTEM})
history.append({"role": "user", "content": prompt})
step = 0
while step < max_steps:
step += 1
# 调用模型
response = client.chat_with_tools(
prompt=prompt,
messages=history,
tools=BASH_TOOLS
)
# 解析响应
assistant_text = []
tool_calls = []
for block in response.content:
if getattr(block, "type", "") == "text":
assistant_text.append(block.text)
elif getattr(block, "type", "") == "tool_use":
tool_calls.append(block)
# 处理响应
full_text = "\n".join(assistant_text)
if full_text:
history.append({"role": "assistant", "content": full_text})
# 如果没有工具调用,返回结果
if not tool_calls:
return full_text or "(No response)"
# 执行工具
all_outputs = []
for tc in tool_calls:
if tc.name == "bash":
cmd = tc.input.get("command")
if cmd:
output = run_bash(cmd)
all_outputs.append(f"$ {cmd}\n{output}")
# 将结果加入历史
if all_outputs:
combined_output = "\n".join(all_outputs)
history.append({"role": "user", "content": f"执行结果:\n{combined_output}\n\n请继续处理。"})
return "达到最大执行步数限制,停止执行。"
if __name__ == "__main__":
# 交互模式实现...
4.5 执行示例
输入:统计当前目录下的代码行数,输出到控制台
执行流程:
- 模型首先调用
pwd确认当前工作目录 - 然后使用
find命令查找所有代码文件 - 最后使用
wc -l统计行数并输出结果
输出:
code复制v3_subagent.py: 304 行
v4_skills.py: 483 行
v0_bash.py: 152 行
v2_todo.py: 207 行
utils.py: 424 行
v1_basic.py: 191 行
总计:1,761 行代码
5. v1: 模型即代理
5.1 设计理念
v1版本的核心思想是让模型成为真正的决策者,赋予它自主调用工具的能力。与传统的聊天机器人模式不同,Agent系统模式如下:
code复制用户 -> 模型 -> [工具 -> 结果]* -> 回复
^_________|
其中*表示模型可以反复调用工具,直到任务完成为止。这种设计将简单的聊天机器人升级为具有自主决策能力的智能代理。
5.2 工具集扩展
v1版本在v0的基础上扩展了工具集,包含以下核心工具:
| 工具 | 用途 | 示例能力 |
|---|---|---|
bash |
运行命令 | npm install, git status, pytest, ls |
read_file |
读取文件内容 | 查看src/index.ts的具体实现 |
write_file |
创建/覆盖文件 | 创建README.md、生成新模块 |
edit_file |
精确修改片段 | 在函数内插入日志、重构方法 |
这四个工具已经能够覆盖80-90%的代码辅助场景,包括:
- 探索代码库
- 理解代码实现
- 做出精确修改
- 运行和验证变更
5.3 系统架构
v1的系统架构如下图所示:

核心组件包括:
- 模型:作为决策中心,决定何时调用哪些工具
- 工具执行器:负责实际执行工具调用
- 消息历史:保存完整的交互记录,提供上下文
核心循环极其简单:
python复制while True:
response = model(messages, tools)
# 打印文本输出
if no tool_use:
return
results = execute(response.tool_calls)
messages.append(response)
messages.append(results)
5.4 代码实现
以下是v1的核心代码实现:
python复制from pathlib import Path
import sys
import traceback
from llm_factory import LLMFactory, LLMChatAdapter
from util.mylog import logger
from utils import execute_base_tools, BASIC_TOOLS
# 初始化API客户端
llm = LLMFactory.create(
model_type="openai",
model_name="deepseek-v3.2",
temperature=0.0,
max_tokens=8192
)
client = LLMChatAdapter(llm)
WORKDIR = Path.cwd()
SYSTEM = f"""你是一个位于 {WORKDIR} 的编码代理,系统为 {sys.platform}。
## 执行流程
简要思考 -> 使用工具(使用 TOOLS) -> 报告结果。
## 规则
- 优先使用工具而不是文字描述。先行动,不要只是解释。
- 永远不要臆造文件路径。如果不确定,先使用 bash ls/find 确认。
- 做最小的修改。不要过度设计。
- 完成后,总结变更内容。
## 要求:
- 循环尽量简单,不要复杂。
"""
def agent_loop(prompt, history=None, max_steps=10) -> list:
if history is None:
history = []
# 添加系统提示词
if not any(msg.get("role") == "system" for msg in history):
history.insert(0, {"role": "system", "content": SYSTEM})
step = 0
while step < max_steps:
step += 1
response = client.chat_with_tools(
prompt=prompt,
messages=history,
tools=BASIC_TOOLS,
)
# 解析响应
assistant_text = []
tool_calls = []
for block in response.content:
if getattr(block, "type", "") == "text":
assistant_text.append(block.text)
elif getattr(block, "type", "") == "tool_use":
tool_calls.append(block)
full_text = "\n".join(assistant_text)
if not tool_calls:
history.append({"role": "assistant", "content": full_text})
return history
# 执行工具
results = []
for tc in tool_calls:
output = execute_base_tools(tc.name, tc.input)
results.append(f"工具 {tc.name}, 输入: {tc.input}, 返回: {output}")
# 更新历史
history.append({"role": "assistant", "content": full_text})
history.append({"role": "user", "content": "\n".join(results)})
return history
5.5 执行示例
输入:统计当前目录下的代码行数,输出到html中
执行流程:
- 模型首先探索目录结构,查找代码文件
- 使用
wc -l统计各文件行数 - 生成HTML报告并写入文件
- 验证输出结果
输出:生成一个包含代码统计信息的HTML文件,展示各文件行数和总行数。
6. v2: 结构化规划与Todo
6.1 设计理念
v1版本虽然能工作,但在处理复杂任务时容易失去方向。v2版本通过引入Todo管理系统来解决这个问题,主要改进包括:
- 显式规划:要求模型先制定计划再执行
- 进度跟踪:清晰标记已完成、进行中和待办任务
- 焦点管理:一次只专注一个任务,避免思维跳跃
6.2 TodoManager设计
TodoManager是v2的核心组件,它具有以下特点:
-
强制约束:
- 最多20条任务,防止无限扩展
- 每个任务必须有content、status和activeForm字段
- 一次只能有一个任务处于进行中状态
-
状态管理:
- pending:待办
- in_progress:进行中
- completed:已完成
-
可视化反馈:以清晰格式渲染任务列表,方便模型和用户查看进度
核心代码如下:
python复制class TodoManager:
def __init__(self):
self.items = []
def update(self, items: list) -> str:
# 验证输入
validated = []
in_progress_count = 0
for i, item in enumerate(items):
# 提取和验证字段
content = str(item.get("content", "")).strip()
status = str(item.get("status", "pending")).lower()
active_form = str(item.get("activeForm", "")).strip()
# 验证规则
if not content:
raise ValueError(f"Item {i}: content required")
if status not in ("pending", "in_progress", "completed"):
raise ValueError(f"Item {i}: invalid status '{status}'")
if not active_form:
raise ValueError(f"Item {i}: activeForm required")
if status == "in_progress":
in_progress_count += 1
validated.append({
"content": content,
"status": status,
"activeForm": active_form
})
# 强制约束
if len(validated) > 20:
raise ValueError("Max 20 todos allowed")
if in_progress_count > 1:
raise ValueError("Only one task can be in_progress at a time")
self.items = validated
return self.render()
def render(self) -> str:
if not self.items:
return "No todos."
lines = []
for item in self.items:
if item["status"] == "completed":
lines.append(f"[x] {item['content']}")
elif item["status"] == "in_progress":
lines.append(f"[>] {item['content']} <- {item['activeForm']}")
else:
lines.append(f"[ ] {item['content']}")
completed = sum(1 for t in self.items if t["status"] == "completed")
lines.append(f"\n({completed}/{len(self.items)} completed)")
return "\n".join(lines)
6.3 系统提示词优化
v2的系统提示词增加了对Todo使用的引导:
python复制SYSTEM = f"""你是一个位于 {WORKDIR} 的编码代理,系统为 {sys.platform}。
## 执行流程
计划(使用 TodoWrite) -> 使用工具行动(使用 TOOLS) -> 更新任务列表 -> 报告。
## 规则
- 使用 TodoWrite 跟踪多步骤任务
- 开始前将任务标记为 in_progress,完成后标记为 completed
- 优先使用工具而不是文字描述。先行动,不要只是解释。
- 完成后,总结变更内容。"""
此外,还添加了提醒机制,当模型长时间未更新Todo时会收到提示:
python复制INITIAL_REMINDER = "<reminder>使用 TodoWrite 处理多步骤任务。</reminder>"
NAG_REMINDER = "<reminder>超过 10 轮未更新任务列表。请更新任务列表。</reminder>"
6.4 执行示例
输入:统计当前目录下的代码行数和功能,输出到html中
执行流程:
-
模型首先创建Todo列表:
- 分析目录结构,识别代码文件
- 统计代码行数和功能
- 生成HTML报告
- 验证输出结果
-
按顺序执行每个任务,更新状态
-
最终生成包含代码统计和功能描述的HTML报告
7. 经验总结与注意事项
7.1 开发经验
- 渐进式开发:从简单到复杂逐步构建,每个版本只解决一个核心问题。
- 约束即赋能:合理的约束(如TodoManager的限制)实际上提高了系统的可靠性和可用性。
- 模型为中心:让模型做它擅长的事(决策和规划),代码只负责它擅长的事(工具执行和状态管理)。
7.2 常见问题与解决
-
模型不按预期调用工具:
- 优化工具描述,确保清晰明确
- 调整系统提示词,强调工具使用的重要性
- 在对话中适时添加提醒
-
复杂任务失去方向:
- 引入Todo等结构化规划工具
- 限制并行任务数量
- 定期总结进度
-
安全问题:
- 实现命令白名单
- 限制文件系统访问范围
- 对危险操作添加确认步骤
7.3 性能优化建议
- 缓存机制:对昂贵的工具调用结果进行缓存
- 异步执行:并行执行独立的工具调用
- 结果精简:对大型工具输出进行摘要后再交给模型
8. 扩展方向
基于当前实现,还可以进一步扩展:
- v3: 子代理:实现代理的分层结构,允许任务分解和并行执行
- v4: Skills:将常用功能封装为可复用技能,提高开发效率
- 可视化监控:添加任务执行过程的可视化界面
- 长期记忆:引入向量数据库存储历史经验,支持类似案例检索
在实际开发中,我发现最重要的不是追求功能的复杂性,而是保持系统的简洁性和可理解性。每个新增功能都应该解决明确的痛点,而不是为了复杂而复杂。
