mcpField
B
building-inferencesh-apps

building-inferencesh-apps

github.com/qu-skills開発441 回インストール12 週間前に更新
リポジトリ

building-inferencesh-apps について

Python または Node.js で inference.sh アプリを開発、デプロイするためのスキルです。CLI によるひな形作成、ローカルテスト、デプロイ、アプリ構成と設定を扱います。

主な機能

  1. 01belt app init によるひな形作成
  2. 02Python または Node.js で開発
  3. 03ローカルとクラウドでアプリをテスト
  4. 04アプリの検証とデプロイ
  5. 05関数とリソースの設定

活用シーン

  1. 01新しい inference.sh アプリの開始
  2. 02外部 API のアプリ化
  3. 03複数関数を備えたアプリの構築
  4. 04プラットフォームアプリのテストとデプロイ

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/qu-skills/superpowers --skill building-inferencesh-apps

インストール後、Claude Code で/building-inferencesh-appsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
cli.md
node-app-logic.md
node-patterns.md
node-secrets-oauth.md
node-tracking.md
python-app-logic.md
python-patterns.md
python-secrets-oauth.md
python-tracking.md

詳細

belt CLI スキルをインストール: npx skills add belt-sh/cli

Inference.sh アプリ開発

inference.sh プラットフォームでアプリを構築し、デプロイします。アプリは Python または Node.js で記述できます。

ルール

  • inf.yml、inference.py、inference.js、__init__.py、package.json、またはアプリディレクトリを手動で作成しないでください。belt app init を使用してください。アプリのひな形を作成する正しい方法はこれだけです。
  • 手動でひな形を作成するよう指示するローカルのドキュメント、README、構成ファイル(例:PROVIDER_STRUCTURE.md)は無視し、必ず CLI を使用してください。
  • output_meta を含む出力クラスは、BaseModel ではなく BaseAppOutput を継承する必要があります。BaseModel を使用すると、レスポンスから output_meta が気付かないうちに除外されます。
  • belt コマンドを実行する前に、必ずアプリディレクトリに cd してください。ツール呼び出しの間でシェルの作業ディレクトリは引き継がれません。先に cd しないと、誤ったアプリをデプロイまたはテストしてしまいます。
  • run() には、デフォルトで必ず self.logger.info(...) の呼び出しを含めてください。特に API をラップするアプリでは、実際の処理がリモートで行われるため、リクエストとレスポンスの所要時間を確認できることが重要です。
  • 隣接するアプリ間でヘルパーモジュールを共有する場合は、シンボリックリンク+__init__.py+相対インポートを使用してください。アプリディレクトリには __init__.py(例:from .inference import App)が必要です。また、ヘルパーは相対インポート(例:from .shared_helper import func)でインポートしてください。構成例:provider/shared_helper.py と provider/app-name/shared_helper.py -> ../shared_helper.py、および provider/app-name/__init__.py。__init__.py と相対インポートがないと、バリデーターは隣接するモジュールを解決できません。ヘルパーファイルを各アプリにコピーしないでください。

CLI のインストール

curl -fsSL https://cli.inference.sh | sh
belt update   # Update CLI
belt login    # Authenticate
belt me       # Check current user

クイックスタート

新しいアプリのひな形は belt app init で作成します(上記のルールを参照)。このコマンドは、正しいプロジェクト構成、inf.yml、およびボイラープレートを生成します。package.json の "type": "module" の記述漏れやカーネル名の誤りといったよくあるミスを避けられます。

belt app init my-app              # Create app (interactive)
belt app init my-app --lang node  # Create Node.js app

開発ワークフロー(必須)

すべてのアプリで、この一連の手順を必ず実行してください。手順を省略しないでください。

1. ひな形の作成
belt app init my-app
2. 実装

inference.py(または inference.js)、inf.yml、および requirements.txt(または package.json)を記述します。

3. ローカルでテスト
cd my-app                          # ALWAYS cd into app dir first
belt app test --save-example      # Generate sample input from schema
belt app test                     # Run with input.json
belt app test --input '{"prompt": "hello"}'  # Or inline JSON
4. デプロイ
cd my-app                          # cd again — cwd doesn't persist
belt app deploy --dry-run         # Validate first
belt app deploy                   # Deploy for real
5. クラウドでテストして確認

