《从0到1动手写 AI Agent:ling》前言 · 先看最终效果

1 阅读5分钟

[《从0到1动手写 AI Agent:ling》全书] (fivwvysqdz.feishu.cn/wiki/UDzKwP…) 本书的目标是参照Claude code做一个真正能干活的 Agent——丢给它一个项目,它能自己看代码、找 Bug、改文件、跑测试,干完还能告诉你它改了什么。

这本书就是带你从零写出这样一个东西。它叫 Ling(灵)。

动手之前,先看看 Ling 的完成品长什么样。下面 8 个场景,都是你读完全书后能自己实现的能力。


场景 1:项目理解——启动即分析

Ling 启动时会自动扫描当前项目,读 package.json、看 git status、数目录结构,然后把这些信息塞进 system prompt。你不用告诉它"这是一个什么项目",它已经知道了。

$ cd ~/my-express-app && ling

  ╭──────────────────────────────────────╮
  │  Ling v0.1.0                         │
  │  项目: my-express-app (Express.js)   │
  │  文件: 47 个  依赖: 12 个            │
  │  分支: feat/user-auth (3 commits)    │
  ╰──────────────────────────────────────╯

> 这个项目在做什么?

  我扫描了项目结构,这是一个 Express.js 后端服务,主要功能:

  1. 用户认证(src/auth/)—— 基于 JWT,刚加了 3 个 commit
  2. RESTful API(src/routes/)—— 用户和文章两组 CRUD
  3. PostgreSQL 数据存储(src/models/)—— 用 Prisma ORM

  目前 feat/user-auth 分支有 3 个未合并的 commit,都是
  在做密码重置功能。src/auth/reset.ts 写了一半,还没有
  对应的测试文件。

这不是魔法。第 4 章会讲上下文工程——怎么从项目里提取关键信息,怎么在有限的 token 预算内塞进最有用的上下文。

场景 2:多模型切换——doubao / claude / openai

国内用豆包(火山引擎),速度快、便宜。需要强推理能力切 Claude。想用 GPT-4o 也行。一个配置文件搞定,运行时也能切。

$ ling --provider doubao
  Using: Doubao-pro-256k (Volcano Engine)

> /model claude

  Switched to: claude-sonnet-4-20250514 (Anthropic)

> /model openai

  Switched to: gpt-4o (OpenAI)

三家 API 的请求格式、tool calling 协议、流式返回结构全都不一样。第 2 章会手写三个 Provider 适配器,统一成一个接口。你会发现 Anthropic 的 tool_use 和 OpenAI 的 function calling 在细节上差异不小——比如参数类型处理、多工具并发返回的方式。

场景 3:自主修 Bug——搜索 + 编辑 + 确认

这是 Agent 最核心的能力循环:理解问题 → 定位代码 → 修改文件 → 验证结果

$ ling

