Community程式設計與開發github.com

partite-ai/write-particle-python

使用 Particlefile.py 建立、編輯或擴展一個 Python Partite Particle。適用於 Python 工具處理程式、API 整合、憑證及執行時期功能。若需 TypeScript 或 JavaScript Particle,請使用對應語言的技能(如有)。

write-particle-python 是什麼?

此技能提供建立 Partite Python Particle 的完整指南。Particle 是一個單一檔案程式,可向 Partite 執行時期公開一個或多個「工具」(具備 JSON Schema 輸入的命名操作)。其核心是 `Particlefile.py` 檔案,其中包含一個 PEP 723 內嵌中繼資料區塊和一個模組級別的 `particle` 變數(一個 `Particle` 實例),用於定義工具、HTTP 設定、檔案系統存取、憑證等功能。此技能詳細記錄了每個元件:工具使用簡單的 `def` 或 `async def` 處理常式,並傳回 JSON 可序列化值;HTTP 存取支援多種程式庫(`http.fetch`、`urllib.request`、`urllib3`、`requests`、`httpx`),並自動繞送通過主機允許清單;檔案系統存取需明確宣告權限,並透過 `wasi:filesystem` 路由;而憑證管理則支援 OAuth2、API 金鑰、Basic 認證、簽署金鑰及原始值。其適用平台為 Partite 執行時期生態系統(`particle build`、`particle run` 指令列介面),開發方式為輕量級、單一檔案的 Python 腳本,幾乎不需要設定(無需 `pyproject.toml` 或虛擬環境)。

相容平台✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/partite-ai/partite-skills/tree/main/skills/write-particle-python

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

建立一個 Python particle

一個 particle 是一個單一檔案程式,可向 Particle 執行時期公開一個或多個工具(具備 JSON-Schema 輸入的命名操作)。此技能提供你產出一個可建置並執行的 particle 所需的一切知識。

1. 檔案結構

在專案根目錄下放置一個原始碼檔案,並精確命名為 Particlefile.py。不需要其他檔案。PyPI 相依套件在檔案頂部的 PEP 723 內嵌中繼資料區塊中宣告;建置時會解析這些相依關係。不需要管理 pyproject.toml 或虛擬環境。

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "idna>=3",       # 選用——僅在使用第三方套件時需要
# ]
# ///

2. 模組層級的 particle——完整的 API

檔案必須將一個 Particle 實例賦值給一個模組層級的 particle 變數。執行時期在建置時會對此進行內省,並在每次工具呼叫時讀取其處理常式。

from particle.manifest import (
    Particle, Tool, Http, Filesystem, Mount, TempMount,
    Credential, OAuth2, ApiKey, ApiKeyLocation, SigningKey, Basic, Raw,
)
from particle import http, credentials, kv, oauth, signing

def _do_thing(args):
    return {"result": f"hello {args['name']}"}

def _ping():
    return {"status": "ok", "message": "alive"}

particle = Particle(
    name="kebab-case-name",       # 必填,kebab-case
    description="...",             # 必填,一行
    version="0.1.0",               # 必填,semver

    # 對外 HTTP 允許清單。未在此列出的主機將在網路層級被拒絕。
    # 若 particle 不發送任何對外請求,可完全省略 `http=`。
    http=Http(allowed_hosts=["api.example.com"]),

    # 主機目錄存取,預設為關閉。請參閱下方的「檔案系統」。
    # filesystem=Filesystem(mounts={...}, temp={...}),

    # 選用。依名稱宣告;使用者在設定時選擇方法。
    credentials={
        "example": Credential(
            hosts=["api.example.com"],
            required=True,
            methods={
                # 請參閱下方的「憑證方法」
            },
        ),
    },

    # 必填。name → Tool(description, input_schema, handler)。
    tools={
        "do_thing": Tool(
            description="此工具的功能——一行,專為 LLM 撰寫。",
            input_schema={
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "..."},
                },
                "required": ["name"],
            },
            handler=_do_thing,
        ),
    },

    # 選用。由 `particle ping` 呼叫。
    ping=_ping,
)

