跳到正文
DEV Community· chubiao huang (MR HUANG)·· 3 小時前AI 評分53

Kivide 分享多模型 AI 影片工作室的工程設計,涵蓋 19 個圖像及影片模型

Building a Multi-Model AI Video Studio: Notes from Kivide

AI 導讀

作者分享 Kivide 將 19 個圖像及影片模型整合至同一工作室的工程設計,聚焦模型能力描述、點數計費與提示詞層。系統以模型規格資料驅動介面、定價和生成端點,按模型及方案計算成本;Pro 年繳用戶支付標準價格的 60%,生成前預扣點數,失敗時退款並以生成 ID 防止重複退款。提示詞示例強調鏡頭角度、動作動詞和物理細節,並將內容拆分為主體、動作、鏡頭、氛圍及品質欄位。

正文

Building a Multi-Model AI Video Studio: Notes from Kivide

What it actually takes to put 19 image/video models behind one interface — routing, credit accounting, and the prompt layer nobody talks about.

Try it: kivide.com · Prompt library: kivide.com/prompts


Kivide cinematic video output

Every few weeks a new video model drops. Seedance 2.5, Kling 2.5 Turbo, Wan 2.7, MiniMax H3, Veo, Sora. Each one is better at something different — one nails motion, another nails faces, a third is cheap enough to use for iteration.

If you're building a product on top of them, you hit the same wall fast: users don't want to choose a model. They want a result.

This post is about how Kivide is structured around that problem. It's not a launch announcement — it's the engineering notes: what the model layer looks like, how credits are accounted for across 19 models, and why the prompt layer turned out to be the hardest part.


The core problem: models are not interchangeable

The naive design is "one generate endpoint, a model parameter, done." That breaks immediately, because models disagree on almost everything:

Dimension Why it breaks a generic interface
Input modality Some are text-to-video only, some accept image-to-video, some do both
Duration 5s / 10s / variable — and pricing often scales per second
Resolution 480p / 1080p / 2K / 4K, each with a different cost
Aspect ratio 16:9, 9:16, 1:1 — and composition prompts don't transfer between them
Latency 20 seconds to several minutes
Failure modes Moderation rejection, queue timeout, provider rate limit, silent partial output

So the model layer can't be a thin proxy. It has to be a capability descriptor that the rest of the system reads from.

What that looks like

Each model is described by data, not by branching code:

type ModelSpec = {
  id: string
  kind: 'video' | 'image'
  inputs: ('text' | 'image')[]
  durations?: number[]        // video only
  resolutions?: string[]      // video only
  ratios: string[]
  // Credit cost per generation, per plan tier
  cost: {
    regular: number
    starterYearly: number
    proYearly: number
    unlimited: number | 'Unlimited'
  }
}

The UI, the pricing table, and the generation endpoint all read from this. Adding a model means adding a row — not touching the routing logic.

Here's what that actually looks like in production. These are live numbers from kivide.com/api/pricing:

Model Type Credits (regular) Credits (Pro yearly)
Seedream 5.0 Lite image 30 18
GPT Image 2 image 30 18
Nano Banana Pro 1K image 30 18
Nano Banana 2 image 40 24
Seedream 5 Pro image 50 30
Nano Banana 2 4K image 70 42
MiniMax H3 480p video 80 48
MiniMax H3 Max 480p video 100 60
Omni Flash 10s video 140 84
MiniMax H3 1080p video 150 90
Wan 2.7 video 200 120
MiniMax H3 Max 1080p video 220 132
Seedance 2.5 video 250 150

The spread is 8x between the cheapest image model and the most expensive video model. That spread is the whole reason the credit system has to be exact.


Credits: why not just charge per generation

The obvious model is "one generation = one credit." It falls apart because a 4K image and a 10-second 1080p video cost the provider wildly different amounts.

So credits are a normalized unit of provider cost, not a unit of user action. A few consequences:

1. Cost has to be decided before the request is sent.
You can't tell the user "this cost 150 credits" after the fact. The model spec is resolved first, the cost is computed, and only then is the request made.

2. Balance has to be reserved, then reconciled.
Two concurrent requests against the same balance can both pass the check and both spend. The pattern that works:

1. Read balance
2. Check balance >= cost          (fail fast, no charge)
3. Deduct cost immediately        (reserve)
4. Call the provider
5. On success  → keep the deduction
   On failure  → refund the exact amount

Step 3 before step 4 is deliberate. Deducting first means a crash mid-request can't leave the user with free generations — it leaves them with a refund owed, which is the safer failure direction and is recoverable from logs.

