一个用于学习与实践的命令行编程智能体。
它不是产品,而是一个把"AI 编程智能体到底怎么运转"讲清楚的玩具。代码刻意保持清晰——每一层职责单一,注释解释"为什么这么设计",你读完能真正理解 agent 内部的工具调用循环、上下文管理、多模型适配。
在终端里和它对话,它能:
- 📖 读文件:
"看看 src/index.ts 在做什么" - ✏️ 改文件:
"给这个函数加上错误处理"→ 自动创建/编辑/精确替换 - 🔍 搜代码:
"找出所有调用了 config 的地方" - ⚙️ 跑命令:
"运行一下测试"→ 执行 shell 命令并汇报结果 - 🧠 多步推理:复杂任务自己拆解、连续调用多个工具直到完成
- Node.js ≥ 20(开发环境用 v24)
- 一个 LLM API Key(GLM 或 DeepSeek)
npm install复制 .env.example 为 .env,填入你的 Key:
# 用 GLM(推荐起步,免费额度多)
MINI_AGENT_MODEL=glm
GLM_API_KEY=你的_key # 去 https://open.bigmodel.cn 申请
GLM_MODEL=glm-4-flash
# 或用 DeepSeek
MINI_AGENT_MODEL=deepseek
DEEPSEEK_API_KEY=你的_key # 去 https://platform.deepseek.com 申请
DEEPSEEK_MODEL=deepseek-chatnpm run dev进入交互式对话:
╭───────────────────────────────────────╮
│ mini-agent · 编程智能体 (v0.1) │
╰───────────────────────────────────────╯
模型:glm | 工作目录:D:\myclaude
输入你的问题开始对话;输入 /exit 退出
> 帮我看看 package.json 里有哪些脚本
⚙ 调用工具 read_file
✓ read_file 完成
package.json 里有以下脚本:dev / build / start / typecheck ...
npx tsx scripts/smoke-test.ts会自动跑三个任务(读文件、写文件、跑命令),全部通过即说明闭环可用。
mini-agent/
├── src/
│ ├── index.ts # CLI 入口:REPL 交互、事件渲染
│ ├── config.ts # 配置层:从环境变量/.env 读取
│ │
│ ├── agent/
│ │ ├── loop.ts # ★ Agent 主循环(项目灵魂)
│ │ └── prompt.ts # 系统提示词
│ │
│ ├── llm/ # LLM 适配层(多模型)
│ │ ├── types.ts # 统一接口定义
│ │ ├── provider.ts # 工厂:按配置创建 provider
│ │ ├── glm.ts # 智谱 GLM 实现
│ │ └── deepseek.ts # DeepSeek 实现
│ │
│ ├── tools/ # 工具层(插件化)
│ │ ├── types.ts # Tool 统一接口
│ │ ├── registry.ts # 注册表:增删查工具
│ │ ├── read_file.ts # 读文件
│ │ ├── write_file.ts # 写文件
│ │ ├── edit_file.ts # 精确替换
│ │ ├── grep.ts # 内容搜索
│ │ └── run_command.ts # 执行命令
│ │
│ └── utils/
│ └── path.ts # 路径安全(防穿越)
│
├── scripts/
│ └── smoke-test.ts # 端到端冒烟测试
│
├── .env.example # 配置模板
└── package.json
agent 区别于"聊天机器人"的关键,在 src/agent/loop.ts:
用户输入
↓
┌─→ 1. 把历史 + 工具定义发给 LLM
│ ↓
│ 2. LLM 返回:文本 和/或 工具调用
│ ↓
│ 3. 有文本 → 打印给用户
│ ↓
│ 4. 没有工具调用?→ 任务完成,退出循环 ★
│ ↓
│ 5. 有工具调用 → 执行工具,结果作为消息塞回历史
│ ↓
└──── 6. 回到第 1 步,LLM 基于新结果继续推理
关键点:什么时候停,是 LLM 自己决定的(它不返回工具调用时即停)。我们只设了 maxTurns 兜底防失控。
- 在
src/tools/新建文件,导出一个Tool对象:export const myTool: Tool = { name: "my_tool", description: "做什么用的", parameters: { type: "object", properties: { ... }, required: [...] }, async execute(args) { return { ok: true, output: "结果" }; }, };
- 在
src/tools/registry.ts的registerAll()里加一行register(myTool)。
不需要动 agent 主循环——LLM 会自动发现新工具并学会用它。
- 在
src/llm/新建xxx.ts,实现LLMProvider接口(参考glm.ts)。 - 在
src/llm/provider.ts的 switch 里加一个case。 - 在
src/config.ts的ModelProvider类型加枚举值。
- 路径隔离:所有文件工具都被限制在工作目录内(
src/utils/path.ts),LLM 给的../../etc/passwd会被拒绝。 - 危险命令拦截:
run_command有黑名单(rm -rf /、fork bomb、mkfs等)和 60 秒超时。 - 输出截断:工具输出超过阈值会截断,防止撑爆 LLM 上下文。
- 错误兜底:工具抛异常不会让 agent 崩溃,而是把错误喂给 LLM 让它自己纠错。
⚠️ 这是一个学习项目,不要在生产环境或敏感目录运行。run_command能执行任意 shell 命令。
- 架构清晰 > 功能堆砌:每一层都能说清"少了它就少了什么能力"。
- 依赖抽象,不依赖具体:agent 只认
LLMProvider接口,不知道底层是 GLM 还是 DeepSeek。 - 不引入重框架:没用 LangChain 之类,避免黑盒。先用 500 行代码理解原理,将来再考虑。
- 注释解释"为什么":不是"这行干嘛",而是"为什么这么设计"。
| 命令 | 作用 |
|---|---|
/exit /quit |
退出 |
/history |
查看当前对话历史长度 |
/help |
帮助 |
MIT