Skip to content

从零手写一个 Agent

这是全书最后一关。我们把前面每个模块的工程化 Demo 串成一个完整的最小 Agent。它零依赖、可运行,是 @pi/agent-core + @pi/ai 的浓缩复现。

它包含哪些"pi 概念"

概念在 pi 里的位置在工程化 Demo 里
消息模型@pi/ai typessrc/ai/types.ts
事件流@pi/ai EventStreamsrc/ai/event-stream.ts
StreamFunction@pi/aisrc/ai/stream-function.ts
工具契约@pi/agent-core AgentToolsrc/agent/types.ts
Agent 循环agent-loop.tssrc/agent/agent-loop.ts
生命周期事件types.ts AgentEventsrc/agent/types.ts
steer/followUpagent.tssrc/agent/agent.ts

工程化结构(完整项目)

text
pi-principles/
├── package.json            # scripts:cli / chat / agent / app / repl / ai ...
├── cli.ts                  # CLI 入口(pi-demo)
├── src/                    # 模块源码(镜像 pi 的包)
│   ├── config.ts       (.env 配置)
│   ├── ai/             (消息/事件流/StreamFn/Faux + api/openai-responses)
│   ├── agent/          (AgentTool/循环/MiniAgent/decider)
│   ├── tools/          (calculator/bash/read)
│   ├── protocol/       (定帧/协议消息)
│   ├── transport/      (服务端/客户端/管道)
│   ├── tui/            (组件/布局/差分渲染/session-view)
│   └── cli/            (args/main/chat/agent/app/repl)
└── play/              模块化离线入口
    ├── ai.ts / mini-agent.ts / protocol.ts / transport.ts / tui.ts

综合入口:play/mini-agent.ts

它把第 1、2 部分的模块缝合起来:

  • @pi/aiStreamFn 形状驱动循环。
  • agent/agent-loop.ts 的 ReAct 循环做"思考→行动→观察"。
  • 挂两个工具(calculator / echo)。
  • 演示 steer(中途插话)与 followUp(完成后追加)。
ts
import { MiniAgent } from "../src/agent/agent.ts";
import { createDeciderStreamFn } from "../src/agent/decider.ts";   // 离线假模型
import type { AgentTool } from "../src/agent/types.ts";

const tools: AgentTool[] = [
	{ name: "calculator", description: "两数相加", execute: ({ a, b }) => `= ${Number(a) + Number(b)}` },
	{ name: "echo", description: "原样返回", execute: ({ text }) => text ?? "" },
];

const agent = new MiniAgent({ streamFn: createDeciderStreamFn(), systemPrompt: "你是一个只会做加法的教学 Agent", tools });
agent.subscribe((e) => { /* 观察事件 */ });

await agent.prompt("计算 2 + 3");
agent.steer({ role: "user", content: "改成计算 2 + 5", timestamp: Date.now() });
agent.followUp({ role: "user", content: "最后把答案乘以 10", timestamp: Date.now() });
await agent.prompt("继续");

想用真实模型?把 createDeciderStreamFn() 换成 createOpenAIStreamFn(loadConfig())(需 .env),或直接 bun cli.ts agent "计算 2 + 3"

运行

bash
cd pi-principles
bun play/mini-agent.ts          # 或 npm run mini-agent

输出(注意工具被调用、steer 中途采纳、followUp 最后处理):

== agent_start ==
[assistant] (调用 calculator:{"a":2,"b":3})
  → 工具: calculator
[toolResult] = 5
[assistant] 答案:= 5
== agent_end ==
== agent_start ==
[user] 改成计算 2 + 5          ← steer 中途注入,再次触发工具
  → 工具: calculator
[toolResult] = 7
[assistant] 答案:= 7
[user] 最后把答案乘以 10        ← followUp 最后处理
[assistant] 答案:最后把答案乘以 10
== agent_end ==

扩展练习(自己做)

  1. 加一个工具:在 tools 数组里加一个 reverse 工具(反转字符串),看模型能否调用。
  2. 改判定逻辑:修改 createDeciderStreamFn,让它识别"乘法"而调用 calculator
  3. 联动 transport:把 MiniAgent 的 transcript 用 protocol/framing.ts 编码后经 transport 推送,让它"跑在远程"。

这本书至此,你已经掌握了

  1. @pi/ai:统一消息模型、事件流、Model/Provider、StreamFunction 契约。
  2. @pi/agent-core:Agent 循环、工具、生命周期事件、steer/followUp 队列。
  3. @pi/protocol:定帧、CBOR 编解码、消息 Schema。
  4. @pi/server + @pi/client:远程会话、握手、快照与增量。
  5. @pi/tui:组件模型、布局、差分渲染。
  6. @pi/coding-agent:会话持久化、内置工具、上下文压缩、扩展系统、SDK。

从"一段字节"到"一个能干活、能存盘、能远程连接、有界面的 Agent",整条流水线你已经走过一遍。下一步回顾全书层级关系。总结与层级关系