0%

用本地模型加 pi Durable 搭本地智能体:操作手册(初稿)

用本地模型加 pi Durable 搭本地智能体

说明:本手册为初稿(2026-10-11,pi 起草、Bot 验收)。命令与代码尚未在 Mac 上实测,凡标“待核实”的地方请以 https://earendil.com/posts/pi-durable/ 和 https://pi.dev/packages/pi-agents 官方文档为准。

本地持久化智能体操作手册

Mac mini M4(16GB,本机)+ Mac mini M6(32GB,LLM 服务器)—— 基于 pi + pi-durable 搭建


1. 目标与架构

1.1 项目目标

构建一个本地持久化智能体:在 Mac mini M4(本机)上长期运行,通过雷雳(Thunderbolt)线直连的 Mac mini M6 进行本地大语言模型(LM)推理,具备对话历史、任务状态和跨会话连续性的能力。核心组件是 Pi(Earendil Works 开发的轻量级智能体框架)、pi-durable(用于长生命周期、崩溃恢复的命名代理)以及 pi-agent CLI(零代码启动与管理)。

1.2 系统架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──────────────────────────────────────┐
│ Mac mini M4(16GB,本机) │
│ • 运行 Pi(Node.js 22+) │
│ • 运行 pi-durable 命名代理(持久化) │
│ • pi-agent CLI、文件、微信入口等 │
│ • 通过 pi -c 续跑中断后的工作 │
│ • 不跑大模型(可选 4B-8B Q4 兜底) │
│ • 雷雳 IP(示例):10.0.0.1 │
└──────────────────┬───────────────────┘
│ 雷雳(Thunderbolt)线直连
│ 雷雳网桥组网,静态 IP(示例)
┌──────────────────┴───────────────────┐
│ Mac mini M6(32GB,计划中) │
│ • LLM 服务器:本地模型推理 │
│ • llama.cpp llama-server / Ollama / │
│ LM Studio,提供 OpenAI 兼容接口 │
│ • 服务绑定在雷雳网卡 IP │
│ (示例:10.0.0.2) │
└──────────────────────────────────────┘

两台机器分工

  • M4(本机,16GB):跑 pi、pi-durable 服务/子代理、文件、微信入口等;不跑大模型。16GB 内存可选跑 4B-8B Q4 小模型兜底。
  • M6(计划中,32GB):专用 LLM 服务器,模型通过雷雳网卡 IP 提供 OpenAI 兼容接口。

关键技术栈

  • Pi — 核心框架,提供 CLI、RPC、持久化会话管理
  • pi-durable — 命名代理,支持崩溃恢复、长生命周期任务
  • pi-agent CLI — 零代码启动,/llama、/model、/llama 等命令
  • llama.cpp llama-server — M6 上的本地 GGUF 模型加载器(原文称 router)
  • Ollama / LM Studio — M6 上的本地模型来源(可选)

1.3 模型选型(按 M6 的 32GB 统一内存)

档位 选择 说明
最高档 约 30B 级 MoE Q4(如 Qwen3-30B-A3B 这一类) 32GB 统一内存可承载的上限附近
均衡档 14B 稠密 Q4-Q6 速度与质量兼顾
上下文 32k 需预留 KV cache
系统预留 约 6-8GB 不要全部用于模型
  • M4(16GB)可选跑 4B-8B Q4 小模型兜底。
  • 待核实:具体模型的实际内存占用随量化版本、上下文长度与机型而异,务必在 M6 上实测后再定档。

2. 准备阶段

安装清单(按机器拆分)

机器 要做的事
M4 配置雷雳网桥 IP(示例 10.0.0.1);安装 Pi / pi-durable / pi-agent;配置 models.json 指向 M6;启动 pi -c 持久化代理
M6 配置雷雳网桥 IP(示例 10.0.0.2);安装 llama.cpp(或 Ollama / LM Studio);下载 GGUF 模型;启动模型服务并绑定雷雳 IP

