多媒体 Provider
Spark Work 多媒体 Provider 配置:图片生成 / 编辑、语音合成 / 转写、视频生成 / 图生视频 / 视频编辑的统一能力注册表、平台适配器(APIMart / xAI / 火山 / 百炼 / 可灵 / Hailuo 等)。
Spark Work 的多媒体能力用「能力注册表 + 平台适配器 + Media Model Manifest」三层实现: Provider Profile 声明它支持哪些能力,适配器负责平台协议,manifest 负责单个模型的参数与 产物提取。图片、语音、视频共用同一套代码路径,画布、spark_mediaMCP 与 Provider 表单读的是同一份元数据。
1. 能力枚举与适配器
统一能力 id 共11 个(不是 9 个),定义在 packages/protocol/src/media-config.ts的MEDIA_CAPABILITY_IDS。 括号里是 Provider 表单上的中文标签(providerMediaConfig.ts的 MEDIA_CAPABILITY_LABELS)。
| 能力 id | 界面标签 | 说明 |
|---|---|---|
image.generate | 生图 | 文生图 |
image.edit | 图生图 / 图片编辑 | 含多图合成 |
image.variations | 图片变体 | 目前只有 Midjourney manifest 声明 |
audio.speech | 语音合成 | TTS |
audio.music | 音乐生成 | Lyria / MiniMax 音乐 / 火山豆包音频生成 |
audio.transcription | 语音转写 | ASR,如 APIMart Whisper |
video.generate | 文生视频 | — |
video.image_to_video | 图生视频 | 首帧驱动 |
video.reference_to_video | 参考图生视频 | 参考图(非首帧)驱动 |
video.edit | 视频编辑 | 按 prompt 改已有视频 |
video.extend | 视频扩展 | 从末帧续拍 |
1.1 平台适配器枚举
mediaProvider的合法值共18 个(MEDIA_PROVIDER_KINDS)。 Provider 表单里的「平台适配器」下拉不是全量枚举,而是按模型类型裁剪: 生图 / 视频模型给 9 项,其余给 15 项。
| mediaProvider | 表单标签 | 协议实现 |
|---|---|---|
apimart | APIMart | 专用适配器ApimartMediaAdapter |
agnes | Agnes AI | 专用适配器AgnesMediaAdapter |
xai | xAI | 专用适配器XaiMediaAdapter |
openai-images | OpenAI 多媒体 | 专用适配器OpenAiOfficialMediaAdapter |
openai-compatible | OpenAI Compatible | 走 manifest 驱动的模板适配器 |
google-generative-ai | Google Gemini / Veo / Lyria | 专用适配器(含:predict/:predictLongRunning/ /interactions) |
omni | Omni | 复用 Google 适配器,走 Omni 专用端点 |
bailian | 阿里百炼 | 专用适配器BailianMediaAdapter |
volcengine-ark | 火山方舟 / Seedance | 专用适配器(content[]嵌套请求体) |
volcengine-speech | 火山豆包语音 | 专用适配器(X-Api-Key鉴权,区别于方舟的 Bearer) |
kling | Kling | 6 个内置 manifest,走模板适配器 |
minimax-hailuo | MiniMax Hailuo | 专用适配器(v1 恒 200 +base_resp、V2 OAI 协议) |
midjourney | Midjourney 网关 | 专用适配器MidjourneyMediaAdapter |
tencent-tokenhub | 腾讯云 TokenHub | 专用适配器(查询是 POST + body) |
custom | 自定义 | 模板适配器 + 自定义 manifest |
pixverse/wan/happyhorse | PixVerse / Wan / HappyHorse | 枚举里有值、有中文标签,但不在表单下拉里,也没有独立 manifest 种子;对应模型挂在 APIMart 下(如pixverse-v6、happyhorse-1.0/1.1、wan2.7-image) |
适配器注册是显式的:MediaRouterService构造函数里register()了 12 个实例(含 Google 适配器注册两次,分别对应google-generative-ai与 omni)。路由时先看有没有专用适配器且它声明支持该能力,否则改用 manifest 驱动的模板适配器;两者都没有时抛 provider_not_configured: No adapter for provider kind ...。
openai-compatible没有注册为独立适配器(openai-compatible-media.adapter.ts是其他适配器的基类),所以这一类渠道必须绑定 manifest 才能调用。
2. Media Model Manifest
Manifest 是「模型元数据单一来源」:类型与 zod schema 在 packages/protocol/src/media-model-manifest.ts,内置种子共167 条,覆盖 13 个providerKind。
| providerKind | 内置 manifest 数 | 种子来源 |
|---|---|---|
apimart | 59 | media-model-manifest.ts内的 APIMart 模型表 |
bailian | 22 | bailian-media-model-manifests.ts |
tencent-tokenhub | 21 | tencent-tokenhub-media-model-manifests.ts |
google-generative-ai | 13 | google-media-model-manifests.ts |
openai-images | 12 | openai-media-model-manifests.ts |
volcengine-ark | 12 | volcengine-ark-media-model-manifests.ts |
minimax-hailuo | 9 | media-model-manifest.ts+ 共享片段 |
xai | 6 | xai-media-model-manifests.ts(视频 1.5)+ 主文件 |
kling | 6 | media-model-manifest.ts |
agnes | 3 | media-model-manifest.ts |
volcengine-speech | 2 | volcengine-speech-media-model-manifests.ts |
omni/midjourney | 各 1 | omni-media-model-manifests.ts/media-model-manifest.ts |
一条 manifest 的关键字段:
id/providerKind/modelId/displayName/domains(image / audio / video / text …)。capabilities[]:每条含id、label、input.required、input.maxImages、output.types、paramSchema、defaults、aliases。invocation:mode(sync/async_polling/async_callback/stream/file_job)、endpoint、method、contentType(json / multipart / binary)、requestTemplate、response、polling。docs.sourceUrls+lastCheckedAt:记录参数是照着哪份官方文档核对的。safety:maxPromptLength、allowLocalFiles、maxInputBytes。
Contract V2 增补了contractVersion: 2、adapterMode(native/template)与baseTemplate;后者在 Provider 表单的「配置自定义适配器」里有 4 个基底可选:完全自定义、OpenAI 接口协议基底、通用异步 JSON 任务(GET 轮询)、ToApis 图片全能力异步轮询。
持久化用两张表:media_model_manifests(manifest 本体)与 media_provider_models(某个 Provider 启用了哪些 manifest、本地默认参数覆盖), 见 packages/storage/migrations/033_media_model_manifests.sql。启动时由 MediaModelCatalogService.seedBuiltinManifests()把内置种子写回 SQLite。
3. 配置步骤(真实界面)
- 打开设置 → Provider,点「新建」(或「新增配置」)打开编辑面板。
- 模型类型选 4 项之一:对话模型/生图模型 /语音模型/视频模型。对话模型属于附加能力场景,需要额外打开附加生成能力开关才会出现多媒体面板。
- 选模板预设(如 APIMart、xAI、火山方舟),会预填 Base URL、默认模型、能力与轮询参数。
- 在多媒体面板里确认三件事:
- 平台适配器:图片 / 视频模型从 9 个里选,其余从 15 个里选。
- 支持能力:勾选本 Provider 真实支持的能力,别多勾——路由和调用方式都按它判断。
- 调用方式:
sync/async/auto。
- 模型清单里从内置目录勾选要启用的 manifest;自定义渠道可以手输模型 ID 并按 Enter 添加。
- 参数默认值填 size / 比例 / 质量 / 时长等;异步渠道再填 轮询间隔 ms与接口超时 ms。
- 填 API Key 保存。表单下方会给出「实际请求地址」预览,确认适配器拼出的 URL 与渠道文档一致。
注意:「测试连接」不是媒体链路验证。它发的是文本协议 ping (OpenAI 兼容走 /chat/completions,Anthropic 走 messages),纯图片 / 视频渠道 常常没有 chat 端点,因此测试失败不代表媒体不可用;反过来测试通过也不代表生图能跑通。请以 「实际请求地址」+ 一次真实生成为准。
4. 内置预设清单
当前共 64 个 Provider 预设,其中35 个带多媒体配置。下表为真实 id 与默认模型 (预设定义在packages/protocol/src/provider-presets.ts)。
| 预设 id | 名称 | 类型 | mediaProvider / 调用方式 | 默认模型 |
|---|---|---|---|---|
openai-images | OpenAI | image | openai-images/ auto | gpt-image-2 |
openai-sora-video | OpenAI | video | openai-images/ async | sora-2 |
agnes-ai | Agnes AI | multimodal | agnes/ auto | agnes-2.0-flash |
apimart-images | APIMart | image | apimart/ auto | gpt-image-2 |
apimart-audio-whisper | APIMart Whisper | voice | apimart/ sync | whisper-1 |
apimart-audio-tts | APIMart TTS | voice | apimart/ sync | tts-1 |
apimart-video-veo3 | APIMart VEO 3 | video | apimart/ async | veo3.1-quality |
apimart-video-sora2 | APIMart Sora 2 | video | apimart/ async | sora-2 |
apimart-video-collection | APIMart 综合 | video | apimart/ async | kling-v3 |
xai-imagine-image | xAI | image | xai/ sync | grok-imagine-image-quality |
xai-imagine-video | xAI | video | xai/ async | grok-imagine-video |
xai-tts | xAI | voice | xai/ sync | grok-tts |
bailian-images | 阿里云百炼 | image | bailian/ async | wan2.7-image-pro |
bailian-video-happyhorse | 阿里云百炼 HappyHorse | video | bailian/ async | happyhorse-1.1-t2v |
bailian-video-wan-i2v | 阿里云百炼 Wan | video | bailian/ async | wan2.7-t2v-2026-06-12 |
bailian-audio-tts | 阿里云百炼 | voice | bailian/ sync | qwen3-tts-flash |
volcengine-seedance-video | 火山方舟 | video | volcengine-ark/ async | doubao-seedance-2-5-260628 |
volcengine-seedream-image | 火山方舟 | image | volcengine-ark/ sync | doubao-seedream-5-0-pro-260628 |
volcengine-speech-tts | 火山豆包语音合成 | voice | volcengine-speech/ sync | seed-tts-2.0 |
volcengine-speech-audio | 火山豆包音频生成 | voice | volcengine-speech/ sync | seed-audio-1.0 |
google-gemini-images | Google Gemini | image | google-generative-ai/ sync | gemini-3.1-flash-image |
google-veo-video | Google Gemini Veo | video | google-generative-ai/ async | veo-3.1-generate-preview |
google-omni-video | Google Gemini Omni Flash | video | omni/ async | gemini-omni-flash-preview |
google-gemini-omni-video | Gemini Omni Flash 官方 | video | google-generative-ai/ async | gemini-omni-flash-preview |
google-lyria-music | Google Gemini Lyria | voice | google-generative-ai/ sync | lyria-3-clip-preview |
midjourney-gateway | Midjourney 网关 | image | midjourney/ async | midjourney |
kling-video | Kling 可灵视频 | video | kling/ async | kling-video-3.0 |
minimax-image | MiniMax 图片 | image | minimax-hailuo/ sync | image-01 |
minimax-hailuo-speech | MiniMax 语音 | voice | minimax-hailuo/ sync | speech-2.8-hd |
minimax-hailuo-music | MiniMax 音乐 | voice | minimax-hailuo/ sync | music-2.6 |
minimax-hailuo-video | MiniMax Hailuo 视频 | video | minimax-hailuo/ async | MiniMax-Hailuo-2.3 |
minimax-h3-video | MiniMax H3 视频 | video | minimax-hailuo/ async | MiniMax-H3 |
minimax-video-agent | MiniMax 视频 Agent | video | minimax-hailuo/ async | video-agent |
tencent-tokenhub-image | 腾讯云 | image | tencent-tokenhub/ auto | hy-image-v3.0 |
tencent-tokenhub-video | 腾讯云 | video | tencent-tokenhub/ async | hy-video-1.5 |
预设里的默认 Base URL(可直接对照渠道文档核对):
APIMart https://api.apimart.ai/v1
xAI https://api.x.ai/v1
OpenAI https://api.openai.com/v1
Agnes AI https://apihub.agnes-ai.com/v1
阿里百炼 https://dashscope.aliyuncs.com/api/v1/services/aigc
火山方舟 https://ark.cn-beijing.volces.com/api/v3
火山豆包语音 https://openspeech.bytedance.com(X-Api-Key,非方舟 Bearer)
Google Gemini https://generativelanguage.googleapis.com/v1beta
Kling https://api.klingai.com
MiniMax https://api.minimaxi.com
腾讯云 TokenHub https://tokenhub.tencentmaas.com
Midjourney 网关 https://your-midjourney-gateway.example/v1(占位,必须改成自建网关)
5. 默认值、超时与重试
| 项 | 默认值 | 可配置范围 |
|---|---|---|
| 视频异步任务总时限 | 48 小时(DEFAULT_VIDEO_POLL_TIMEOUT_MS) | mediaDefaults.timeoutMs/polling.timeoutMs:1000 ~ 172800000 |
| 轮询间隔 | 5000 ms | polling.intervalMs:250 ~ 300000 |
| 单次轮询 HTTP 超时 | 30 s | 不单独配置,且不会超过任务剩余总时限 |
| 轮询间隔退避 | 每次 ×1.3,上限 15 s | 代码内固定 |
| 轮询请求的瞬时错误重试 | 最多 3 次,退避 1 s 起、每次 ×2、上限 8 s | manifest 的polling.retry(maxAttempts 0 |
重试只发生在异步轮询阶段:只有「网络层错误 / HTTP 5xx / 429」会重试,并会识别 响应里的Retry-After(秒数或时间戳,上限 8 s);4xx 参数错误、 task_failed、task_timeout不重试。一次性提交请求(create/submit) 本身不自动重试,失败会直接把错误交给调用方。
历史配置的超时会被迁移脚本抬高到当前口径:055_video_poll_timeout_30m.sql、056_image_poll_timeout_10m.sql、067_video_poll_timeout_48h.sql。 迁移只替换「仍等于旧默认值」的记录,你手填过的值不会被覆盖。
6. 产物、任务与错误码
- 产物根目录是当前 workspace 下的
.spark-artifacts/media,按类型分成images/audio/videos/text(语音转写落 text)。落盘前会把.spark-artifacts写进仓库本地忽略,避免被连带提交。 - 每次调用都会被持久化成
media_generation_tasks(表在029_media_generation_tasks.sql,恢复所需字段由066_media_task_recovery.sql补充)。运行时有 submit / inquire / cancel / materialize 四段接口。 media-task-recovery.service.ts只按持久化的查询契约去问渠道「这个任务怎么样了」,不会重新提交 create/submit,所以重启应用不会重复计费。- 画布的模型调用详情面板会显示 Provider / Manifest / 模型 / 状态 / 反向提示词 / Request ID 以及本次实际参数。
代码里会出现的错误码(MediaProviderError.code):
provider_not_configured:渠道没配好,或该mediaProvider既没有专用 适配器也没有匹配到 manifest。capability_not_supported:勾选的能力与实际调用不匹配。api_key_missing/auth_required:凭据缺失或渠道返回鉴权失败。invalid_input:参数被 manifest 校验拦下(常见于时长、分辨率越界)。provider_http_error:渠道返回非 2xx,附带statusCode。task_failed/task_timeout:异步任务失败或超过总时限。artifact_download_failed:任务成功但产物 URL 下载失败(签名过期、需要鉴权头)。
渠道错误响应还会被归一成unsupported_parameter/ invalid_parameter_value/missing_required_input/ auth_failed/quota_exceeded/rate_limited/ content_policy_blocked/bad_provider_response这类语义码,便于判断 该改参数还是该充值。
7. spark_media MCP
会话只要存在可用的媒体 Provider,就会注入内部 stdio MCP serverspark_media,共 14 个工具:
mcp__spark_media__list_models 列出已配置的模型与能力(可用 capability 过滤)
mcp__spark_media__describe_model 查看单个模型的参数 schema 与调用元数据
mcp__spark_media__generate_image 文生图 / 图生图
mcp__spark_media__edit_image 图片编辑 / 多图合成
mcp__spark_media__generate_audio 语音合成或音乐生成(取决于所选模型)
mcp__spark_media__transcribe_audio 语音转写
mcp__spark_media__generate_video 文生视频 / 图生视频 / 参考图生视频 / 视频编辑 / 扩展
mcp__spark_media__upload_file 上传文件到渠道文件平台
mcp__spark_media__get_file 查询渠道文件元数据
mcp__spark_media__list_files 列出渠道文件
mcp__spark_media__delete_file 删除渠道文件(需用户确认)
mcp__spark_media__list_tasks 列出渠道异步任务(百炼支持官方 24 小时查询窗口)
mcp__spark_media__get_task 查询任务(支持生成工具返回的 taskId 或百炼 task id)
mcp__spark_media__cancel_task 取消排队中/运行中的任务(百炼仅 PENDING 可远程取消)
文件四件套只在路由到volcengine-ark或bailian时暴露;其余渠道 工具列表里看不到它们。
- 参数:
model支持「selectionKey(推荐)/ manifest id / 渠道模型 id」三种写法,selectionKey形如<providerProfileId>/<manifestId>。常用参数(size/aspectRatio/resolution/durationSeconds/mode/seed/output_format/prompt_optimizer等)直接暴露,渠道私有字段走extraJson,且会被按 manifest 裁剪,多余字段不会发给渠道。 - 凭据隔离:运行时把 Provider 列表写进一个 0600 权限的临时配置文件(
SPARK_MEDIA_CONFIG_FILE),API Key 不进文件,而是通过SPARK_MEDIA_API_KEY_0、SPARK_MEDIA_API_KEY_1… 逐个注入子进程环境变量。 Agent 拿不到 Key,也没有工具能读出来。 - 多档切换:每个模型保留自己 Provider 的 Key / endpoint / 适配器;生成工具靠
model参数切换实际路由,同名模型必须用唯一selectionKey指定。 - 单 Provider 环境变量(
SPARK_MEDIA_PROVIDER/SPARK_MEDIA_MODEL/SPARK_MEDIA_API_TYPE/SPARK_MEDIA_BASE_URL/SPARK_MEDIA_OUTPUT_DIR)与SPARK_MEDIA_MANIFESTS_JSON/SPARK_MEDIA_PROVIDERS_JSON仍作为兼容回退保留。 - 图片的旧链路
spark_image只在「没有可解析的统一媒体配置」时才注入,避免两套工具 争抢模型选择;详见图片生成 Provider。
8. 接入自定义渠道
两条路,先试第一条:
- UI 手工配置:Provider 表单里把「平台适配器」选成自定义, 会多出「自定义渠道适配器 → 配置自定义适配器」入口,用
ProviderManifestContractEditor从 4 个基底模板改起(提交 / 鉴权 / Body / 上传 / 轮询 / 参数 / 错误契约)。适合渠道是「JSON 提交 + 可选轮询 + URL/base64/binary 产物」。 - 让 Agent 配置:Agent 侧有 5 个平台工具——
providers_media_guide(取能力枚举与起始契约)、providers_media_validate(只读校验,不落库)、providers_media_configure(校验通过后写入正式 Provider,Key 进 Keychain)、providers_media_discover_models(打渠道/models)、providers_media_diagnose(分阶段诊断 401/404/400/轮询/产物解析)。诊断里的真实 生成调用需要用户显式确认。
只有在渠道需要自定义鉴权、multipart、回调、文件型 job 或特殊取消语义时,才需要写专用适配器: 新增 packages/agent-runtime/src/services/media/adapters/<vendor>-media.adapter.ts, 在MediaRouterService构造函数里register(),并补 mediaProvider枚举、表单标签与预设。给这个渠道写 manifest 的模型更适合走模板适配器。
9. 常见报错与排查
| 现象 | 原因与处理 |
|---|---|
会话里没有spark_media工具 | 检查 Provider 是否enabled、是否填了 Key、modelType是否为 image/voice/video 或声明了媒体能力、默认模型是否非空;任一不满足就不会注入。 |
No adapter for provider kind xxx | 这个mediaProvider没有专用适配器也没匹配到 manifest。改成模板能覆盖的渠道,或为该渠道补 manifest。 |
| Video 任务一直 pending 到超时 | 确认polling.intervalMs与timeoutMs;默认 48 小时。渠道返回的 status 若不在 manifest 的statusMap里,默认按失败处理——除非把 unknownStatus设为running,此时视为继续等待。 |
| 任务成功但产物下载失败 | 产物 URL 可能带签名时效或需要鉴权头。artifact_download_failed 表示任务成功、下载失败,重试「查询」而不是重新提交。 |
| 参数被静默丢弃 | manifest 的paramPolicy会裁剪当前模型不支持的字段;describe_model能看到真实 schema。 |
| 「测试连接」失败但生成正常 | 测试连接 ping 的是文本端点,纯媒体渠道本来就没有。忽略它,用一次真实生成验证。 |
速查表
| 项目 | 内容 |
|---|---|
| 统一能力 id | 11 个:image.generate/edit/variations · audio.speech/music/transcription · video.generate/image_to_video/reference_to_video/edit/extend |
| mediaProvider 枚举 | 18 个:apimart/agnes/xai/openai-compatible/openai-images/google-generative-ai/bailian/volcengine-ark/volcengine-speech/kling/pixverse/minimax-hailuo/wan/happyhorse/omni/midjourney/tencent-tokenhub/custom |
| mediaApiType | sync / async / auto |
| 内置 manifest | 167 条 / 13 个 providerKind(表:media_model_manifests + media_provider_models,migration 033) |
| 多媒体预设 | 35 条(Provider 预设共 64 条) |
| 视频轮询默认 | 间隔 5s、退避 ×1.3 上限 15s、总时限 48h、瞬时错误最多重试 3 次(1s→8s) |
| 产物目录 | <workspace>/.spark-artifacts/media/{images,audio,videos,text} |
| MCP | spark_media(14 个工具;文件四件套仅 volcengine-ark / bailian) |
上手清单:在 Spark Work 中接入 APIMart 多媒体服务
从新建 Provider 到让 Agent 生成第一张图
- 打开「设置 → Provider」,点「新建」
- 「模型类型」选「生图模型」,预设选 APIMart
- 确认「平台适配器」= APIMart、「调用方式」= auto、「支持能力」勾选「生图」与「图生图 / 图片编辑」
- 在「模型清单」里勾选要用的 manifest(如 gpt-image-2),并设为默认调用模型
- 填 API Key 并保存,检查下方「实际请求地址」是否为 https://api.apimart.ai/v1/images/generations
- 新会话里让 Agent 生图,它会调用 mcp__spark_media__generate_image,产物落到 .spark-artifacts/media/images
常见问题
统一能力一共几个?我只看到 9 个的旧说法。
11 个。除 image.generate / image.edit / image.variations / audio.speech / audio.transcription / video.generate / video.image_to_video / video.edit / video.extend 之外,还有 audio.music(音乐生成)和 video.reference_to_video(参考图生视频)。
mediaProvider 和 imageProvider 是什么关系?
mediaProvider 是统一字段。保存 modelType=image 的 Provider 时,Spark 会按 imageProvider 推导并回写 mediaProvider / mediaApiType / mediaCapabilities,其中 openai→openai-compatible、seeddance→volcengine-ark、gemini→google-generative-ai,其余未知值兜底为 custom。
pixverse / wan / happyhorse 能选吗?
不能直接选。它们是 mediaProvider 枚举里的值,但 Provider 表单的适配器下拉不含这三项,也没有对应 manifest 种子;这些模型在 APIMart 渠道下以模型 id 的形式提供(如 pixverse-v6、happyhorse-1.1、wan2.7-image)。
API Key 存在哪里?Agent 能看到吗?
Key 通过 keystore 存储(macOS 上是单一 Keychain 条目 spark-agent / credential-vault-v1,Provider 只持有 keystore_ref)。调用时只注入到本地 MCP 子进程的环境变量,配置文件里不含 Key,Agent 与模型都看不到明文。
应用重启后异步任务会重新提交吗?
不会。任务持久化在 media_generation_tasks,恢复逻辑只按已保存的查询契约去渠道查询状态,从不调用 create/submit 端点。
内置预设一共有多少条?
64 条 Provider 预设,其中 35 条带多媒体配置(mediaProvider 非空)。