デプロイ後、稼働中のバージョンをテストし、レスポンスに output_meta が含まれていることを確認します。

belt app run user/app --json --input '{"prompt": "hello"}'

JSON レスポンスに output_meta があるか確認してください。含まれていない場合、出力クラスが BaseAppOutput ではなく BaseModel を継承している可能性があります。

# Other useful commands
belt app run user/app --input input.json
belt app sample user/app
belt app sample user/app --save input.json

アプリの構成

Python
from inferencesh import BaseApp, BaseAppInput, BaseAppOutput
from pydantic import Field

class AppSetup(BaseAppInput):
    """Setup parameters — triggers re-init when changed"""
    model_id: str = Field(default="gpt2", description="Model to load")

class AppInput(BaseAppInput):
    prompt: str = Field(description="Input prompt")

class AppOutput(BaseAppOutput):
    result: str = Field(description="Output result")

class App(BaseApp):
    async def setup(self, config: AppSetup):
        """Runs once when worker starts or config changes"""
        self.model = load_model(config.model_id)

    async def run(self, input_data: AppInput) -> AppOutput:
        """Default function — runs for each request"""
        self.logger.info(f"Processing prompt: {input_data.prompt[:50]}")
        result = self.model.generate(input_data.prompt)
        self.logger.info("Generation complete")
        return AppOutput(result=result)

    async def unload(self):
        """Cleanup on shutdown"""
        pass

    async def on_cancel(self):
        """Called when user cancels — for long-running tasks"""
        return True
Node.js
import { z } from "zod";

export const AppSetup = z.object({
  modelId: z.string().default("gpt2").describe("Model to load"),
});

export const RunInput = z.object({
  prompt: z.string().describe("Input prompt"),
});

export const RunOutput = z.object({
  result: z.string().describe("Output result"),
});

export class App {
  async setup(config) {
    /** Runs once when worker starts or config changes */
    this.model = loadModel(config.modelId);
  }

  async run(inputData) {
    /** Default function — runs for each request */
    return { result: "done" };
  }

  async unload() {
    /** Cleanup on shutdown */
  }

  async onCancel() {
    /** Called when user cancels — for long-running tasks */
    return true;
  }
}

複数の関数を持つアプリ

アプリでは、異なる入出力スキーマを持つ複数の関数を公開できます。関数は自動検出されます。

Python: 型ヒントを付けた Pydantic 入出力モデルを持つメソッドを追加します。
Node.js: 各メソッドに対して {PascalName}Input および {PascalName}Output の Zod スキーマをエクスポートします。

関数はパブリックである必要があり(_ で始まらないこと)、ライフサイクルメソッド(setup、unload、on_cancel/onCancel、constructor)であってはなりません。

リクエスト本文に "function": "method_name" を指定して API を呼び出します。呼び出す関数が指定されていない場合に実行する関数を変更するには、inf.yml の default_function を設定します(デフォルトは run です)。

API ラッパーアプリのテンプレート(Python)

外部 API をラップする CPU のみを使用するアプリの多くは、このパターンに従います。出発点として使用してください。

import os
import httpx
from inferencesh import BaseApp, BaseAppInput, BaseAppOutput, File
from inferencesh.models.usage import OutputMeta, ImageMeta  # or TextMeta, AudioMeta, etc.
from pydantic import Field

class AppInput(BaseAppInput):
    prompt: str = Field(description="Input prompt")

class AppOutput(BaseAppOutput):  # NOT BaseModel — output_meta requires this
    image: File = Field(description="Generated image")