2.1 雷雳网桥组网配置(新增)

  1. 用雷雳(Thunderbolt)线将 M4 与 M6 直连。
  2. 打开 macOS 系统设置 → 网络 → 雷雳网桥(英文界面为 “Thunderbolt Bridge”;中文名称待核实)。
  3. 手动设置 IP(以下为示例,可自选其他私有网段):
    • M4:10.0.0.1,子网掩码 255.255.255.0(/24)
    • M6:10.0.0.2,子网掩码 255.255.255.0(/24)
  4. 验证链路:
    1
    2
    3
    4
    5
    6
    7
    # 在 M4 上 ping M6
    ping 10.0.0.2

    # 带宽测试:M6 上启动服务端
    iperf3 -s
    # M4 上测试
    iperf3 -c 10.0.0.2
    • iperf3 需提前安装(如 brew install iperf3,待核实)。
    • 待核实:雷雳网桥接口是否在插线后自动出现、是否需先配置再插线、不同 macOS 版本中的位置差异。
    • 待核实:直连后的实际带宽(取决于机型的雷雳接口版本)。

2.2 安装 Pi 及相关包(M4 上做)

2.2.1 官方安装脚本(推荐)

1
curl -fsSL https://pi.dev/install.sh | sh

该脚本会将 Pi 安装到当前目录,并自动配置 ~/.pi/agent/ 工作区。

2.2.2 npm 全局安装(离线/无网络环境)

1
npm install -g --ignore-scripts @earendil-works/pi-coding-agent

2.2.3 核心包安装

1
2
3
4
5
# pi-durable 包
pi install npm:@earendil-works/pi-durable

# 若使用 Ollama 作为本地模型提供者
pi install npm:ollama

2.3 在 M6 上部署模型服务(M6 上做)

Pi 默认不包含任何本地模型。要使用本地 GGUF 模型,需要在 M6 上启动 llama-server(原文称 router)并指向 ~/models 目录。

启动模型服务(单模型模式)(在 M6 上,绑定雷雳网卡 IP)

1
2
3
4
5
6
7
8
llama-server \
--models-dir ~/models \
--no-models-autoload \
--jinja \
--host 10.0.0.2 \
--port 8080 \
-ngl 999 \
-c 32768

  • --models-dir ~/models:存放本地 GGUF 文件(如 Qwen3-30B-A3B-Q4_K_M.gguf,30B 级 MoE,支持工具调用)
  • --no-models-autoload:禁止自动加载未显式选择的模型
  • --jinja:启用兼容的聊天模板和工具调用(对 pi-durable 代理有利)
  • --host 10.0.0.2:M6 雷雳网卡 IP(示例)。需绑定雷雳 IP(或 0.0.0.0),M4 才能经雷雳访问;绑定 0.0.0.0 与雷雳 IP 的区别见 7.5
  • --port 8080:默认端口,可自行调整
  • -ngl 999:将几乎所有层移至 GPU(Apple Silicon 上的 Metal GPU / MPS)
  • -c 32768:上下文窗口大小(按 1.3 选型预留 KV cache;可省略以使用模型原生上下文)

验证服务可达性(在 M4 上)

1
2
curl http://10.0.0.2:8080/health
curl http://10.0.0.2:8080/models

可选:Ollama / LM Studio(M6 上)

  • Ollama:可通过环境变量指定监听地址(如 OLLAMA_HOST=10.0.0.2:11434,待核实)。
  • LM Studio:在设置中指定监听地址为雷雳 IP(具体选项待核实)。

2.4 配置 models.json(M4 上做,本地模型注册)

M4 上的 pi 通过 models.json 指向 http://10.0.0.2:<端口>/v1(IP、端口均为示例)。Pi 使用 ~/.pi/agent/models.json 来注册本地模型;若使用 llama.cpp,请确保其模型已通过 /llama 命令加载(待核实:远程服务下的加载方式),然后在 M4 的 models.json 中添加对应条目。

示例 models.json(仅作参考,IP/端口为示例)

