解密openclaw底层pi-mono架构系列一:1.从架构到实战
不谈玄学,只讲落地。
我是一名深耕算法工程化一线的实践者,擅长将 新技术、关键技术、AI/ML 技术从论文和 demo 转化为可规模化部署的生产系统。在这里,你看不到堆砌公式的理论空谈,只有真实项目中踩过的坑、趟过的路,每一篇文章都源自实战经验的提炼。我相信技术的价值在于解决真实问题,而不是制造焦虑。如果你也厌倦了"收藏即学会",渴望掌握让算法真正跑起来的硬核能力,那么这里就是你的技术补给站。
用 Pi Mono 构建统一 AI Agent ------ 从架构到实战
Pi Mono 是最近火爆的Openclaw的底层框架模块
Pi Mono 是 @mariozechner 开源的 AI Agent 工具包以 Monorepo 形式将统一 LLM API、智能体运行时、终端 UI、Web UI 等
七个包打包在一起,让你用一套代码调用 20+ 个大模型提供商,构建真正可用的 AI 助手。
目录
- 项目概览与架构
- 安装与快速上手
- [核心模块:pi-ai 统一 LLM API](#核心模块:pi-ai 统一 LLM API)
- 完整示例:从简单问答到工具调用
- [多提供商无缝切换(含 MiniMax)](#多提供商无缝切换(含 MiniMax))
- 高级特性
- 开发规范与最佳实践
1. 项目概览与架构
Pi Mono 采用分层 Monorepo 架构,7 个包按依赖关系清晰分层:
| 包名 | 功能 | 类比 |
|---|---|---|
pi-ai |
统一调用 20+ LLM 提供商 | 万能遥控器,控制所有品牌电视 |
pi-agent-core |
智能体运行时,状态机 + 工具调用 | 机器人的"大脑" |
pi-tui |
终端 UI 组件库 | 在终端里绘制漂亮界面的画笔 |
pi-coding-agent |
交互式编程助手(类似 Claude Code) | 终端 AI 编程搭档 |
pi-web-ui |
Web 聊天界面组件 | 浏览器里的 AI 对话框 |
pi-mom |
Slack 机器人集成 | 住在 Slack 里的 AI 助手 |
pi-pods |
GPU 服务器/Pod 管理 | 云端 AI 模型的"部署管家" |
架构图

整体分为四层,自上而下:应用层(4 个业务包)→ 核心运行时层(pi-agent-core)→ 统一 LLM API 层(pi-ai)→ LLM 提供商层(20+)。pi-tui 作为独立的终端 UI 框架,由 pi-coding-agent 按需引用。
包依赖关系
pi-ai(基础,无内部依赖)
↓
pi-agent-core(依赖 pi-ai)
↓
pi-coding-agent ── 依赖 pi-agent-core + pi-tui + pi-ai
pi-mom ── 依赖 pi-agent-core + pi-ai
pi-web-ui ── 依赖 pi-agent-core + pi-ai
pi-pods ── 独立(无内部依赖)
pi-tui ── 独立(无内部依赖)
2. 安装与快速上手
安装信息来源:github.com/badlogic/pi-mono
2.1 系统要求
- Node.js >= 20
- npm >= 9(Workspaces 支持)
2.2 克隆并安装
bash
# 克隆仓库
git clone https://github.com/badlogic/pi-mono
cd pi-mono
# 安装所有包的依赖(Monorepo 统一安装)
npm install
# 构建所有包(必须先构建再做其他操作)
# 构建顺序:tui → ai → agent-core → coding-agent → mom → web-ui → pods
npm run build
2.3 开发常用命令
bash
# 代码质量检查(Lint + 格式化 + 类型检查)
# 注意:必须先运行 npm run build,web-ui 的 tsc 依赖其他包编译出的 .d.ts
npm run check
# 运行测试(未配置 API Key 时会跳过 LLM 相关测试)
./test.sh
# 从源码直接运行 pi(必须在仓库根目录执行)
./pi-test.sh
# 开发模式:所有包并行监听文件变化,自动重编译
npm run dev
2.4 单包测试
bash
# 运行指定包的测试
cd packages/ai
npx vitest --run
2.5 验证安装
bash
# 检查 Node 版本(必须 >= 20)
node -v
# 查看 coding-agent 帮助
node packages/coding-agent/dist/cli/index.js --help
# 查看所有支持的模型列表
node packages/ai/dist/cli/index.js list
# 筛选指定提供商
node packages/ai/dist/cli/index.js list --provider anthropic
3. 核心模块:pi-ai 统一 LLM API
3.1 为什么需要 pi-ai?
直接调用各厂商 SDK 的最大痛点是格式不统一:
typescript
// Anthropic 的格式
const res = await client.messages.create({ ... });
const text = res.content[0].text; // ← Anthropic 专属格式
// OpenAI 的格式
const res2 = await openai.chat.completions.create({ ... });
const text2 = res2.choices[0].message.content; // ← 完全不同!
切换提供商需要改大量代码。pi-ai 的解决方案 是提供统一的 completeSimple / streamSimple 接口,配合 getModel 选择提供商:
typescript
import { completeSimple, getModel } from "@mariozechner/pi-ai";
// 选择 MiniMax 国内版模型
const model = getModel("minimax-cn", "MiniMax-M2.5");
// 无论哪个提供商,Context 格式完全一样
const context = {
systemPrompt: "你是一个友好的 AI 助手。",
messages: [{ role: "user" as const, content: "Hello", timestamp: Date.now() }],
};
const reply = await completeSimple(model, context, {
apiKey: process.env.MINIMAX_CN_API_KEY!,
});
3.2 内部架构与数据流

请求从 stream() 统一入口进入,经 model-resolver 根据模型名路由到对应 Provider 实现,翻译成各厂商格式后发出 HTTP 请求,响应再解析回统一的 StreamResult(包含 messages、tokens、cost)返回给调用方。
packages/ai/src/ 目录结构:
packages/ai/src/
├── types.ts # 核心类型定义
├── stream.ts # 统一入口:路由到具体提供商
├── tokens.ts # Token 计数和成本计算
├── models.generated.ts # 自动生成的模型列表(3000+ 行)
├── model-resolver.ts # 模型名称 → 提供商映射
└── providers/ # 各提供商实现(20+)
├── anthropic.ts
├── openai-completions.ts
├── google.ts
├── aws-bedrock.ts
└── ...
3.3 核心类型
Context(对话上下文):
typescript
type Context = {
systemPrompt?: string; // 系统提示词
messages: Message[]; // 对话历史(UserMessage | AssistantMessage | ToolResultMessage)
tools?: ToolDef[]; // 可用工具列表
};
// 用户消息
type UserMessage = { role: "user"; content: string; timestamp: number };
// AI 回复(content 包含文字块、工具调用块)
type AssistantMessage = {
role: "assistant";
content: (TextContent | ToolCall)[];
usage: { input: number; output: number; cost: { total: number } };
};
两个核心函数:
typescript
// 等待完整回复(非流式)
completeSimple(model, context, { apiKey }): Promise<AssistantMessage>
// 流式输出,返回异步事件迭代器
streamSimple(model, context, { apiKey }): AsyncIterable<StreamEvent>
StreamEvent 事件类型:
typescript
type StreamEvent =
| { type: "start" }
| { type: "text_delta"; delta: string } // 新增文字片段
| { type: "text_end" }
| { type: "thinking_delta"; delta: string } // 推理模型思考过程
| { type: "done"; message: AssistantMessage; reason: string }
| { type: "error"; error: { errorMessage: string } };
4. 完整示例:从简单问答到工具调用
运行方法

示例 1:简单问答
对应
examples/01-pi-ai/01-simple-chat.ts
typescript
import { completeSimple, getModel } from "@mariozechner/pi-ai";
const model = getModel("minimax-cn", "MiniMax-M2.5");
const apiKey = process.env.MINIMAX_CN_API_KEY!;
const context = {
systemPrompt: "你是一个友好的 AI 助手,回答简洁清晰。",
messages: [
{ role: "user" as const, content: "用一句话解释什么是递归?", timestamp: Date.now() },
],
};
const reply = await completeSimple(model, context, { apiKey });
const text = reply.content
.filter((c) => c.type === "text")
.map((c) => (c as { type: "text"; text: string }).text)
.join("");
console.log("AI 回复:", text);
console.log(`输入 tokens: ${reply.usage.input}`);
console.log(`输出 tokens: ${reply.usage.output}`);
console.log(`费用: $${reply.usage.cost.total.toFixed(6)}`);
示例 2:流式输出(打字机效果)
对应
examples/01-pi-ai/02-streaming.ts
typescript
import { streamSimple, getModel } from "@mariozechner/pi-ai";
const model = getModel("minimax-cn", "MiniMax-M2.5");
const apiKey = process.env.MINIMAX_CN_API_KEY!;
const context = {
systemPrompt: "你是一个会写故事的 AI,回答要有创意。",
messages: [
{ role: "user" as const, content: "写一个关于程序员和 AI 的三句话短故事。", timestamp: Date.now() },
],
};
console.log("AI 正在输出(流式):\n");
const eventStream = streamSimple(model, context, { apiKey });
for await (const event of eventStream) {
switch (event.type) {
case "text_delta":
process.stdout.write(event.delta); // 逐字打印,不换行
break;
case "thinking_delta":
process.stdout.write(`[思考] ${event.delta}`); // M1/M2 推理过程
break;
case "done":
console.log("\n\n--- 完成 ---");
console.log(`停止原因: ${event.reason}`);
console.log(`tokens: ${event.message.usage.input} in / ${event.message.usage.output} out`);
break;
case "error":
console.error("\nAPI 错误:", event.error.errorMessage);
break;
}
}
示例 3:多轮对话
对应
examples/01-pi-ai/04-multi-turn.ts
typescript
import * as readline from "node:readline";
import { streamSimple, getModel } from "@mariozechner/pi-ai";
import type { Context, UserMessage, AssistantMessage } from "@mariozechner/pi-ai";
const model = getModel("minimax-cn", "MiniMax-M2.5");
const apiKey = process.env.MINIMAX_CN_API_KEY!;
const context: Context = {
systemPrompt: "你是一个友好的 AI 助手。记住对话历史,保持连贯回答。",
messages: [],
};
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const ask = (p: string) => new Promise<string>((resolve) => rl.question(p, resolve));
console.log('多轮对话(输入 exit 退出)\n');
while (true) {
const userInput = await ask("你: ");
if (userInput.toLowerCase() === "exit") { rl.close(); break; }
const userMsg: UserMessage = { role: "user", content: userInput, timestamp: Date.now() };
context.messages.push(userMsg);
process.stdout.write("AI: ");
for await (const event of streamSimple(model, context, { apiKey })) {
if (event.type === "text_delta") process.stdout.write(event.delta);
if (event.type === "done") {
process.stdout.write("\n");
context.messages.push(event.message as AssistantMessage); // 保存到历史
}
if (event.type === "error") console.error("\n错误:", event.error.errorMessage);
}
console.log(` [历史: ${context.messages.length} 条]\n`);
}
示例 4:工具调用(Tool Calling)
工具调用是构建 AI Agent 的关键能力。整个过程分两轮完成:

对应
examples/01-pi-ai/03-tool-calling.ts
typescript
import { completeSimple, getModel, Type } from "@mariozechner/pi-ai";
import type { Context, ToolResultMessage, AssistantMessage, ToolCall } from "@mariozechner/pi-ai";
const model = getModel("minimax-cn", "MiniMax-M2.5");
const apiKey = process.env.MINIMAX_CN_API_KEY!;
// 1. 用 TypeBox 定义工具 Schema
const tools = [
{
name: "get_weather",
description: "获取指定城市的当前天气",
parameters: Type.Object({
city: Type.String({ description: "城市名称,如 '北京'" }),
}),
},
{
name: "calculate",
description: "执行数学计算",
parameters: Type.Object({
expression: Type.String({ description: "数学表达式,如 '2 + 3 * 4'" }),
}),
},
];
// 2. 工具实现
function executeToolCall(tc: ToolCall): string {
if (tc.name === "get_weather") {
const { city } = tc.arguments as { city: string };
return JSON.stringify({ city, temperature: 22, unit: "celsius", condition: "晴天" });
}
if (tc.name === "calculate") {
const { expression } = tc.arguments as { expression: string };
const result = Function(`"use strict"; return (${expression})`)();
return JSON.stringify({ result, expression });
}
return JSON.stringify({ error: "未知工具" });
}
// 3. 构建上下文
const context: Context = {
systemPrompt: "你是一个智能助手,可以查询天气和做数学计算。",
messages: [
{ role: "user", content: "北京今天天气怎样?顺便算一下 (15 + 27) * 3", timestamp: Date.now() },
],
tools,
};
// 4. 工具调用循环(自动处理多轮)
let turn = 1;
while (true) {
console.log(`\n--- 第 ${turn++} 轮 LLM 调用 ---`);
const reply: AssistantMessage = await completeSimple(model, context, { apiKey });
context.messages.push(reply);
const toolCalls = reply.content.filter((c): c is ToolCall => c.type === "toolCall");
if (toolCalls.length === 0) {
const text = reply.content.filter((c) => c.type === "text").map((c: any) => c.text).join("");
console.log("\nAI 最终回答:", text);
break;
}
for (const tc of toolCalls) {
console.log(` 调用: ${tc.name}`, tc.arguments);
const result = executeToolCall(tc);
console.log(` 结果: ${result}`);
const toolResult: ToolResultMessage = {
role: "toolResult",
toolCallId: tc.id,
toolName: tc.name,
content: [{ type: "text", text: result }],
isError: false,
timestamp: Date.now(),
};
context.messages.push(toolResult);
}
}
5. 多提供商无缝切换
只需换模型名,代码不变:
typescript
const question = "用一句话解释递归";
const baseMessages = [{
role: "user" as const,
content: [{ type: "text" as const, text: question }]
}];
// Anthropic Claude
const r1 = await stream({ model: "claude-haiku-4-5-20251001", messages: baseMessages, apiKey: process.env.ANTHROPIC_API_KEY!, maxTokens: 100 });
// OpenAI GPT
const r2 = await stream({ model: "gpt-4o-mini", messages: baseMessages, apiKey: process.env.OPENAI_API_KEY!, maxTokens: 100 });
// Google Gemini
const r3 = await stream({ model: "gemini-2.0-flash", messages: baseMessages, apiKey: process.env.GOOGLE_API_KEY!, maxTokens: 100 });
console.log("Claude:", getLastText(r1.messages));
console.log("GPT-4o:", getLastText(r2.messages));
console.log("Gemini:", getLastText(r3.messages));
支持的提供商
| 提供商 | 示例模型 | 环境变量 |
|---|---|---|
| Anthropic | claude-opus-4-6, claude-haiku-4-5-20251001 |
ANTHROPIC_API_KEY |
| OpenAI | gpt-4o, gpt-4o-mini |
OPENAI_API_KEY |
gemini-2.0-flash |
GOOGLE_API_KEY |
|
| AWS Bedrock | anthropic.claude-3-5-sonnet |
AWS 配置 |
| Mistral | mistral-large-latest |
MISTRAL_API_KEY |
| Groq | llama-3.3-70b-versatile |
GROQ_API_KEY |
| OpenRouter | openrouter/... |
OPENROUTER_API_KEY |
| MiniMax | MiniMax-Text-01, MiniMax-M1 |
MINIMAX_API_KEY |
| 自定义兼容 | 任意 OpenAI 兼容接口 | 自定义 baseURL |
MiniMax 配置
MiniMax 提供 OpenAI 兼容接口,在 pi-mono 中有两种接入方式:
方式一:pi-ai 直接调用(代码中使用)
typescript
import { stream } from "@mariozechner/pi-ai";
const result = await stream({
model: "MiniMax-Text-01",
messages: [{ role: "user", content: [{ type: "text", text: "你好" }] }],
apiKey: process.env.MINIMAX_API_KEY!,
// MiniMax OpenAI 兼容端点
baseUrl: "https://api.minimax.chat/v1",
maxTokens: 1000,
});
切换到推理模型 MiniMax-M1:
typescript
const result = await stream({
model: "MiniMax-M1", // 具备深度推理能力
messages: [...],
apiKey: process.env.MINIMAX_API_KEY!,
baseUrl: "https://api.minimax.chat/v1",
thinkingTokenBudget: 5000, // 开启推理模式
maxTokens: 8000,
});
方式二:models.json 注册(供 pi-coding-agent 使用)
编辑 ~/.pi/agent/models.json,将 MiniMax 注册为自定义提供商:
json
{
"providers": {
"minimax": {
"baseUrl": "https://api.minimax.chat/v1",
"apiKey": "MINIMAX_API_KEY",
"api": "openai-completions",
"models": [
{
"id": "MiniMax-Text-01",
"name": "MiniMax Text 01",
"input": ["text"],
"contextWindow": 1000000,
"maxTokens": 4096,
"cost": { "input": 0.7, "output": 2.1, "cacheRead": 0, "cacheWrite": 0 }
},
{
"id": "MiniMax-M1",
"name": "MiniMax M1 (Reasoning)",
"reasoning": true,
"input": ["text"],
"contextWindow": 1000000,
"maxTokens": 40960,
"cost": { "input": 1.1, "output": 5.5, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
配置完成后,在 pi-coding-agent 中直接使用 /model MiniMax-Text-01 切换模型。
环境变量 :配置中
"apiKey": "MINIMAX_API_KEY"是环境变量名,pi 会自动从环境中读取其值。也可写成字面量字符串,但不推荐直接硬编码密钥。
6. 高级特性
6.1 Token 计数(不调用 LLM)
typescript
import { countTokens } from "@mariozechner/pi-ai";
const tokenCount = await countTokens({
model: "claude-opus-4-6",
messages: [...],
apiKey: "...",
});
console.log(`预计 Token 数: ${tokenCount}`);
6.2 思考模式(Extended Thinking)
让 Claude 在回复前进行深度推理:
typescript
const result = await stream({
model: "claude-opus-4-6",
messages: [...],
apiKey: process.env.ANTHROPIC_API_KEY!,
thinkingTokenBudget: 5000, // 允许最多 5000 个思考 Token
maxTokens: 8000,
});
console.log(`思考 Token: ${result.thinkingTokens}`);
6.3 取消请求
typescript
const controller = new AbortController();
// 3 秒后取消
setTimeout(() => controller.abort(), 3000);
try {
const result = await stream({
model: "claude-opus-4-6",
messages: [...],
apiKey: "...",
signal: controller.signal,
});
} catch (err) {
if (err.name === "AbortError") {
console.log("请求已取消");
}
}
7. 开发规范与最佳实践
Pi Mono 的 AGENTS.md 是所有贡献者的"宪法",核心规范:
- 禁止使用
any类型 ------ 除非绝对必要 - 禁止内联 import ------ 所有 import 必须在文件顶部
- 升级依赖而不是降级代码 ------ 遇到 API 变化,升级包,不要绕过
- 快捷键必须可配置 ------ 不允许硬编码快捷键
- 修改后必须运行
npm run check - 提交前只提交修改的文件 (不使用
git add .)
技术栈
语言: TypeScript 5.9.2
运行时: Node.js 20+
模块系统: ES Modules
包管理器: npm Workspaces
构建工具: tsgo
代码检查: Biome 2.3.5(格式化 + Lint)
测试框架: Vitest 3.2.4
Git Hooks: Husky 9.1.7
总结
Pi Mono 的核心价值在于抽象的彻底性:
- pi-ai 消除了 LLM 提供商的 API 差异,一套代码走天下
- pi-agent-core 将工具调用的复杂状态机封装好,专注业务逻辑
- Monorepo 设计让七个包保持一致的代码规范和构建流程
无论你是想用 Claude 写代码助手、用 Slack 构建工作流机器人,还是部署 GPU 上的模型服务,Pi Mono 都提供了生产就绪的基础设施。
🔗 项目地址:github.com/badlogic/pi-mono
📄 MIT 协议开源 | ⭐ 21.1k Stars