统一模型运行时设计
1. 目标
统一管理文本、图片、视频、语音、音色和音乐模型,同时保留供应商及模型的特有能力。模型层不以“统一成相同参数”为目标,而以以下能力为目标:
- 统一模型注册、启停、版本和租户凭证;
- 统一生成意图与任务生命周期;
- 显式描述每个模型支持什么、不支持什么;
- 在请求发出前完成素材、参数和组合合法性校验;
- 允许用户指定供应商和模型,也支持按策略自动路由;
- 统一记录成本、质量、延迟、错误和可追溯快照。
1.1 首期供应商范围
首期只接入两个供应商:
- 火山引擎:承载豆包、Seedance 等图像、视频和音频模型;
- 阿里云百炼:承载通义万相、Qwen-Image、CosyVoice 等图像、视频和音频模型。
原型、配置中心、模型注册表和路由策略均不得把其他供应商展示为已接入。后续如需扩展,必须新增供应商适配器并完成能力矩阵、成本、错误映射和回归验证。
2. 核心对象
type ProviderType = 'volcengine' | 'bailian';
type ModelModality =
| 'text'
| 'image'
| 'video'
| 'speech'
| 'voice'
| 'music';
type GenerationMode =
| 'text-to-image'
| 'image-to-image'
| 'text-to-video'
| 'first-frame-to-video'
| 'first-last-frame-to-video'
| 'reference-to-video'
| 'audio-driven-video'
| 'video-extension'
| 'video-edit'
| 'text-to-speech'
| 'voice-clone'
| 'text-to-music';ModelDefinition
interface ModelDefinition {
id: string;
provider: ProviderType;
providerModelId: string;
displayName: string;
version?: string;
modality: ModelModality;
enabled: boolean;
capabilities: ModelCapabilities;
pricing: PricingDefinition;
adapterKey: string;
}ModelCapabilities
interface ReferenceConstraint {
minCount: number;
maxCount: number;
mediaTypes: string[];
maxFileSize?: number;
durationRange?: [number, number];
dimensionRange?: {
minWidth?: number;
maxWidth?: number;
minHeight?: number;
maxHeight?: number;
};
}
interface ModelCapabilities {
modes: GenerationMode[];
references: {
firstFrame?: ReferenceConstraint;
lastFrame?: ReferenceConstraint;
images?: ReferenceConstraint;
videos?: ReferenceConstraint;
audios?: ReferenceConstraint;
masks?: ReferenceConstraint;
};
output: {
resolutions: string[];
aspectRatios: string[];
durationRange?: [number, number];
durations?: number[];
fps?: number[];
supportsAudio?: boolean;
supportsSeed?: boolean;
supportsWatermarkControl?: boolean;
};
compatibilityRules: CompatibilityRule[];
providerOptionsSchema?: Record<string, unknown>;
}MediaReference
interface MediaReference {
assetId: string;
role:
| 'first-frame'
| 'last-frame'
| 'character'
| 'scene'
| 'prop'
| 'style'
| 'pose'
| 'motion'
| 'camera'
| 'video'
| 'driving-audio'
| 'voice'
| 'music';
subjectId?: string;
priority?: number;
weight?: number;
timeRange?: {
start: number;
end: number;
};
}GenerationRequest
interface GenerationRequest {
tenantId: string;
projectId: string;
mode: GenerationMode;
modelId?: string;
routingPolicyId?: string;
promptSource: {
type: 'raw' | 'shot-spec' | 'music-brief' | 'speech-script';
id?: string;
text?: string;
};
references: MediaReference[];
output: {
aspectRatio?: string;
resolution?: string;
duration?: number;
fps?: number;
audio?: boolean;
};
options: {
seed?: number;
count?: number;
negativePrompt?: string;
};
providerOptions?: Record<string, unknown>;
}3. 兼容规则
能力矩阵必须支持表达“单项支持,但组合不支持”。例如:
type CompatibilityRule =
| {
type: 'mutually-exclusive';
groups: string[][];
}
| {
type: 'requires';
when: Record<string, unknown>;
require: Record<string, unknown>;
}
| {
type: 'forbids';
when: Record<string, unknown>;
forbid: Record<string, unknown>;
};示例:
- 首尾帧模式不能和多模态参考模式混用;
- 开启原生音频时只能使用特定模型版本;
- 选择首帧后,输出比例由首帧决定;
- 某模型 1080P 仅允许特定时长;
- 某模型参考图最多 9 张、参考音频最多 1 段。
4. Provider Adapter
interface ModelProviderAdapter {
validate(request: GenerationRequest): Promise<ValidationResult>;
estimate(request: GenerationRequest): Promise<CostEstimate>;
submit(request: ResolvedGenerationRequest): Promise<ProviderTask>;
getStatus(task: ProviderTask): Promise<ProviderTaskStatus>;
cancel?(task: ProviderTask): Promise<void>;
normalizeResult(result: unknown): Promise<GenerationResult>;
normalizeError(error: unknown): ProviderError;
}建议包结构:
packages/model-runtime/
├── contracts/
├── capability-registry/
├── validation/
├── routing/
├── pricing/
├── lifecycle/
├── prompt-compiler/
└── adapters/
packages/provider-volcengine/
├── ark/
├── jimeng/
├── speech/
└── music/
packages/provider-bailian/
├── image/
├── video/
├── speech/
└── music/5. Prompt Compiler
不直接把用户文本发送给模型。编译输入包括:
ShotSpec
+ 已审核角色资产描述
+ 已审核场景资产描述
+ 道具和服装状态
+ 项目风格规范
+ 镜头语言
+ 连续性要求
+ 模型专属提示词规范
+ 模型专属负面词输出包括:
- 平台标准提示词;
- Provider 专属提示词;
- 参考素材及顺序;
- 被裁剪或降级的能力说明;
- 警告与阻断项。
如果模型无法表达关键约束,默认阻断并提示选择更合适的模型,不能静默丢弃参考素材。
6. 模型路由
路由输入:
- 生成模式和必需参考类型;
- 质量档位;
- 最大预算;
- 最大等待时间;
- 目标比例、时长和分辨率;
- 租户允许使用的供应商;
- 模型实时成功率、排队和限流状态;
- 历史同类镜头质量评分。
路由结果:
interface RoutingDecision {
modelId: string;
reason: string[];
estimatedCost: number;
estimatedDuration: number;
degradedCapabilities: string[];
}MVP 可以先支持手工选模型和静态优先级。积累数据后再加入质量/成本自动路由。
7. 生成任务生命周期
DRAFT
→ VALIDATING
→ QUEUED
→ SUBMITTED
→ RUNNING
→ DOWNLOADING
→ VERIFYING
→ SUCCEEDED / FAILED / CANCELLED
→ REVIEW_PENDING
→ APPROVED / REJECTEDGeneration 记录必须保存:
- 标准请求快照;
- 最终模型和版本;
- 编译后的供应商请求;
- 参考素材版本与 OSS 对象版本;
- 供应商 taskId/requestId;
- 全部状态变化与时间;
- 输出文件、媒体探测信息和缩略图;
- 成本、质量评分和审核结果;
- 原始供应商错误与平台归一化错误。
8. 统一错误分类
AUTHENTICATION_ERROR
PERMISSION_ERROR
QUOTA_EXCEEDED
RATE_LIMITED
INVALID_PARAMETER
UNSUPPORTED_CAPABILITY
INVALID_REFERENCE
CONTENT_REJECTED
PROVIDER_UNAVAILABLE
PROVIDER_TIMEOUT
TASK_FAILED
DOWNLOAD_FAILED
MEDIA_INVALID
CANCELLED
UNKNOWN前端展示平台错误,同时为管理员保留供应商 requestId 和原始错误。
9. OSS 媒体策略
对象 Key 建议:
/{tenantId}/{workspaceId}/{projectId}/
source/
assets/{assetType}/{assetId}/{version}/
generations/{generationId}/input/
generations/{generationId}/output/
proxies/
renders/{renderId}/
deliveries/{deliveryId}/规则:
- 供应商调用使用时效覆盖任务周期的签名 URL;
- 输出先下载到平台 OSS,再写入业务状态;
- 不把供应商临时 URL 当永久产物;
- 保存 ETag、大小、MIME、分辨率、时长和校验值;
- 原始素材、代理素材、中间产物和成片使用不同生命周期策略。
10. 前端模型选择器
交互顺序:
选择生成类型
→ 选择自动路由或指定供应商
→ 选择模型
→ 根据能力动态生成表单
→ 添加带语义角色的参考素材
→ 实时校验
→ 展示预计成本与耗时
→ 提交生成界面必须明确提示:
- 当前模型支持哪些参考方式;
- 哪些素材会被使用;
- 哪些能力不能组合;
- 是否会自动调整比例、分辨率或时长;
- 是否产生原生音频;
- 是否使用企业自己的模型额度。
11. 员工模型凭证与执行身份
公司为每位员工分别签发火山引擎和阿里云百炼凭证;同一员工可以同时绑定两家。叙镜只负责凭证绑定、可用性验证、任务执行身份和实际用量归属。供应商侧独立控制可用额度与限流,叙镜不设置内部额度上限,不做额度占位、任务拦截或备用账号切换。
11.1 核心对象
interface EmployeeModelCredential {
id: string;
employeeId: string;
providerId: 'volcengine' | 'aliyun_bailian';
region: string;
credentialRef: string; // 只存密钥系统引用
maskedKey: string; // 仅用于界面展示,不可逆推出密钥
credentialStatus: 'valid' | 'missing' | 'invalid' | 'disabled';
authorizedModelIds: string[];
lastVerifiedAt?: string;
version: number;
}
interface ProjectExecutionIdentity {
id: string;
projectId: string;
volcengineEmployeeId: string;
bailianEmployeeId: string;
version: number;
}
interface ModelUsageLedger {
id: string;
employeeId: string;
credentialId: string;
providerId: 'volcengine' | 'aliyun_bailian';
generationId: string;
providerTaskId?: string;
requestCount: number;
videoSeconds: number;
imageCount: number;
audioMinutes: number;
actualCost: number;
createdAt: string;
}ModelUsageLedger 只记录供应商返回或账单核对后的实际请求、视频秒数、图像张数、音频分钟和费用,用于观察、财务核对与员工归属;它不是限额计数器,也不参与任务提交判断。
11.2 执行身份解析与锁定
用户手工发起任务时,Server 解析发起人在目标供应商下的 EmployeeModelCredential。自动化或定时任务使用项目 ProjectExecutionIdentity 为该供应商指定的员工执行账号。两类任务不得互相借用身份。
生成任务创建时必须把最终 credentialRef、credentialId 和 employeeId 固化进请求快照;Worker 只能读取锁定引用,不能重新选择员工或凭证。凭证缺失、失效或停用时,任务直接进入需用户处理状态;Conductor 不对同一凭证盲目重试。
11.3 供应商错误与明确不实现项
本系统明确不实现 ModelQuota、QuotaReservation、RoutingPolicy 及其内部限额、占位、阻断、优先级和备用账号语义。供应商返回的额度耗尽继续归一化为 QUOTA_EXCEEDED,限流归一化为 RATE_LIMITED;这只是在任务错误模型中表达供应商结果,不代表叙镜维护或执行内部额度策略。响应保留 providerTaskId / requestId、是否建议稍后重试、用户处理建议和追踪 ID。
11.4 密钥安全
明文密钥只能加密存储到 KMS、Vault 或 Secret Manager。Nacos 仅允许保存 credentialRef 与供应商、地域、状态、授权模型等非敏感元数据,不允许保存 Access Key、Secret Key 或 API Key 明文。业务日志、错误日志、链路追踪和审计记录均不得输出 Key;界面和通知只能使用不可逆掩码。
11.5 审计要求
以下操作写入不可变审计记录:员工凭证绑定、更新、验证、启停、授权模型变化、项目执行账号变化,以及任务的执行身份解析与 credentialRef 锁定。记录包含 employeeId、credentialId、Provider、操作者、变更前后非敏感元数据、generationId / providerTaskId、原因、时间和追踪 ID;任何情况下都不记录明文或可恢复的密钥内容。