工具

  • description:專為 LLM 呼叫者撰寫——簡短、以行動為導向。
  • input_schema:JSON Schema Draft 2020-12,以物件為根。主機在呼叫你的處理常式之前會根據 schema 驗證引數,因此你可以信任處理常式內部的 dict 結構。
  • handler:一個接受一個位置 dict 並傳回任何 JSON 可序列化值的純粹可呼叫物件。可以是 def 或 async def;非同步處理常式在一次性輪詢迴圈中執行。透過拋出例外來表現錯誤。

Ping

選用。傳回一個類似以下的 dict:

{"status": "ok" | "degraded" | "unhealthy",
 "message": "...",            # 選用
 "details": ...}              # 選用,JSON 可序列化

3. 可供處理常式使用的執行時期 API

所有主機功能都位於 from particle import ... 之下。這些是唯一需要的非標準庫模組。

對外 HTTP

有四種等效的方式可發送 HTTP 呼叫。選擇與你程式碼其餘部分慣用語法相符的方式;它們都會透過主機允許清單路由。

particle.http.fetch——原生 API。當設定 credential_name= 時會自動套用憑證。

from particle import http

res = http.fetch(
    "https://api.example.com/things",
    method="POST",
    headers={"Content-Type": "application/json"},
    body=json.dumps({...}).encode(),
    credential_name="example",   # 選用;主機會替換秘密
)
if not res.ok:
    raise RuntimeError(f"upstream {res.status_code}")
data = res.json()

http.fetch 傳回一個 Response,其中包含:

  • .status_code (int), .ok (bool),
  • .headers ( (name, bytes) 元組的 list ), .header(name) 查詢,
  • .body (bytes), .text (str), .json() (已解析)。

urllib.request.urlopen——直接使用即可。執行時期會對其進行猴子補丁,使其透過 wasi:http 路由。

import urllib.request
with urllib.request.urlopen("https://api.example.com/x") as r:
    body = r.read()

urllib3 / requests——直接使用即可。執行時期會在 urllib3 匯入時注入一個自訂連線類別,因此 requests.get(...) 等可以直接運作。它們不會自動套用憑證——對於需要認證的請求,請使用 http.fetch(..., credential_name=...),或自行取得佔位符並設定標頭。

import requests
r = requests.get("https://api.example.com/x")     # 運作

httpx——直接使用即可,同步和非同步皆可。匯入時會注入自訂 HTTPTransport / AsyncHTTPTransport。

import httpx
r = httpx.get("https://api.example.com/x")

原始 socket 不可用。 socket.socket() 會拋出 OSError。

檔案系統

預設為關閉——處理常式只能看到自己的套件。需宣告 Filesystem 功能以取得主機目錄存取權,然後使用一般的 open() / pathlib 進行讀寫(執行時期會透過 wasi:filesystem 路由)。

from particle.manifest import Filesystem, Mount, TempMount

filesystem=Filesystem(
    # 使用者映射到真實路徑的主機目錄——可使用
    # `particle mount <particle> <name> <host-path>` 永久映射,或每次執行時
    # 使用 `--mount <name>=<host-path>` 映射。
    mounts={
        "data": Mount(
            description="報告寫入的位置",  # 顯示在安裝提示中
            path="/mnt/data",                          # 沙箱內的絕對路徑
            access="readwrite",                        # "readonly" | "readwrite"
            required=True,                             # 拒絕執行直到映射完成(預設為 False)
        ),
    },
    # 主機每次執行時全新配置並在結束時清除的暫存空間。
    # 始終為讀寫;使用者無需映射這些。
    temp={
        "work": TempMount(description="暫存空間", path="/tmp/work", max_size="10MB"),
    },
),
with open("/mnt/data/in.json") as f:
    data = f.read()
with open("/mnt/data/out.json", "w") as f:
    f.write(result)
  • 處理常式直接開啟已宣告的 path;掛載名稱永遠不會出現在沙箱內部。
  • 寫入 readonly 掛載,或讀寫任何已宣告掛載之外的路徑,會拋出 OSError。
  • 使用者從未映射的 required 掛載會在執行時期(而非建置/安裝時)失敗。
  • max_size 是一個位元組計數,可選擇性加上 KB/MB/GB 後綴(如 "10MB");超過此大小的寫入會失敗。

