解密openclaw底层pi-mono架构系列一:1.从架构到实战

解密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 助手。


目录

  1. 项目概览与架构
  2. 安装与快速上手
  3. [核心模块:pi-ai 统一 LLM API](#核心模块:pi-ai 统一 LLM API)
  4. 完整示例:从简单问答到工具调用
  5. [多提供商无缝切换(含 MiniMax)](#多提供商无缝切换(含 MiniMax))
  6. 高级特性
  7. 开发规范与最佳实践

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
Google 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 是所有贡献者的"宪法",核心规范:

  1. 禁止使用 any 类型 ------ 除非绝对必要
  2. 禁止内联 import ------ 所有 import 必须在文件顶部
  3. 升级依赖而不是降级代码 ------ 遇到 API 变化,升级包,不要绕过
  4. 快捷键必须可配置 ------ 不允许硬编码快捷键
  5. 修改后必须运行 npm run check
  6. 提交前只提交修改的文件 (不使用 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

相关推荐
霖霖总总2 小时前
[Redis小技巧5]Redis Sorted Set 深度解析:从跳表原理到亿级排行榜架构
redis·架构
编码如写诗2 小时前
【k8s】arm架构从零开始在线/离线部署k8s1.34.5+KubeSphere3.4.1
arm开发·架构·kubernetes
天涯明月19933 小时前
微服务架构实战指南:从理论到实践
微服务·云原生·架构
电子科技圈3 小时前
从工具到平台:如何化解跨架构时代的工程开发和管理难题
人工智能·设计模式·架构·编辑器·软件工程·软件构建·设计规范
min1811234563 小时前
PC流程图模板大全 中文定制化满足不同行业需求
架构·pdf·流程图
GISer_Jing3 小时前
Agent技术深度解析:LLM增强智能体架构与优化
前端·人工智能·架构·aigc
min1811234563 小时前
组织结构图导出PDF 高清无水印在线生成
网络·人工智能·架构·pdf·流程图·copilot
银河麒麟操作系统6 小时前
银河麒麟桌面操作系统V10SP1(全X86/ARM架构)【进程资源限制与性能优化实践】技术文章
arm开发·性能优化·架构
架构师沉默6 小时前
如果 Spring 没了,Java 会怎么样?
java·后端·架构