跟我学 AI Agent : 第 2 课 — Agent 开发环境与 pi-mono 框架全景

模块: 模块一: Agent 基础认知 | 难度: ⭐ | 预计时长: 2 小时原书参考: Appendix C + 框架搭建pi-mono API: Agent, AgentTool, getModel, stream, complete, transformContext, subscribe
1. 开场引言
本节以介绍 pi-mono 为主,编程的内容多些,不需要关注细节,从中感受 Agent 如何与 LLM 进行沟通即可,其中的代码相对简单,也很好读。
上一课我们从概念上理解了 Agent 的四级进化。但概念只是概念 —— 我们需要一件趁手的兵器。
AI Agent 框架从 ChatGPT 时刻以后就开始有大师公司在研究,业界比较有名的包括 LangChain/LangGraph(Python)、Google ADK(Python)、CrewAI(Python)等等。
前天在群上有个朋友问关于向量数据库的问题,问 pg 和 milvus 该学哪一个?我的观点是,在 AI 时代,不要再学习某个具体的产品或框架,而是要学习这个产品背后要想到解决的问题。 AI Agent 领域也是如此。
前段时间在研究 OpenClaw 时,我接触到 pi-mono。被它的设计理念所吸引,本次课程将只使用这一套 TypeScript API,贯穿全部 Agent 设计模式。
为什么是它?一句话:它把 Agent 的本质 —— 状态、工具、事件流 —— 用最少的抽象表达出来了。 没有黑魔法,没有过度封装,你能看清每一步发生了什么。
💡 核心问题: pi-mono 的架构是什么样的?核心 API 怎么用?如何用它构建一个完整的 Agent?
2. 核心概念
2.1 pi-mono 全景架构
pi-mono 是一个 TypeScript monorepo,由 Mario Zechner(badlogic)开发,GitHub 38k star。
pi-mono/├── packages/ai/ ← @mariozechner/pi-ai│ 统一 LLM API,15+ provider│ token counting, cost tracking│├── packages/agent/ ← @mariozechner/pi-agent-core│ 有状态 Agent + 工具执行 + 事件流│ (本课程核心)│├── packages/coding-agent/ ← 编程 Agent CLI│ TUI / VS Code 扩展 / Skills│ (L17 高级实战)│├── packages/tui/ ← 终端 UI 组件├── packages/web-ui/ ← Web UI 组件├── packages/mom/ ← Agent 管理工具└── packages/pods/ ← vLLM 部署工具
课程中我们主要用两个包:
-
• pi-ai: 底层 LLM 调用(对应原书的 “哪个模型” 问题) -
• pi-agent-core: Agent 编排(对应原书的 “怎么编排” 问题)
2.2 三层 API 设计
pi-mono 有三个抽象层次,由低到高:
|
|
|
|
|
|
stream() / |
|
|
|
agentLoop() |
|
|
|
Agent 类 |
|
课程建议: 90% 场景用 Agent 类就够了。底层 API 只在 L01 对比时用。
2.3 核心概念: AgentState
每个 Agent 维护一个状态对象:
interfaceAgentState {systemPrompt: string; // 系统提示词model: Model<any>; // LLM 模型实例thinkingLevel: ThinkingLevel; // 推理深度tools: AgentTool<any>[]; // 可用工具列表messages: AgentMessage[]; // 对话历史readonlyisStreaming: boolean;readonlystreamingMessage?: AgentMessage;}
理解 AgentState 就理解了 Agent 的一切 —— 它就是「记忆 + 人设 + 能力」的容器。
2.4 核心概念: 消息流
Agent 内部的消息处理管线:
AgentMessage[] → transformContext() → AgentMessage[] → convertToLlm() → Message[] → LLM (可选: 剪枝/压缩) (必须: 过滤自定义类型)
-
• transformContext(): 你可以在这里剪掉老消息、注入外部知识、压缩上下文 -
• convertToLlm(): 把自定义消息类型转成 LLM 能理解的格式
这个处理流程会涉及到课程后面的 L10-Memory-Management、L11-RAG-and-MCP、L14-Guardrails-and-Safety 等基础设施。此处仅需要理解这个过程即可。
2.5 核心概念: 事件流
每次 prompt() 调用会触发一系列事件:
prompt("你好")├─ agent_start├─ turn_start├─ message_start { userMessage }├─ message_end { userMessage }├─ message_start { assistantMessage }├─ message_update { text_delta: "你" }├─ message_update { text_delta: "好" }├─ message_update { text_delta: "!" }├─ message_end { assistantMessage }├─ turn_end└─ agent_end
如果有工具调用,事件流会更丰富:
prompt("读 config.json")├─ agent_start, turn_start├─ message_start/end { userMessage }├─ message_start → update → end { assistantMessage (含 toolCall) }├─ tool_execution_start { toolName: "read_file" }├─ tool_execution_end { result }├─ message_start/end { toolResultMessage }├─ turn_end│├─ turn_start ← LLM 根据工具结果继续回复├─ message_start → update → end { assistantMessage }├─ turn_end└─ agent_end
这个事件流模型是 pi-mono 最大的教学优势 —— 你可以精确观察 Agent 的每一步决策。
3. 架构图解
pi-mono 分层架构

应用代码层
应用代码层职责:最顶层,封装你的核心业务逻辑。你只需关注如何设计 Agent 的意图,而无需关心底层的实现细节。
核心 Agent 层
Agent 类 -> 由 @mariozechner/pi-agent-core 提供职责:Agent 的“大脑”。它负责维护 Agent 的状态(如 initialState、记忆),编排工具(Tool Orchestration)的调用顺序,并提供系统级的事件流(subscribe),让你能观察到 Agent 的每一个步骤。
循环与推理层
agentLoop() / stream/complete()) -> 由 @mariozechner/pi-agent-core 和 @mariozechner/pi-ai 共同支持职责:实现 Agent 的核心运行逻辑。
-
• agentLoop():实现 Agent 的多步循环,接收 LLM 的决策并执行(如:推理 -> 决策 -> 调用工具 -> 感知反馈 -> 再次推理)。 -
• stream()/complete():负责具体的 LLM 调用,提供同步 (complete) 和流式 (stream) 两种底层模式。
模型接口层
getModel() – 统一接口 -> 由 @mariozechner/pi-ai 提供职责:化繁为简的核心。 它提供了一个单一、统一的 API,让你能用相同的方法调用 15+ 个 不同的模型提供商。无论你想换哪个模型,对上层代码来说都是无感的。
模型服务层
Provider职责:最底层的真实能力来源。这里包括了业界主流的大模型服务,如图中的 OpenAI、Anthropic、Google、DeepSeek 等。pi-mono 负责对它们进行集成和适配。
Agent 工具执行流程

