building-inferencesh-apps について
inference.sh 上で Python または Node.js のアプリを開発・デプロイするためのスキルです。CLI によるひな形作成を軸に、テストやデプロイ、プラットフォーム固有の実装方法を案内します。
主な機能
- 01CLI によるアプリのひな形作成
- 02Python と Node.js の開発ガイド
- 03ローカルテストとデプロイの手順
- 04複数関数アプリのパターン
- 05GPU とリソースの設定
活用シーン
- 01inference.sh アプリの開発開始
- 02belt によるアプリのテストとデプロイ
- 03複数の呼び出し可能な関数を持つアプリの構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ inference-sh/ skills --skill building-inferencesh-appsインストール後、Claude Code で/building-inferencesh-appsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/cli.mdnode-app-logic.mdnode-patterns.mdnode-secrets-oauth.mdnode-tracking.mdpython-app-logic.mdpython-patterns.mdpython-secrets-oauth.mdpython-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(...)の呼び出しを含めてください。BaseAppにはlogger属性がありません。まずimport loggingし、setup()でself.logger = logging.getLogger(__name__)を設定してください。実際の処理がリモートで行われるため、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
import logging
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.logger = logging.getLogger(__name__)
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 logging
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.logger = logging.getLogger(__name__)
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 # the slug that addresses the app: namespace/name
title: My App # optional — how it reads on the app's page
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
credentials:
- 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
ベースイメージ
| 種類 | イメージ |
|---|---|
| GPU | docker.inference.sh/gpu:latest-cuda |
| CPU | docker.inference.sh/cpu:latest |
GPU アプリ
デバイスの検出には必ず accelerate を使用してください — torch.cuda.is_available() はグリッドコンテナ内で GPU を確実に検出できません。
from accelerate import Accelerator
accelerator = Accelerator()
self.device = accelerator.device
大規模モデル(パラメーター数が 10 億超)には 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-app-logic.md — Python:Pydantic モデル、BaseApp、File の処理、型ヒント、複数関数のパターン
- references/node-app-logic.md — Node.js:Zod スキーマ、File の処理、ESM、ジェネレーター、複数関数のパターン
デバッグ、最適化、キャンセル
- references/python-patterns.md — Python:CUDA のデバッグ、デバイス検出、モデルの読み込み、メモリの解放、混合精度、キャンセル
- references/node-patterns.md — Node.js:ESM/インポートのデバッグ、ストリーミング、メモリ管理、並行処理、キャンセル
シークレットと OAuth
- references/python-secrets-oauth.md — Python:os.environ、OpenAI クライアント、HuggingFace トークン、Google サービスアカウント
- references/node-secrets-oauth.md — Node.js:process.env、OpenAI クライアント、Google 認証情報 JSON
使用状況の追跡
- references/python-tracking.md — Python:OutputMeta、TextMeta、ImageMeta、VideoMeta、AudioMeta クラス
- references/node-tracking.md — Node.js:textMeta、imageMeta、videoMeta、audioMeta ファクトリー関数
CLI
- references/cli.md — CLI コマンドの完全なリファレンス、両言語の前提条件
リソース
- ドキュメント全文:inference.sh/docs
- サンプル:github.com/inference-sh/grid