智能体工程:Pi SDK

上一篇文章中,我用 Pi 的 CLI 加上一个扩展文件和一个系统提示词文件构建了一个新闻阅读器。在 .pi/ 目录里放两个文件,用 pi -p 运行。相比 Claude Code 版本,它已经显得很精简,但它仍然依赖 Pi 的运行时来发现和加载扩展。

Pi 还有一个编程式 SDK,可以让你把智能体嵌入到自己的脚本中,就像 Agent SDK 让我把 Claude Code 版本折叠成单个 Python 文件一样。

1、内联工具、内联提示词

工具代码与 CLI 版本相同:web_fetch 用 turndown 抓取页面,save_news_item 把 JSON 写入 data/。区别在于,它们不再放在 .pi/extensions/news-tools.ts 里,而是用 defineTool() 内联定义。下面是一个例子:

import {
  createAgentSession, DefaultResourceLoader, defineTool,
  getAgentDir, SessionManager,
} from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
import TurndownService from "turndown";

const webFetchTool = defineTool({
  name: "web_fetch",
  label: "Fetch URL",
  description: `Fetch a web page and return content as markdown. Allowed domains: ${ALLOWED_DOMAINS.join(", ")}`,
  parameters: Type.Object({
    url: Type.String({ description: "URL to fetch" }),
  }),
  async execute(_id, params) {
    const hostname = new URL(params.url).hostname;
    if (!ALLOWED_DOMAINS.includes(hostname)) {
      throw new Error(`Domain not allowed: ${hostname}`);
    }
    const resp = await fetch(params.url);
    const html = await resp.text();
    return { content: [{ type: "text", text: turndown.turndown(html) }], details: {} };
  },
});

// save_news_item defined the same way (full source on GitHub)

SDK 的编程模型是基于事件的。你创建一个会话,发送提示词,然后在智能体工作的过程中订阅事件。会话设置如下:

// Replace Pi's default system prompt and suppress global skill discovery
const resourceLoader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  systemPromptOverride: () => SYSTEM_PROMPT,
  appendSystemPromptOverride: () => [],
  skillsOverride: () => ({ skills: [], diagnostics: [] }),
});
await resourceLoader.reload();

// Create a session with only our two custom tools
const { session } = await createAgentSession({
  resourceLoader,
  customTools: [webFetchTool, saveNewsItemTool],
  tools: ["web_fetch", "save_news_item"],
  sessionManager: SessionManager.inMemory(),
});

// Stream agent output to stdout
session.subscribe((event) => {
  if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
    process.stdout.write(event.assistantMessageEvent.delta);
  }
});

// Run the agent
await session.prompt("Go");
session.dispose();

结果是一个完全自包含的设置:没有文件系统发现,也没有会话持久化。认证默认使用 ~/.pi/agent/auth.jsonpi /login 存储的任何内容),但你可以传入自定义的 authStorage 来实现完全独立的设置。

session.subscribe() 是你观察智能体所做一切的方式。会话会为文本输出、工具调用、工具结果、思考和压缩(compaction)发出事件。这里我只关心把文本流式输出到 stdout。SDK 示例展示了更多模式。

npx tsx scrape.ts 运行它。

2、比较两个 SDK

两个 SDK 最终形状相似:

Claude Agent SDK Pi SDK
语言 Python TypeScript
入口点 query() 异步生成器 createAgentSession() + session.prompt()
工具定义 @tool 装饰器 + Pydantic defineTool() + TypeBox
系统提示词 prompt 参数 systemPromptOverride 回调
工具限制 allowed_tools 列表 tools 白名单
会话 默认无状态 SessionManager.inMemory()
子智能体 AgentDefinition 对象 不可用
输出 消息的异步迭代器 事件订阅

Claude Agent SDK 有更多内置结构。Pi 的 SDK 给你一个带工具和提示词的会话,其余部分由你自己构建。

3、什么时候用 SDK 而不是 CLI

CLI 适合一次性自动化:运行一条命令、拿到结果、退出。当你想把 Pi 嵌入到更大的应用中,或者在一个会话里链式发起多个提示词而无需重新初始化时,SDK 才更有意义。

对于这个新闻阅读器来说,CLI 版本更简单。SDK 版本则可以作为参考:当你需要把 Pi 当作库而不是命令来使用时。


原文链接:Agent engineering: Pi SDK

汇智网翻译整理,转载请标明出处