憑證

from particle import credentials

# 使用者在設定時選擇的方法("oauth"、"pat" 等)或 None。
method = credentials.get_configured_method("example")

# "raw" 類型憑證的原始值。
value = credentials.get_raw("some-raw-cred")

# 低階:取得一個不透明佔位符 + 描述主機預期在哪裡替換它的 apply-spec。
# 幾乎總是使用 `http.fetch(..., credential_name=...)` 來代替,它會在內部執行此操作。
info = credentials.get_placeholder("example")

鍵值儲存

以每個 particle 為範圍。僅支援字串——如有需要,可對二進位資料進行 base64 編碼。需在 Particle 上使用 kv=KV(enabled=True) 宣告——若無宣告,每次呼叫都會拋出 not-declared 錯誤(無需使用者核准,只需宣告即可):

from particle.manifest import Particle, KV
# particle = Particle(..., kv=KV(enabled=True))
from particle import kv

kv.set("cursor", "abc123")
v = kv.get("cursor")             # str | None
kv.delete("cursor")
keys = kv.list("prefix:")        # list[str]   — 注意:會遮蔽內建的 list

將模組匯入為 kv(而非 from particle.kv import list),這樣 list 就不會遮蔽內建的 list。

OAuth 重新整理

僅當你有 OAuth2 憑證時使用。使用 credential_name= 呼叫 http.fetch 會自動重新整理已過期的令牌;僅在上游拒絕了你認為仍然有效的令牌時才呼叫此方法。

from particle import oauth
oauth.refresh("example")

簽署

僅適用於 SigningKey 憑證。金鑰材料絕不會進入 Python。

from particle import signing
sig = signing.sign("webhook-key", payload_bytes)
ok  = signing.verify("webhook-key", data_bytes, signature_bytes)

4. 憑證方法

每個 Credential 都有一個 methods={...} 映射。使用者在設定時選擇一個方法。資料類別類型來自 particle.manifest。

OAuth2

"oauth": OAuth2(
    description="透過 OAuth 登入",
    flows=["authorization-code", "device-code"],
    scopes=["read", "write"],
    authorization_url="https://provider/oauth/authorize",
    token_url="https://provider/oauth/token",
    device_auth_url="https://provider/oauth/device/code",  # 適用於 "device-code"
),

有效的流程字串:"authorization-code"、"authorization-code-pkce"、"device-code"。

ApiKey

"pat": ApiKey(
    description="使用個人存取令牌",
    location=ApiKeyLocation(kind="auth-scheme", scheme="Bearer"),
),

ApiKeyLocation.kind 可以是:

  • "header" + name:<name>: <key>
  • "auth-scheme" + scheme:Authorization: <scheme> <key>(例如 Bearer)
  • "query-param" + name:附加為 ?<name>=<key>

完全省略 location 以讓匯入者在設定時提示使用者。

Basic

"basic_auth": Basic(description="使用者名稱 + 密碼"),

替換為 Authorization: Basic <base64>。

SigningKey

"webhook": SigningKey(description="...", algorithm="hmac-sha256"),

有效的演算法:"hmac-sha256"、"hmac-sha512"。透過 signing.sign 使用。

Raw

"api_key_blob": Raw(description="..."),

透過 credentials.get_raw(name) 使用。僅在確實需要原始位元組時選擇此項——其他類型更安全,因為該值永遠不會進入 Python 記憶體。

5. PyPI 相依套件

在檔案頂部的 PEP 723 區塊中宣告:

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "httpx>=0.27",
#   "pyjwt>=2",
# ]
# ///

限制條件:

  • 僅限純 Python wheel。 任何需要 C 擴充功能的套件(如 numpy、pydantic v1、lxml 等)都無法載入。
  • 標準庫大部分可用——json、re、hashlib、hmac、base64、secrets、datetime、urllib.parse、asyncio、email 等都可運作。
  • ssl、原始 socket、subprocess——不可用。
  • open() / pathlib 僅能操作已宣告檔案系統掛載內的路徑(請參閱上方「檔案系統」)以及 particle 自身的 /particle/ 套件;任意主機路徑都會被拒絕。

