跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

Extension

最后更新 查看 MarkdownAgent 设置

工具与系统提示词 section 都通过 extension 提供给 Pi Harness。extension 属于 Pi Durable:带 name、tools 与 sections 的普通对象,安装在你传给 Harness.open() 的 registry 上。PiHarness 不会包装它们,因此所有 Pi Durable extension 功能都可用。

安装 extension

用 createRegistry() 创建 registry,并在 harness factory 中、Harness.open() 之前安装 extension:

src/index.jsjs
import { DurableObject } from "cloudflare:workers";
import { Type } from "@earendil-works/pi-ai";
import { createModels } from "@earendil-works/pi-ai/models";
import { createRegistry, Harness } from "@earendil-works/pi-durable";
import { PiHarness } from "agents/harness/pi";
import { Lifecycle } from "agents/lifecycle";
import { createAI } from "agents/models/pi-ai";

const WordCount = Type.Object({ text: Type.String() });

const wordCount = {
	name: "word_count",
	description: "Count the words in a text.",
	parameters: WordCount,
	replay: "safe",
	// `text` is typed from `parameters`, which Pi validates first.
	async execute({ text }) {
		const words = text.split(/\s+/).filter(Boolean).length;
		return { output: [{ type: "text", text: String(words) }] };
	},
};

export class Editor extends DurableObject {
	ai = createAI({ binding: this.env.AI });
	registry = createRegistry();

	harness = new PiHarness({
		harness: ({ storage, context, onWake }) => {
			this.registry.install({
				name: "editor",
				sections: [
					{ key: "preamble", render: () => "You are an editor.", tag: false },
				],
				tools: [wordCount],
			});
			const models = createModels();
			models.setProvider(this.ai.provider);
			return Harness.open(
				storage,
				{ models, registry: this.registry, onWake },
				context,
			);
		},
		defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") },
	});

	lifecycle = Lifecycle.install(this).use(this.harness);
}
src/index.tsts
import { DurableObject } from "cloudflare:workers";
import { Type } from "@earendil-works/pi-ai";
import { createModels } from "@earendil-works/pi-ai/models";
import {
    createRegistry,
    Harness,
    type ToolRegistration,
} from "@earendil-works/pi-durable";
import { PiHarness } from "agents/harness/pi";
import { Lifecycle } from "agents/lifecycle";
import { createAI } from "agents/models/pi-ai";

const WordCount = Type.Object({ text: Type.String() });

const wordCount: ToolRegistration<typeof WordCount> = {
    name: "word_count",
    description: "Count the words in a text.",
    parameters: WordCount,
    replay: "safe",
    // `text` is typed from `parameters`, which Pi validates first.
    async execute({ text }) {
        const words = text.split(/\s+/).filter(Boolean).length;
        return { output: [{ type: "text", text: String(words) }] };
    },
};

export class Editor extends DurableObject<Env> {
    ai = createAI({ binding: this.env.AI });
    registry = createRegistry();

    harness = new PiHarness({
        harness: ({ storage, context, onWake }) => {
            this.registry.install({
                name: "editor",
                sections: [
                    { key: "preamble", render: () => "You are an editor.", tag: false },
                ],
                tools: [wordCount],
            });
            const models = createModels();
            models.setProvider(this.ai.provider);
            return Harness.open(
                storage,
                { models, registry: this.registry, onWake },
                context,
            );
        },
        defaults: { model: this.ai("@cf/zai-org/glm-5.3-flash") },
    });

    lifecycle = Lifecycle.install(this).use(this.harness);
}

factory 在 object 启动期间运行;如果 harness 在存储错误后重新打开 Pi,它也会再次运行。registry.install() 会替换同名的已安装 extension,因此可以安全地在 factory 中重复安装 extension。

请为每个 object 创建 registry,并将其作为 class field,而不是在 module scope 中创建。Pi 会让每个 Harness 一直订阅其 registry,直到 Harness 关闭;被驱逐的 object 永远不会关闭它。

