MyAgent - 从零手搓 AI Agent 实践项目
用 TypeScript 从零构建 CLI Agent,不依赖任何 Agent 框架,逐步实现 LLM 对话、工具调用、Agent Loop、会话持久化等核心能力。
Agent 并非黑盒——它是一个循环、一组工具、一段记忆。当你亲手把它们一个个拼起来,Agent 就不再神秘。
不引入任何 Agent 框架,尽量少用第三方依赖,核心逻辑全部手写。
以下 9 个模块按顺序递进,每一步都建立在前一步的基础之上。建议按顺序实践。
这是项目的地基——让 LLM 跑起来,让对话循环起来,让错误可控。
你需要理解和实现:
- LLM 通信:OpenAI 兼容的 Chat Completion API 流式接口(SSE),理解请求格式、响应格式、delta 增量拼接
- Agent Loop:ReAct (Reason + Act) 推理循环——发送消息 → LLM 推理 → 若需工具则执行工具 → 工具结果回填 → 再次推理,直到 LLM 给出最终回答
- 错误重试:指数退避 + 抖动,区分可重试错误(429/5xx/网络超时)与不可重试错误(4xx)
Agent 没有记忆就无法持续工作。这一步让对话可以保存、恢复、跨会话管理。
你需要思考和实现:
- 会话的存储格式与路径约定
- 元数据(ID、标题、时间)与消息数据的分离
- 原子写入防止崩溃导致数据损坏
- 会话恢复机制(将历史消息加载回 Agent Loop)
一个「聪明」的 Agent 需要了解自己运行的环境。这一步让 Agent 从「通用助手」变为「情境感知助手」。
你需要思考和实现:
- 采集哪些环境信息(工作目录、平台、用户、时区……)
- 如何注入——追加到 System Prompt,让 LLM 在推理时能感知当前环境
- 环境信息如何影响 Agent 的决策(路径解析、命令生成、时间判断)
工具系统是 Agent 的双手。没有工具,Agent 只能聊天;有了工具,Agent 才能真正「做事」。
你需要思考和实现:
- 工具接口:每个工具需要
name(LLM 调用名)、description(功能描述)、parameters(JSON Schema 参数定义)、execute(执行函数),这些信息决定了 LLM 能否正确选择和调用工具
- 七个核心工具:read(读取文件)、write(写入文件)、edit(精确字符串替换)、find(Glob 模式查找文件)、grep(正则搜索文件内容)、ls(列出目录)、fetchUrl(获取 URL 内容)
- 工具注册中心:统一管理工具的注册、发现、定义生成
- 输出截断:工具输出可能很长,需要截断防止撑爆上下文窗口——给用户看的显示截断和给模型看的 LLM 截断,策略不同
Bash 工具让 Agent 拥有完整的 Shell 能力,但也带来了最大的安全风险。
你需要思考和实现:
- Bash 命令的执行(spawn 子进程)、超时控制、输出截断
- 安全防护:能力越大,防护越要到位。你需要实现双重拦截机制——黑名单(精确匹配危险命令)+ 正则模式(捕获变体写法),防止
rm -rf /、fork bomb 等破坏性操作
LLM 的上下文窗口是有限的。不了解消耗情况,Agent 可能在不知不觉中用完上下文,导致推理质量骤降或直接报错。
你需要思考和实现:
- 从 LLM 响应的
usage 字段获取真实 Token 消耗
- 结合模型上下文大小计算使用率
- 可视化展示消耗情况(让用户和 Agent 都能做出更好的决策)
上下文窗口就像一个房间,东西越塞越多,终有一天装不下。上下文压缩是延长对话寿命的关键机制。
你需要思考和实现:
- 触发时机:当上下文使用量达到阈值时自动触发
- 压缩策略:保留什么(System Prompt、近期对话、关键工具调用结果),裁剪什么(早期闲聊、已被覆盖的中间推理、超长工具输出)
- 阈值选择:需要给压缩留出缓冲区,避免在压缩完成前就溢出
AGENTS.md 是一种约定——让项目可以向 Agent 传达特定指令,类似 .editorconfig 之于编辑器。
你需要思考和实现:
- 文件发现与自动注入:在 Agent 启动时读取 AGENTS.md 内容,注入到 System Prompt
- 优先级:全局
~/.myagent/agents.md(低优先级)→ 项目 ./AGENTS.md(高优先级),项目级覆盖全局级
- 应用场景:代码规范、构建命令、禁止操作、技术栈说明等
交互模式适合日常使用,非交互模式适合自动化场景。
你需要思考和实现:
- 通过命令行参数传入单次 prompt,执行完毕后自动退出
- 合理的退出码与管道友好的输出格式
- 适用场景:CI/CD 自动化、脚本编排、批量处理
MIT