1
2
3
4
5
6
7
8
9
10
11
12
{
"providers": {
"llama": {
"baseUrl": "http://10.0.0.2:8080/v1",
"api": "openai-completions",
"apiKey": "ollama",
"models": [
{ "id": "Qwen3-30B-A3B-Q4_K_M" }
]
}
}
}

待核实:实际提供的 models.json 格式可能因版本不同而略有差异,请根据 pi list 输出进行确认。

2.5 启动 pi-durable 命名代理(M4 上做)

1
2
3
4
pi install npm:@earendil-works/pi-durable
# 临时加载(若需要)
pi -e npm:@earendil-works/pi-durable
pi -c # 启动持久化子代理
  • -c 参数开启持久化会话,Pi 会在磁盘上保存状态(包括模型加载、工具调用历史、任务分支)。
  • 代理名称由 pi -c 中的前缀决定(如 agent-001),后续可通过 pi -c agent-001 继续运行。 (待核实:pi -c 原义是继续最近会话,命名续跑以官方文档为准)

3. 第一步:零代码 —— 安装 pi-agent 扩展并运行持久化子代理

3.1 安装 pi-agent CLI 扩展(M4 上)

1
2
# 安装 pi-agent 扩展(真实包名待核实,以文档为准)
pi install npm:@earendil-works/pi-agent

3.2 启动持久化子代理

1
pi -c agent-001   # 待核实
  • 首次启动会自动加载 llama-server(如果未启动)并进入持久化模式。(待核实:llama-server 现在运行在 M6 上,pi 能否自动加载远程服务待核实)
  • 代理名为 agent-001,后续续跑使用 pi -c agent-001。 (待核实:pi -c 原义是继续最近会话,命名续跑以官方文档为准)

3.3 演示中断后续跑

1
2
3
4
5
6
7
# 1. 启动代理
pi -c agent-001 # 待核实

# 2. 模拟中断(例如让代理执行耗时任务并退出)
# 这里我们用一个简单的循环模拟
pi -c agent-001 # 待核实
# 在代理内部执行一段耗时操作后返回 exit

续跑机制(pi -c 续跑)

1
2
# 重新启动同名代理,Pi 会从上次保存的状态继续
pi -c agent-001 # 待核实

  • Pi 会检测 ~/.pi/agent/sessions/ 中的检查点,并恢复到上次保存的位置。
  • 所有工具调用、对话历史、模型状态均保留。

注意:pi -c 续跑仅适用于同一进程的子代理。如果需要跨会话恢复,请使用 pi -c <session-id> 指定具体会话 ID。


3.5 Durable 注意事项(持久化操作安全性)

待核实:以下规则基于通用持久化系统设计,具体实现以 pi-durable 官方文档为准。

  • 只标可安全重跑的工具:只有幂等操作(查询数据库、读取文件、计算统计)才应标记为“可安全重跑”。如果工具执行后对外产生可见副作用(发消息、删文件、写库、创建订单),不要标成可安全重跑,避免中断恢复时重复执行导致数据不一致。
  • 中断后交给模型判断:对于非幂等步骤(发消息、删文件、写库),在断点恢复时应由模型根据上下文判断是否已完成;若不确定,建议先查询恰当前状态再决定继续或回滚,而非盲目重跑。
  • 外部来信去重:若外部渠道(如微信、邮件、API 回调)带有 requestId(或消息唯一 ID),应在进入 durable 代理前去重:先检查已处理列表,已存在则跳过,保证只处理一次;若无 requestId,应由模型根据内容哈希或时间窗口判断是否已处理,避免重复回复。

4. 第二步:pi-durable 包写骨架 Node.js 常驻服务

4.1 项目结构

1
2
3
4
5
6
7
8
9
10
manual/
├── 2026-10-11-local-agent-pi-durable-manual.md # 本手册
├── pi-durable-service/
│ ├── package.json
│ ├── src/
│ │ ├── index.ts # 入口,启动 pi-durable 代理
│ │ ├── router.ts # llama.cpp router 集成
│ │ └── agent.ts # 持久化子代理逻辑
│ └── models.json # 本地模型注册
└── README.md