编写工具

工具是 Pi Durable 的 ToolRegistration:

  • 带类型的参数。 将工具类型写为 ToolRegistration<typeof Parameters>,execute 会获得来自 schema 的参数类型。Pi 在调用 execute 前按 parameters 校验它们。裸 ToolRegistration 将参数类型为 unknown。
  • 运行中的调用可用内容。 execute(args, api, context) 获得 Pi 为该调用提供的操作。api.output() 流式输出运行中输出,api.details() 发送供 UI 使用的数据,api.memo() 存储在驱逐后仍然保留的值。
  • Abort。 调用被 abort 时 context.abortSignal 被 abort。请遵守它:session.abort() 会等到每个正在运行的工具返回。
  • 并行调用。 Pi 默认同时运行同一模型轮次中的工具调用。对必须依次运行的工具设置 executionMode: "sequential"。一次 sequential 调用会使其整轮变为 sequential。
const FetchPage = Type.Object({ url: Type.String() });

const fetchPage: ToolRegistration<typeof FetchPage> = {
    name: "fetch_page",
    description: "Fetch a web page as text.",
    parameters: FetchPage,
    replay: "safe",
    async execute({ url }, api, context) {
        api.output(`fetching ${url}\n`);
        const response = await fetch(url, { signal: context.abortSignal });
        return { output: [{ type: "text", text: await response.text() }] };
    },
};

将工具标记为 replay-safe

驱逐中断工具调用时,Pi 根据工具的 replay 设置决定如何处理:

  • replay: "safe":对象重启后 Pi 再次运行该工具。
  • replay: "unsafe"(默认):Pi 不会再次运行它。模型得到中断结果并决定下一步。

当两次运行无害时将工具标记为 safe,例如读取、搜索或整文件写入。当第二次运行会失败或重复副作用时保持 unsafe,例如已替换文本的编辑、运行代码或发送邮件。

编写 prompt section

section 的 render 在每次模型请求前运行。它接收对话、其 Agent 与提供的工具,以及已提交的读取,因此 section 可以随请求变化。除非 tag 为 false,Pi 会将输出包在 <key> 标签中:

this.registry.install({
    name: "context",
    sections: [
        { key: "preamble", render: () => "You are an editor.", tag: false },
        { key: "today", render: () => new Date().toDateString() },
    ],
});

添加 skills

来自 agents/harness/pi 的 skills(sources) 会将 agents/skills 中的 Agent Skills source 转换为名为 agents.skills 的 Pi extension。source 可以从 Computer Workspace、R2 或 manifest 读取 skill。extension 有两个 tool:activate_skill 和 read_skill_resource,以及列出 skill 的 skills section。它会读取 source,因此请将其安装在 async factory 中:

import { skills } from "agents/harness/pi";
import { manifest } from "agents/skills";

const handbook = manifest({
    id: "handbook",
    skills: [
        {
            name: "release-notes",
            description: "Write release notes.",
            body: "One line per change.",
        },
    ],
});

harness = new PiHarness({
    harness: async ({ storage, context, onWake }) => {
        this.registry.install(await skills([handbook]));
        // ...models, then Harness.open() with onWake
    },
});

这些 tool 与 Think 提供的 tool 匹配,因此为其中一个编写的 skill 也适用于另一个。factory 构建 extension 时只读取一次 source。模型可以读取 skill 的说明和资源。要运行 skill script,请为模型提供 shell 或 Workspace tool。请参阅执行。

使用更多 Pi Durable 功能

因为 registry 属于 Pi,Pi Durable extension 能做的其余事情同样可用:

  • 模型请求、工具调用与 compaction 上的 hook,例如 hook(ToolTask, { beforeTool })。
  • 重启后恢复的持久自定义任务。
  • 包装另一 extension 的工具与 section。
  • 按对话选择 extension。
  • 带类型文档中的 extension 状态。

各项请参阅 Pi Durable README ↗。

这篇文档对您有帮助吗?