3. Refunds have to be idempotent.
Provider webhooks retry. If a refund is keyed only on "this generation failed," a retried webhook refunds twice. Every refund needs a deterministic key derived from the generation id.

4. Plan tier changes the price, not the model.
Pro-yearly users pay 60% of the regular rate for the same model. That's a pricing decision, not a routing one — so the cost table is keyed by (model, tier), and the tier is resolved from the subscription at request time.


The prompt layer is the actual product

Here's the part that surprised me.

Once the model layer is abstracted, switching models is cheap. What's not cheap is that a prompt tuned for one model produces garbage on another. The same sentence that gives you a beautiful cinematic shot on Seedance gives you a flat, static image on a cheaper model.

Three things that consistently matter more than style adjectives:

Camera direction is the highest-leverage token

❌  "a beautiful cinematic shot of a motorcycle at night"
✅  "low-angle shot of a rider on a white sport motorcycle
     speeding down a highway at night. Red taillight streak."

The second one specifies a camera position (low-angle) and a physical artifact (red taillight streak, which only appears in a long exposure). The model has seen thousands of real photos matching that description, so it has something concrete to reproduce.

"Beautiful" and "cinematic" are not instructions. They're adjectives the model has learned to ignore because they appear in every prompt.

Motion verbs decide whether video looks like video

A text-to-video prompt with no motion verb produces a still image with a slow digital zoom. Always include one:

  • slow dolly forward
  • tracking shot following
  • handheld, slight shake
  • orbit around

Physical detail forces rendering

dust trails, steam rising, water droplets frozen mid-air, film grain, soft bokeh. These are rendering instructions, not mood words. They tell the model which optical phenomenon to simulate.


What this looks like end to end

A generation request goes through five stages:

1. Resolve model spec        →  inputs, ratios, cost
2. Check + reserve credits   →  fail fast if insufficient
3. Validate input            →  image dimensions, duration, ratio
4. Dispatch to provider      →  with a per-provider timeout
5. Reconcile                 →  keep charge on success, refund on failure

The interesting failures are all in stage 4 and 5:

  • Provider timeout — the job may still complete on their side. You need a sweeper that reconciles orphaned generations, or users get charged for nothing.
  • Moderation rejection — comes back as a provider error, but it's not your error. It shouldn't be refunded differently than a technical failure, but it should be logged differently.
  • Partial success — a 10-second video that comes back as 5 seconds. Treat output validation as a separate check from HTTP status.

A concrete example, start to finish

Let me walk through one prompt from the library to show what "specific" means in practice.

Goal: a cyberpunk night motorcycle shot.

First attempt (what most people write):

A cool cyberpunk motorcycle rider at night, neon lights, cinematic, 8K, highly detailed

What comes back: a static portrait of a motorcycle. Nice lighting. No sense of motion. Looks like a stock photo.

Rewritten using the five slots:

Low-angle shot of a rider on a white sport motorcycle speeding down a highway at night. Red taillight streak, cyberpunk night vibe, 8K.

Slot Content
Subject a rider on a white sport motorcycle
Action speeding down a highway
Camera low-angle shot
Lighting/mood cyberpunk night vibe
Quality 8K

The two words doing the most work are low-angle and taillight streak. The first sets the camera below the subject, which makes speed read. The second is a long-exposure artifact — it implies the camera is tracking the bike, which is what makes the image feel like a still from a video rather than a render.

Run it: kivide.com/prompts/cyberpunk-night-motorcycle-prompt


More output examples

Ghibli-style valley sunset

Surreal floral installation

Both of these came from prompts in the public prompt library — each one is a copy-paste-ready prompt with a one-click "use this" action that opens the studio pre-filled.


Things I'd do differently

Start with the cost table, not the UI. The model/cost matrix is the actual product constraint. Building the UI first means rebuilding it when you discover a 4K model costs 8x a lite one.

Make refunds a first-class flow from day one. Retrofitting idempotent refunds onto a system that already has users is painful.

Treat prompts as data, not strings. Once prompts live in a structured library (subject / motion / camera / mood / quality), you can A/B them, version them, and generate landing pages from them. Strings in a textarea can't do any of that.

Don't hide the model choice entirely. Users who care will find out anyway, and the ones who don't care are served fine by a good default. Hiding it completely creates support tickets.


Try it

If you're building something similar and want to compare notes on the credit accounting or the provider abstraction, the comments are open.


Disclosure: I build Kivide. The numbers in this post are read live from the production pricing API, not from memory.

來源:DEV Community · dev.to