LLM 的推理能力只是 Agent 的“大脑”,而工具才是它的“手脚”。
Agent 真正的核心力量体现在 “工具调用循环”中:模型在接收目标后,会自主决策是否需要使用工具,并能感知外部世界的反馈。
它不是简单地执行工具调用,而是将反馈结果(Tool Result)重新压入对话历史(messages),带着新信息再次进行推理,从而实现多步骤、可反思、可修正的自主行动,直到目标达成。
没有这个循环,AI 就只能是一个“足不出户的书呆子”;有了它,它才是能真正解决复杂问题的“自主助手”。
4. 代码实战
4.1 环境搭建
# 需要 Node.js 18+node -v # v18.x 或更高# 需要 Node.js 18+node -v # v18.x 或更高# 克隆课程代码仓库git clone https://github.com/OmniTexts/learning-ai-agent.gitcd learning-ai-agent/code# 安装依赖npm install
设置 API Key(以 Minimax CN 为例):
export MINIMAX_CN_API_KEY="sk-..."
4.2 最小可运行: Hello Agent
// hello-agent.tsimport { Agent } from"@mariozechner/pi-agent-core";import { getModel } from"@mariozechner/pi-ai";const agent = newAgent({initialState: {systemPrompt: "你是一个友好的助手。用中文回复。",model: getModel("minimax-cn", "MiniMax-M2.7"), },});// 流式输出agent.subscribe((event) => {if ( event.type === "message_update" && event.assistantMessageEvent.type === "text_delta" ) { process.stdout.write(event.assistantMessageEvent.delta); }});await agent.prompt("你好!介绍一下你自己。");
运行:
npx tsx hello-agent.ts
4.3 事件流观察器
完整的 Agent 运行就像一出舞台剧 —— 每个角色都有上台和下台的时刻。下面这段代码让你看到完整的「剧本」:
// event-watcher.tsimport { Agent } from"@mariozechner/pi-agent-core";import { getModel } from"@mariozechner/pi-ai";import { Type } from"@sinclair/typebox";const timeTool = {name: "get_current_time",description: "获取当前时间",parameters: Type.Object({timezone: Type.String({ description: "时区,如 Asia/Shanghai" }), }),execute: async (_id: string, params: { timezone: string }) => {const time = newDate().toLocaleString("zh-CN", {timeZone: params.timezone, });return {content: [{ type: "text"asconst, text: time }], }; },};const agent = newAgent({initialState: {systemPrompt: "你是一个助手。用中文回复。",model: getModel("minimax-cn", "MiniMax-M2.7"),tools: [timeTool], },});// 事件观察器agent.subscribe((event) => {const indent = " ";switch (event.type) {case"agent_start":console.log("🎬 Agent 开始");break;case"turn_start":console.log(`${indent}🔄 Turn 开始`);break;case"message_start":console.log(`${indent}${indent}📝 消息开始 (${event.message.role})`);break;case"message_update":if (event.assistantMessageEvent.type === "text_delta") { process.stdout.write(event.assistantMessageEvent.delta); }break;case"message_end":console.log(`\n${indent}${indent}✅ 消息结束 (${event.message.role})`);break;case"tool_execution_start":console.log(`${indent}${indent}🔧 工具调用: ${event.toolName}(${JSON.stringify(event.args)})` );break;case"tool_execution_end":console.log(`${indent}${indent}📦 工具返回: ${JSON.stringify(event.result?.content?.[0]?.text)}` );break;case"turn_end":console.log(`${indent}🔄 Turn 结束`);break;case"agent_end":console.log("🎬 Agent 结束");break; }});await agent.prompt("现在北京时间几点了?");
运行结果:
🎬 Agent 开始 🔄 Turn 开始 📝 消息开始 (user) ✅ 消息结束 (user) 📝 消息开始 (assistant) 🔧 工具调用: get_current_time({"timezone":"Asia/Shanghai"}) 📦 工具返回: "2026/4/21 13:05:32" ✅ 消息结束 (assistant) 🔄 Turn 结束 🔄 Turn 开始 📝 消息开始 (assistant)现在北京时间是 2026年4月21日 13:05。 ✅ 消息结束 (assistant) 🔄 Turn 结束🎬 Agent 结束
注意:Agent 触发了两个 Turn —— 第一个 Turn 调工具,第二个 Turn 根据工具结果生成自然语言回复。
4.4 完整工具定义: 带进度流和错误处理
// full-tool-demo.tsimport { Agent } from"@mariozechner/pi-agent-core";import { getModel } from"@mariozechner/pi-ai";import { Type } from"@sinclair/typebox";import * as fs from"fs";const readFileTool = {name: "read_file",description: "读取文件内容",parameters: Type.Object({path: Type.String({ description: "文件路径" }),encoding: Type.Optional(Type.String({ description: "编码,默认 utf-8" })), }),// 强制顺序执行(文件操作不适合并行)executionMode: "sequential"asconst,execute: async (_id: string,params: { path: string; encoding?: string },_signal: AbortSignal,onUpdate?: (update: any) =>void ) => {// 流式进度 onUpdate?.({content: [{ type: "text"asconst, text: `正在读取 ${params.path}...` }],details: {}, });if (!fs.existsSync(params.path)) {// 抛错!不要返回错误文本thrownewError(`文件不存在: ${params.path}`); }const content = fs.readFileSync(params.path, params.encoding || "utf-8");return {content: [{ type: "text"asconst, text: content.slice(0, 5000) }],details: {path: params.path,size: content.length,truncated: content.length > 5000, }, }; },};const agent = newAgent({initialState: {systemPrompt: "你是一个文件分析助手。读取文件并总结其内容。",model: getModel("minimax-cn", "MiniMax-M2.7"),tools: [readFileTool], },// 全局并行执行(但 readFileTool 自身是 sequential)toolExecution: "parallel",// 工具调用前检查beforeToolCall: async ({ toolCall, args }) => {if (args.path?.includes("..")) {return { block: true, reason: "不允许路径遍历" }; } },// 工具调用后处理afterToolCall: async ({ toolCall, result, isError }) => {if (!isError) {console.log(`[审计] ${toolCall.name} 调用成功`); } },});agent.subscribe((event) => {if ( event.type === "message_update" && event.assistantMessageEvent.type === "text_delta" ) { process.stdout.write(event.assistantMessageEvent.delta); }});await agent.prompt("读取 package.json 并总结这个项目的信息。");
关键设计点:
-
• executionMode: "sequential"— 文件操作不宜并行 -
• throw new Error()— 工具错误要抛异常,不要返回错误文本 -
• beforeToolCall— 安全检查(阻止路径遍历) -
• afterToolCall— 审计日志 -
• onUpdate— 流式进度反馈
4.5 与三大框架的对照
|
|
|
|
|
pi-mono |
|
|
|
|
|
Agent 类 |
|
|
|
|
|
TypeBox |
|
|
|
|
|
AgentState |
|
|
|
|
|
subscribe() |
|
|
|
|
|
transformContext() |
|
|
|
|
|
内置 parallel |
|
|
|
|
|
thinkingLevel |
|
|
|
|
|
Steering |
|
|
|
|
|
TypeScript |
pi-mono 的独特优势:
-
1. 一套 API 不分裂 — 不需要在三个框架间切换 -
2. 事件流完整可观测 — 每个模式都能看到完整事件链 -
3. TypeScript 类型安全 — 工具参数在编译期检查 -
4. Steering + Follow-up — 运行时干预能力,原书三个框架都没有
5. 要点总结
|
|
|
|
|
|
pi-ai 是底层 | getModel()
stream()/complete() 统一 15+ Provider |
|
|
Agent 是核心 | new Agent()
prompt() 触发执行 |
|
|
AgentTool 定义能力 |
|
|
|
事件流是教学利器 |
|
|
|
transformContext 是关键扩展点 |
|
|
|
beforeToolCall/afterToolCall 是安全网 |
|
|
|
工具错误要 throw |
|
6. 动手练习
练习 1: 基础 (⭐) — 运行 Hello Agent
完成 4.2 的环境搭建,确保 hello-agent.ts 能运行。
练习 2: 进阶 (⭐⭐) — 自定义工具
创建一个 calculate 工具,支持加减乘除:
const calcTool = {name: "calculate",description: "执行数学计算",parameters: Type.Object({expression: Type.String({ description: "数学表达式,如 '2+3*4'" }), }),execute: async (_id, params) => {// 提示: 可以用 Function 构造器安全执行// 或者用 math.js 库const result = /* 你的实现 */;return {content: [{ type: "text"asconst, text: String(result) }], }; },};
测试:让 Agent 回答「如果我有 12 个苹果,分给 3 个人每人 2 个,还剩几个?」
练习 3: 挑战 (⭐⭐⭐) — 多工具协作
给 Agent 添加三个工具:read_file、list_files、write_file。让它能:
-
1. 列出当前目录的文件 -
2. 读取一个文件 -
3. 创建一个摘要文件
观察:Agent 会按什么顺序调用这些工具?事件流是什么样的?
7. 延伸阅读
-
• 🔗 pi-ai README: https://github.com/badlogic/pi-mono/blob/main/packages/ai/README.md -
• 🔗 pi-agent-core README: https://github.com/badlogic/pi-mono/blob/main/packages/agent/README.md -
• 🔗 TypeBox 文档: https://github.com/sinclairzx81/typebox
夜雨聆风