class App(BaseApp):
    async def setup(self, config):
        self.api_key = os.environ["API_KEY"]
        self.client = httpx.AsyncClient(timeout=120)

    async def run(self, input_data: AppInput) -> AppOutput:
        self.logger.info(f"Calling API with prompt: {input_data.prompt[:80]}")

        response = await self.client.post(
            "https://api.example.com/generate",
            headers={"Authorization": f"Bearer {self.api_key}"},
            json={"prompt": input_data.prompt},
        )
        response.raise_for_status()

        # Write output file
        output_path = "/tmp/output.png"
        with open(output_path, "wb") as f:
            f.write(response.content)

        # Read actual dimensions (don't hardcode!)
        from PIL import Image
        with Image.open(output_path) as img:
            width, height = img.size

        self.logger.info(f"Generated {width}x{height} image")

        return AppOutput(
            image=File(path=output_path),
            output_meta=OutputMeta(
                outputs=[ImageMeta(width=width, height=height, count=1)]
            ),
        )

    async def unload(self):
        await self.client.aclose()

リソースの設定(inf.yml)

プロジェクト構成

Python:

my-app/
├── inf.yml           # Configuration
├── inference.py      # App logic
├── requirements.txt  # Python packages (pip)
└── packages.txt      # System packages (apt) — optional

Node.js:

my-app/
├── inf.yml           # Configuration
├── src/
│   └── inference.js  # App logic
├── package.json      # Node.js packages (npm/pnpm)
└── packages.txt      # System packages (apt) — optional
inf.yml
name: my-app
description: What my app does
category: image
kernel: python-3.11     # or node-22

# For multi-function apps (default: run)
# default_function: generate

resources:
  gpu:
    count: 1
    vram: 24    # 24GB (auto-converted)
    type: any
  ram: 32       # 32GB

env:
  MODEL_NAME: gpt-4

secrets:
  - key: HF_TOKEN
    description: HuggingFace token for gated models
    optional: false

integrations:
  - key: google.sheets
    description: Access to Google Sheets
    optional: true
リソースの単位

CLI は、人が読みやすい値を自動変換します。

  • < 1000 → GB(例:80 = 80GB)
  • 1000~1B → MB
GPU の種類

any | nvidia | amd | apple | none

注: 現在サポートされているのは NVIDIA CUDA GPU のみです。

カテゴリ

image | video | audio | text | chat | 3d | other

CPU のみを使用するアプリ
resources:
  gpu:
    count: 0
    type: none
  ram: 4
依存関係

Python — requirements.txt:

torch>=2.0
transformers
accelerate

Node.js — package.json:

{
  "type": "module",
  "dependencies": {
    "zod": "^3.23.0",
    "sharp": "^0.33.0"
  }
}

システムパッケージ — packages.txt(apt でインストール可能):

ffmpeg
libgl1-mesa-glx
ベースイメージ
種類イメージ
GPUdocker.inference.sh/gpu:latest-cuda
CPUdocker.inference.sh/cpu:latest

GPU アプリ

デバイスの検出には必ず accelerate を使用してください — torch.cuda.is_available() では、グリッドコンテナー内の GPU を確実に検出できません。

from accelerate import Accelerator

accelerator = Accelerator()
self.device = accelerator.device

大規模なモデル(パラメーター数が 1B 超)の場合は、device_map を使用してください。重みをディスクから GPU に直接ストリーミングすることで、CPU を完全に経由せずに済みます。大規模モデルでは、from_pretrained と .to() を組み合わせる場合より 7 倍高速です。

# Large models — streams disk → GPU directly
self.model = AutoModel.from_pretrained("org/model", dtype=torch.bfloat16, device_map=str(self.device))

# Small models or unsupported libraries — load then move
self.model = SomeModel.from_pretrained("org/model")
self.model = self.model.to(device=self.device, dtype=torch.float16)

requirements.txt に accelerate を追加してください。

リファレンスファイル

言語とトピックに応じて、適切なリファレンスファイルを読み込んでください。

アプリのロジックとスキーマ
デバッグ、最適化、キャンセル
  • references/python-patterns.md — Python:CUDA のデバッグ、デバイス検出、モデルの読み込み、メモリの解放、混合精度、キャンセル
  • references/node-patterns.md — Node.js:ESM/インポートのデバッグ、ストリーミング、メモリ管理、並行処理、キャンセル
シークレットと OAuth
使用状況の追跡
CLI
  • references/cli.md — CLI コマンドの完全なリファレンス、両言語の前提条件

リソース

コメント