注:该工程运行在 M4 上;router.ts 对应的 llama-server 实际部署在 M6 上。

4.2 核心实现思路

组件 职责
router.ts 启动 llama-server(或其他本地模型提供者),监听 /models 端点(在 M6 上)
index.ts 启动 pi-durable 命名代理,绑定 /agent 命令(在 M4 上)
agent.ts 持久化子代理:接收用户指令,通过 router 调用模型,记录任务状态
models.json 注册本地 GGUF 模型(通过 llama-server 或 Ollama)

4.3 实现说明(待核实)

待核实:piAgent、registerRouter、durableAgent.handle 等 API 名称及参数疑似编造,需以 @earendil-works/pi-durable 官方文档为准。以下为参考思路:

  1. 通过 pi 框架初始化实例(具体导出名待核实)。
  2. 创建持久化命名代理(替代 piAgent({ name: 'agent-001', persistence: true }) 的实际方法待查文档)。
  3. 配置本地模型路由(替代 registerRouter 方法;通常通过环境变量或配置文件指定模型端点)。
  4. 实现消息处理循环(替代 durableAgent.handle 方法;通常通过事件监听或命令接口获取用户输入)。
  5. 启动服务并保持运行(用于长期任务的典型方式)。

4.4 完整服务入口(待核实)

待核实:pi.start()、agent.registerCommand、ctx.message 等接口疑似编造,需以实际包文档为准。参考思路:

  1. 初始化 pi 实例,注册 /agent 命令处理函数。
  2. 命令处理函数接收 ctx.message 中的用户指令。
  3. 将指令转发到 durable 代理处理,返回结果。
  4. 启动 pi(替代 pi.start() 的实际方法待查文档)。

4.5 启动流程

1
2
3
4
5
6
7
8
9
# 1. 在 M6 上启动模型服务(需先独立运行)
llama-server --models-dir ~/models --no-models-autoload --host 10.0.0.2 --port 8080 -ngl 999 -c 32768

# 2. 在 M4 上启动 pi-durable 代理
pi -c agent-001 # 待核实

# 3. 测试
pi -c agent-001 # 待核实
# 输入任意提示,观察持久化行为

5. 第三步:混合路由 —— 困难步骤退回 magpie 免费/套餐模型链

当某些任务难以由 pi-durable 代理单独完成时,可以通过 混合路由 将请求分流到外部模型链。pi-durable 代理本身可以调用 magpie 等免费/付费模型组合,以获得更强大的能力。

5.1 混合路由策略

任务类型 首选模型 备用模型
代码生成 magpie(免费) gpt-4o(付费)
复杂推理 magpie(付费) 本地 30B 级 MoE Q4(如 Qwen3-30B-A3B,70B 在 M6 32GB 上不可行)
多模态处理 magpie(图像) lm-studio(本地)
长文本摘要 magpie llama-3.2-1b

5.2 实现示例(src/agent.ts 扩展)

1
2
3
4
5
6
7
8
9
10
11
async function routeRequest(request: Request) {
// 根据任务特征选择模型
if (request.type === 'coding') {
return await callMagpie(request) /* 模型名待核实 */;
}
if (request.type === 'analysis') {
return await callMagpie(request) /* 模型名待核实 */;
}
// 默认使用本地 pi-durable 模型
return await callLocalModel(request, model: 'llama-3.2-1b-Q4_K_M');
}

5.3 优缺点

  • 优势:混合路由能结合免费模型的成本优势与本地模型的高效性能
  • 劣势:增加路由逻辑复杂度,需管理模型切换状态
  • 建议:优先使用混合路由处理“边界模糊”任务(如代码+分析复合),保持本地模型作为基准线

6. 最小原型场景

6.1 每晚整理知识库

场景:每天晚上将收到的文档、会议纪要、任务清单同步到本地知识库,供下班后快速检索。

