架构概览
核心组件
| 组件 | 作用 | 说明 |
|---|
| generateVideo Tool | 任务创建 | 验证权限、NSFW 检测、选择 Provider |
| Agent onFinish | 消息发布 | 将 pending 任务发布到 NATS |
| NATS JetStream | 异步队列 | TASKS stream,subject tasks.video.* |
| kira-video-worker | 任务执行 | 扣费、调用 Provider API、后处理、上传 |
| Supabase Storage | 视频存储 | agent_message bucket |
| Centrifugo | 状态推送 | WebSocket 实时通知前端 |
视频生成服务商
| Provider | 模型 | 说明 |
|---|
| Seedance | seedance-1-5-pro (BytePlus) | NSFW 内容专用,支持 i2v,720p |
| Grok | grok-imagine-video (xAI) | 默认 Provider,支持多种比例 |
| Veo | veo-3.1-fast-generate-preview (Google) | 高质量,3x 成本(当前已禁用) |
Provider 选择逻辑
Provider 选择基于 NSFW 内容检测,而非用户订阅等级:
Veo Provider 因 Google 文件处理问题暂时禁用。启用后将作为 Max 用户的高级选项。
Provider 能力对比
| 能力 | Seedance | Grok | Veo |
|---|
| 文生视频 (t2v) | ✅ | ✅ | ✅ |
| 图生视频 (i2v) | ✅ | ✅ | ✅ |
| 支持比例 * | 1:1, 3:4, 4:3, 16:9, 9:16, 21:9 | 16:9, 4:3, 1:1, 9:16, 3:4, 3:2, 2:3 | 16:9, 9:16 |
| 分辨率 | 720p | 720p | 720p+ |
| 轮询方式 | 每 2 秒,最长 10 分钟 | Deferred Request (202/200) | 每 10 秒 Long-running Operation |
* 上表列出的是各 Provider 的原生支持比例。当前 generateVideo 工具 schema 仅允许 4 种比例:auto | 16:9 | 9:16 | 1:1。其他比例(如 21:9, 3:4 等)是 Provider 能力但工具层未暴露。
生成模式
| 模式 | 输入 | 说明 |
|---|
| 文生视频 (t2v) | prompt | 纯文本描述生成视频 |
| 图生视频 (i2v) | prompt + sourceImageId | 基于起始帧图片生成视频 |
参数配置
| 参数 | 可选值 | 默认值 |
|---|
| duration | "5", "10" | "5" |
| ratio | "auto", "16:9", "9:16", "1:1" | "auto" |
当 ratio 为 "auto" 且提供了 sourceImageId 时,Worker 会从源图片自动检测最接近的比例。
Credits 计费
| 时长 | 基础成本 (Grok/Seedance) | 高级成本 (Veo) |
|---|
| 5 秒 | 50 credits | 150 credits |
| 10 秒 | 100 credits | 300 credits |
计费流程
Credits 扣费使用 taskId 做幂等性保障。同一 taskId 不会重复扣费或重复退款。
任务生命周期
状态说明
| 状态 | 说明 |
|---|
pending | 任务已创建,等待 Worker 处理 |
processing | Worker 正在调用 Provider API |
completed | 视频生成成功,已上传存储 |
failed | 生成失败,已自动退款,errorMessage 记录原因 |
insufficient_credits | Credits 不足,用户充值后可重新提交 |
insufficient_plan | 方案不支持视频,用户升级后可重新提交 |
kira-video-worker 架构
并发控制
- 同一用户的视频任务串行处理(一次只处理一个)
- 不同用户的任务并发处理
- 最大并发数:100 个任务
NATS Consumer 配置
| 配置项 | 值 | 说明 |
|---|
| Stream | TASKS | JetStream 持久化流 |
| Subject | tasks.video.* | 过滤视频任务 |
| Consumer | video-worker | 持久化消费者 |
| Max Deliver | 3 | 最多重试 3 次 |
| Ack Wait | 5 分钟 | 处理超时时间 |
Worker 处理流程
接收任务
从 NATS JetStream 接收视频任务消息
扣费(Pre-deduction)
原子化扣除 Credits,使用 taskId 保证幂等。扣费失败 → NAK → NATS 自动重试(安全,无 Credits 损失)
调用 Provider
更新状态为 processing,通过 Centrifugo 通知前端,调用对应 Provider API 生成视频
后处理
下载视频 → ffprobe 提取尺寸 → ffmpeg 截取首帧缩略图 → 生成 blurhash
上传存储
视频和缩略图上传到 Supabase Storage,更新 JSONB 状态为 completed
通知前端
通过 Centrifugo WebSocket 推送完成状态和 Signed URL
内容审核
异步审核缩略图(Fire-and-forget)
错误处理策略
| 阶段 | 失败行为 | 说明 |
|---|
| 扣费前 | NAK → NATS 重试 | 安全,无 Credits 损失 |
| 扣费后 | 退款 + 标记 failed | Credits 原子退款,不再重试 |
| Provider 超时 | 退款 + 标记 failed | 超过轮询时限 |
数据模型
视频状态存储在 thread_version 表的 JSONB videos 数组中,无独立视频表:
interface Video {
// 创建时确定(不可变)
toolCallId?: string;
toolName?: string;
taskId: string; // UUID
provider: "seedance" | "grok" | "veo";
prompt?: string;
sourceImageId?: string;
duration: number; // 5 或 10
ratio: string; // "auto" | "16:9" | "9:16" | "1:1"
createdAt: string; // ISO 8601
// Worker 更新(可变)
status: "pending" | "processing" | "completed" | "failed"
| "insufficient_credits" | "insufficient_plan";
videoId?: string; // 存储路径
thumbId?: string; // 缩略图路径
width?: number;
height?: number;
errorMessage?: string;
}
异步处理流程
AI Tool 创建任务
generateVideo tool 检测 NSFW → 选择 Provider → 验证 Plan 和 Credits → 在 thread_version.videos 中插入记录
发布到 NATS
Agent onFinish 回调中,遍历 pending 状态的视频任务,逐个发布到 tasks.video.{taskId} subject
Worker 处理
kira-video-worker 消费消息,原子扣费,调用对应 Provider API,更新状态为 processing
后处理和上传
视频生成后下载 → 提取尺寸 → 生成缩略图 → 上传到 Supabase Storage → 更新状态为 completed
实时通知
通过 Centrifugo WebSocket 推送状态变更到前端(processing → completed/failed)
NATS 消息格式
interface VideoTaskMessage {
taskId: string;
userId: string;
threadId: string;
provider: "seedance" | "grok" | "veo";
prompt?: string;
duration: "5" | "10";
ratio: "auto" | "16:9" | "9:16" | "1:1";
sourceImageId?: string;
}
WebSocket 通知格式
// Centrifugo 频道: {userId}/{threadId}#{userId}
{
type: "video_status";
taskId: string;
video: {
taskId: string;
status: "processing" | "completed" | "failed";
videoId?: string; // completed 时存在
thumbId?: string; // completed 时存在
width?: number;
height?: number;
errorMessage?: string; // failed 时存在
url?: string; // completed 时 Signed URL
thumbUrl?: string; // completed 时 Signed URL
}
}
文件存储结构
agent_message/
├── {userId}/
│ └── {threadId}/
│ ├── video_{uuid}.mp4 # 原始视频
│ ├── vc_{uuid}.jpg # 视频缩略图
│ └── wv_{uuid}.mp4 # 带水印视频(按需生成)
└── feed/
└── {userId}/
├── video_{uuid}.mp4 # Feed 视频副本
└── vc_{uuid}.jpg # Feed 缩略图副本
水印处理
免费用户下载视频时自动添加水印,Pro+ 用户可下载无水印版本。
Feed 集成
视频作为一等内容类型发布到 Feed:
{
videoId: string; // Feed 存储路径
thumbId: string; // 缩略图路径
prompt?: string;
duration: number;
ratio: string;
width: number;
height: number;
blurhash?: string; // 缩略图 blurhash
}
- Feed 表
media_type 字段区分 "image" 和 "video"
- 视频和缩略图从 message 存储复制到 feed 存储
- 使用
origin_image_id 进行去重检查
- 发布前进行内容审核和向量相似度检查(LanceDB)
清理与删除
删除消息或回溯时,自动清理关联的视频文件:
- 从
thread_version.videos 提取 taskId 列表
- 获取对应的
videoId、thumbId 存储路径
- 从
agent_message bucket 中删除文件
- 清理 NATS 中未消费的任务(
purgeVideoTasks)
成本追踪
// PostHog 成本追踪
trackAICost({
type: "tool_video",
model: provider, // "seedance" | "grok" | "veo"
duration: videoDurationSeconds,
cost: pricePerSecond * videoDurationSeconds,
});
| Provider | 单价 |
|---|
| Seedance / Grok | $0.05/秒 |
| Veo | $0.15/秒 |