> 用户注册接口返回 500,帮我查一下

  ● grep "register" src/routes/*.ts
    Found 3 matches

  ● read_file src/routes/user.ts:42-68
    Reading lines 42-68...

  找到了。src/routes/user.ts 第 55 行,bcrypt.hash() 的
  第二个参数 saltRounds 写成了字符串 "10",应该是数字 10。

  ● edit_file src/routes/user.ts
    L55: - const hash = await bcrypt.hash(password, "10")
    L55: + const hash = await bcrypt.hash(password, 10)

  ⚠ 修改 src/routes/user.ts?(Y/n) y

  ✔ 已保存

  ● bash npm test -- --grep "register"
    PASS src/routes/__tests__/user.test.ts
    ✓ POST /register returns 201 (45ms)

  测试通过了。问题就是 saltRounds 的类型错误,bcrypt
  期望 number 但拿到了 string,内部抛了异常。

看到了吗?Ling 用了 4 个工具:grep 搜索、read_file 读文件、edit_file 改代码、bash 跑测试。这些工具的实现在第 3 章,Agent Loop 的循环逻辑在第 1 章。它们组合在一起,就是一个能自主解决问题的 Agent。

场景 4:权限拦截——危险操作先问人

Agent 能跑 shell 命令,这事说起来就让人后背发凉。万一它执行 rm -rf /?万一它 git push --force 到 main?

Ling 有一套三层权限模型:allow(直接放行)、ask(先问用户)、deny(直接拒绝)。

> 帮我清理一下临时文件

  ● bash rm -rf /tmp/my-app-cache/
    ⚠ 命令匹配规则 [deny]: rm -rf *
    ✖ 已拦截。该命令被安全策略禁止。

  换个安全的方式。

  ● bash find /tmp/my-app-cache -name "*.tmp" -delete
    ⚠ 需要确认:删除 /tmp/my-app-cache 下的 .tmp 文件?(Y/n) y
    Deleted 23 files.

  清理完毕,删除了 23 个 .tmp 文件。没有动其他文件。

rm -rf 被直接拦截,连问都不问。find -delete 虽然也是删除操作,但目标更精确,所以走的是 ask 流程——让你确认一下再执行。

这套机制在第 5 章实现。你会学到 glob 模式匹配、权限中间件、文件系统边界限制,还有 prompt injection 的基本防御。

场景 5:子 Agent 并行——拆任务并行执行

一个大任务,比如"把这个 Express 项目迁移到 Hono",一个 Agent 独自干会很慢。Ling 可以拆成多个子 Agent 并行干。

> 把项目从 Express 迁移到 Hono

  分析完毕,拆分为 3 个子任务并行执行:

  ┌─────────────────────────────────────────────┐
  │ Agent-1 [routes]    迁移 src/routes/ 下的   │
  │                     5 个路由文件             │
  │ Agent-2 [middleware] 迁移 3 个中间件         │
  │ Agent-3 [config]    替换依赖 + 改入口文件   │
  └─────────────────────────────────────────────┘

  ● Agent-1 [routes]     ✔ 完成 (12s) — 5 files changedAgent-3 [config]     ✔ 完成 (8s)  — 3 files changedAgent-2 [middleware]  ✔ 完成 (15s) — 3 files changed

  所有子任务完成。汇总变更:

  - 替换依赖:expresshono (+@hono/node-server)
  - 迁移路由文件 5 个,中间件 3- 入口 app.ts 重写
  - 修改点:Expressreq.body 改为 c.req.json(),
    res.json() 改为 c.json(),中间件签名全部适配

  ● bash npm test
    PASS (47 tests, 0 failures)

每个子 Agent 有独立的上下文窗口,只看自己负责的文件,互不干扰。第 10 章会实现 AgentSpawner、并行调度器和结果聚合。

场景 6:MCP 接入数据库——外部数据源查询

MCP(Model Context Protocol)是 Agent 工具的"USB 接口"。你不用把所有功能都写死在代码里,通过 MCP 可以接入任何外部工具。

$ cat .ling/mcp.json
{
  "servers": {
    "sqlite": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-sqlite",
               "--db-path", "./data/app.db"]
    }
  }
}

$ ling

> 用户表里有多少条记录?最近注册的 5 个人是谁?

  ● mcp:sqlite query
    SELECT COUNT(*) as total FROM users;
    → total: 1,247

  ● mcp:sqlite query
    SELECT name, email, created_at FROM users
    ORDER BY created_at DESC LIMIT 5;

  用户表共 1,247 条记录。最近注册的 5 人:

  | 姓名     | 邮箱                | 注册时间        |
  |----------|---------------------|-----------------|
  | 张三     | zhang3@example.com  | 2026-04-08 14:22|
  | 李四     | li4@example.com     | 2026-04-08 11:05|
  | 王五     | wang5@test.io       | 2026-04-07 22:18|
  | 赵六     | zhao6@gmail.com     | 2026-04-07 19:33|
  | 陈七     | chen7@corp.cn       | 2026-04-07 16:47|

Ling 自己不懂 SQL,也不知道你的数据库长什么样。它只是通过 MCP 协议连接了一个 SQLite Server,剩下的事——发现有哪些工具可用、传参数、拿结果——全是协议规定好的。第 9 章会从零实现 MCP Client,还会手写一个 MCP Server。

场景 7:CI 管道模式——非交互 + 结构化输出

Agent 不只是给人用的,也可以嵌进 CI/CD 管道里,给机器用。

# .github/workflows/pr-review.yml
- name: AI Code Review
  run: |
    cat diff.patch | ling -p "Review this diff" \
      --provider doubao \
      --format json \
      --schema '{"issues": [{"file":"string","line":"number",
                "severity":"string","message":"string"}]}'
{
  "issues": [
    {
      "file": "src/auth/reset.ts",
      "line": 23,
      "severity": "error",
      "message": "resetToken 没有设置过期时间,安全风险"
    },
    {
      "file": "src/auth/reset.ts",
      "line": 41,
      "severity": "warning",
      "message": "catch 块吞掉了异常,建议至少打个日志"
    }
  ]
}

-p 参数让 Ling 进入 print 模式:读 stdin、调 LLM、输出结果、退出。--format json--schema 可以约束输出格式,方便下游程序解析。这些在第 11 章实现。

场景 8:会话恢复——退出后接着聊

你花了 20 分钟让 Ling 分析一个复杂的代码问题,分析到一半要去开会。回来之后:

$ ling --continue

  ╭───────────────────────────────────────────╮
  │  恢复会话: "debug auth module" (30 min ago)│
  │  消息: 24 条  工具调用: 11 次              │
  ╰───────────────────────────────────────────╯

  [恢复上下文...]

> 刚才分析到哪了?

  上次我们在排查 JWT 刷新逻辑的竞态条件。已经定位到
  src/auth/token.ts 的 refreshToken() 函数——两个并发
  请求可能同时通过旧 token 的校验,导致签发两个新 token。

  我提了一个方案但还没确认:用 Redis SET NX 做一个简单的
  分布式锁。要继续看这个方案吗?

所有对话历史(包括工具调用记录)都持久化在本地。--continue 恢复最近的会话,--resume <id> 恢复指定会话。第 7 章会实现 Session 存储和跨会话记忆。


技术规格

看完场景,来看硬数据。这是 Ling 完成品的技术规格:

项目数值
核心代码量~2000 行 TypeScript
内置工具8 个(read_file / write_file / edit_file / grep / glob / bash / list_files / ask_user)
LLM Provider3 个(火山引擎(豆包)/ Claude / OpenAI)
运行时Node.js 20+
外部依赖< 10 个 npm 包
协议支持MCP (Model Context Protocol) stdio 传输
输出模式交互式 / Print / Stream JSON
权限模型三层(allow / ask / deny),Glob 模式匹配
会话持久化本地 JSON 文件
子 Agent支持并行,独立上下文 + Worktree 隔离

2000 行代码听起来不多。但它涵盖了一个工业级 Agent 的核心架构。每一行你都会亲手写,而且知道为什么这样写。


本书适合谁

目标读者:有 1-3 年经验的程序员,用过 ChatGPT 或 Claude,好奇"这东西到底是怎么做出来的",想自己动手造一个。

你不需要懂机器学习,不需要会训练模型。这本书从第一行代码到最后一行代码都在应用层——调 API,不碰权重。

你需要准备

  • TypeScript 基础——能看懂 async/awaitinterface泛型 就够。不需要精通,遇到的新语法我会解释。
  • Node.js 环境——Node.js 20 以上,npm 或 pnpm 都行。
  • 一个 LLM API Key——火山引擎(豆包)、Claude、OpenAI 任选一个。书里默认用豆包做演示,因为国内访问稳定,注册就送额度。三个都有最好,第 2 章会全部用到。
  • 操作系统——macOS 或 Linux。Windows 用户请使用 WSL 2(Windows Subsystem for Linux),因为书中的 grep、bash 等工具直接调用系统命令,原生 Windows 不兼容。
  • 一个终端和编辑器——VS Code 或任何你顺手的都行。

不适合的读者

  • 想学 LangChain / LlamaIndex 这类框架的——这本书不用任何 Agent 框架,全部手写。
  • 想了解大模型原理和训练的——这本书只管调用,不管模型内部。
  • 已经读过 Claude Code 源码并且理解其架构的——你可能会觉得内容太基础。

全书路线图

11 章,从一个 50 行的玩具到一个能跑在生产环境的 Agent。每一章都在前一章的代码上递增,不跳步。

暂时无法在飞书文档外展示此内容

第 1-3 章是地基。你会拿到一个能对话、能用工具、能切模型的 Agent。这三章完成后,Ling 就已经能干不少活了。

第 4-6 章是打磨。上下文工程让 Agent 更聪明,权限系统让它更安全,流式交互让它用起来不像在等一个 HTTP 请求。

第 7-9 章是进阶。会话持久化、Hook 生命周期扩展、MCP 工具插件——这些是把 Ling 从"能用"推向"好用"的关键。

第 10-11 章是收官。多 Agent 并行协作,再把 Ling 从一个交互式 CLI 变成一个可以嵌入管道、跑在 CI 里的生产力工具。

每一章结尾都有一个"对照 Claude Code"环节。Claude Code 是目前最成熟的 AI 编程助手之一,它的开源版本是我们最好的参照物。你写的每个模块,都能在 Claude Code 里找到对应的工业级实现。看看它怎么做的,想想为什么那样做,比单纯跟着教程抄代码有用。


行,废话到此为止。翻到第 1 章,打开编辑器,新建一个 ling.ts——50 行代码,你的第一个 Agent。

《从0到1动手写 AI Agent:ling》全书