实现(在 src/agent.ts 中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function organizeKnowledge(basePath: string) {
const files = await fs.promises.readdir(basePath);
const organized = [];
for (const file of files) {
if (file.endsWith('.md')) {
const content = await fs.promises.readFile(file, 'utf-8');
organized.push({
id: file,
path: file,
content,
createdAt: new Date(),
});
}
}
// 写入索引文件
await fs.promises.writeFile(
basePath + '-index.json',
JSON.stringify(organized, null, 2)
);
return organized;
}

6.2 处理微信待办

场景:将微信待办列表导入本地,支持按日期/标签过滤、批量创建任务卡片。

实现(在 src/agent.ts 中)

1
2
3
4
5
6
7
8
9
10
11
async function syncWeChatTasks() {
const tasks = await fetchWeChatTasks(); // 模拟 API 调用
const processed = tasks.map(task => ({
id: task.id,
title: task.title,
priority: normalizePriority(task.priority),
dueDate: task.dueDate,
}));
await organizeKnowledge(processed);
return processed;
}

6.3 部署建议

步骤 操作
1 在 M6 上预装 Llama.cpp(llama-server)并下载本地 GGUF 模型
2 在 M4 上配置 ~/.pi/agent/models.json 注册模型,指向 M6 雷雳 IP
3 在 M4 上启动 pi -c agent-001 启动持久化代理 (待核实:pi -c 原义是继续最近会话,命名续跑以官方文档为准)
4 编写 src/agent.ts 实现核心逻辑
5 测试中断-续跑功能,确保状态持久化
6 逐步替换 magpie 等外部模型,实现混合路由

7. 常见问题排查

7.1 Pi 无法启动(M4)

  • 检查 ~/.pi/agent/sessions/ 是否存在
  • 确认 M6 上 llama-server 已启动、雷雳链路通(ping 10.0.0.2)且端口 8080 未被占用
  • 运行 pi list 查看已安装的包

7.2 模型未出现在 /model

  • 确保 M6 上 llama-server 已启动并通过 /llama 命令加载模型
  • 检查 M4 的 models.json 格式及 baseUrl 是否指向 M6 雷雳 IP
  • 尝试 pi -c agent-001 /llama 列出可用模型 (待核实:pi -c 原义是继续最近会话,命名续跑以官方文档为准)

7.3 持久化失败(pi -c 续跑不恢复)

  • 确认 ~/.pi/agent/sessions/ 目录
  • 检查 pi -c 命令是否正确使用
  • 确认代理名称与上次运行一致

7.4 雷雳链路不通(新增)

  • 检查雷雳线两端是否插紧、接口是否正常
  • 在 系统设置 → 网络 中确认出现“雷雳网桥”接口(中文名称待核实)
  • 确认两台机器静态 IP 在同一网段、掩码正确(示例:M4 10.0.0.1、M6 10.0.0.2,均 /24)
  • 用 ping 验证连通性,用 iperf3(见 2.1)验证带宽
  • 确认服务绑定的是雷雳 IP 而不是 127.0.0.1(见 7.5)

7.5 服务绑定 0.0.0.0 与雷雳 IP 的区别(新增)

  • 0.0.0.0:监听所有网卡(Wi-Fi、以太网、雷雳等),M4 可经任一可达地址访问,但暴露面更大
  • 雷雳 IP(如 10.0.0.2,示例):仅监听雷雳网桥接口,更安全,直连场景推荐
  • 127.0.0.1:仅本机回环,M4 无法访问
  • M4 的 models.json 必须指向服务实际绑定的地址,否则连接失败

7.6 macOS 防火墙放行端口(新增)

  • 系统设置 → 网络 → 防火墙,允许 llama-server(及测试用 iperf3)的入站连接(具体选项名称与行为待核实)
  • 命令行方式:sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add <可执行文件路径> 等(待核实)
  • 排错时可临时关闭防火墙,仅限测试(待核实)