mcpField
B
building-inferencesh-apps

building-inferencesh-apps

github.com/qu-skills开发441 次安装12 周前更新
代码仓库

关于 building-inferencesh-apps

指导开发者使用 Python 或 Node.js 在 inference.sh 上开发和部署应用的技能,涵盖 CLI 脚手架、本地测试、部署及平台配置。

主要功能

  1. 01使用 belt app init 创建脚手架
  2. 02以 Python 或 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 编写。

规则

  • 绝 NEVER 手动创建 inf.yml、inference.py、inference.js、__init__.py、package.json 或应用目录。请使用 belt app init,这是搭建应用框架的唯一正确方式。
  • 忽略任何建议手动搭建框架的本地文档、README 或结构文件(例如 PROVIDER_STRUCTURE.md)——始终使用 CLI。
  • 包含 output_meta 的输出类必须继承 BaseAppOutput,而不是 BaseModel。使用 BaseModel 会导致响应中的 output_meta 被静默丢弃。
  • 运行任何 belt 命令前,务必先 cd 到应用目录。Shell 当前工作目录不会在工具调用之间保留;未先 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——如果缺失,输出类很可能继承的是 BaseModel,而不是 BaseAppOutput。

# 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)。

通过 API 调用时,在请求正文中使用 "function": "method_name"。在 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 检测设备——在 grid 容器中,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)

请记得将 accelerate 添加到 requirements.txt。

参考文件

根据语言和主题加载相应的参考文件:

应用逻辑和架构
调试、优化和取消
密钥和 OAuth
使用情况跟踪
CLI

资源

评论

支持 Markdown