6. 重要規則

  1. 不要在模組作用域呼叫主機功能。 在檔案頂層呼叫 http.fetch、kv.get、credentials.get_placeholder 等會在建置時失敗(內省階段會在陷阱儲存下執行你的模組)。所有主機呼叫都必須在 handler 或 ping 函式內部進行。

    # ✗ 會導致建置失敗
    _cached_data = http.fetch("https://api.example.com/init")
    particle = Particle(...)
    
    # ✓ 在處理常式內部
    def _go(args):
        res = http.fetch("https://api.example.com/...")
        ...
    particle = Particle(tools={"go": Tool(..., handler=_go)})
    
  2. 宣告你將擷取的每一個主機。 對缺少於 http=Http(allowed_hosts=[...]) 清單中的主機發送請求會被拒絕。錯誤會以處理常式中的例外形式呈現。

  3. Credential.hosts 必須是 HTTP 允許清單的子集。 建置時會拒絕綁定到 particle 無法連線之主機的憑證。

  4. name 必須是 kebab-case(小寫、連字號)。註冊表的鍵是 (name, version)。

  5. version 必須是有效的 semver。 例如 0.1.0、1.2.3-rc.1 等。

  6. 工具處理常式必須傳回 JSON 可序列化的值。 純 dict、list、str、int、float、bool、None。dataclass 或 datetime 將無法序列化——請在傳回前進行轉換。

7. 工作流程

在寫好 Particlefile.py 之後:

particle build                          # 解析、解析相依性、內省、註冊
particle ping <name>                    # 驗證 ping(如果有定義)
particle run  <name>                    # 列出工具
particle run  <name> <tool> --help      # 顯示工具的旗標
particle run  <name> <tool> --foo=bar   # 執行它

particle build 會在首次執行時以互動方式引導使用者完成憑證設定。如果建置失敗,錯誤訊息會指出是哪個階段(import-scan、resolve-and-fetch、manifest-extract)以及具體問題。

如果 particle 宣告了檔案系統掛載,請在執行前對其進行映射(或在 particle run 中傳遞 --mount name=path 進行一次性執行):

particle mount <name> <mount-name> <host-path>   # 儲存持久映射
particle mount <name>                            # 列出掛載 + 當前映射

若要將 particle 公開為獨立可執行檔(對於 Claude Code 技能和 shell 使用很方便),請執行 particle link <name> ./<name> 來建立一個啟動器,它會將其引數轉發給 particle run <name>。

8. 完整範例

一個公開 GitHub 倉庫查詢工具的最小 particle:

# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///
import json
from particle import http
from particle.manifest import Particle, Tool, Http, Credential, ApiKey, ApiKeyLocation

def _get_repo(args):
    res = http.fetch(
        f"https://api.github.com/repos/{args['owner']}/{args['repo']}",
        headers={"Accept": "application/vnd.github+json"},
        credential_name="github",
    )
    if not res.ok:
        raise RuntimeError(f"GitHub {res.status_code}: {res.text}")
    r = res.json()
    return {
        "full_name": r.get("full_name"),
        "stars":     r.get("stargazers_count"),
        "language":  r.get("language"),
        "description": r.get("description"),
    }

particle = Particle(
    name="github-repo-lookup",
    description="查詢 GitHub 倉庫的詳細資訊",
    version="0.1.0",
    http=Http(allowed_hosts=["api.github.com"]),
    credentials={
        "github": Credential(
            hosts=["api.github.com"],
            required=True,
            methods={
                "pat": ApiKey(
                    description="GitHub 個人存取令牌",
                    location=ApiKeyLocation(kind="header", name="Authorization"),
                ),
            },
        ),
    },
    tools={
        "get_repo": Tool(
            description="取得 GitHub 倉庫的資訊,包括星數、語言和描述。",
            input_schema={
                "type": "object",
                "properties": {
                    "owner": {"type": "string", "description": "倉庫擁有者(使用者或組織)"},
                    "repo": {"type": "string", "description": "倉庫名稱"},
                },
                "required": ["owner", "repo"],
            },
            handler=_get_repo,
        ),
    },
)

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相關技能