feat: 视频生成接口新增 Seedance(火山 ARK)原生请求/响应兼容入口 - #5737
Conversation
Add a dedicated /seedance/api/v3/contents/generations prefix so clients using the Seedance / VolcEngine ARK video SDK can switch base_url to new-api without changing request/response shapes, mirroring the existing Kling and Jimeng native routes. - router: register native submit/fetch/list task routes - middleware: SeedanceRequestConvert marks official API and rewrites path - controller: RelaySeedanceTask / RelaySeedanceTaskFetch handlers - relay: seedance_task fetch-by-id and list with upstream-shaped response - doubao adaptor: official-API branch preserving native content payload - tests for middleware, doubao adaptor and relay response shaping Closes QuantumNous#5735 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WalkthroughAdds a Seedance official API route group, request-conversion middleware, controller handlers, and relay logic for task submission and retrieval. The Doubao adaptor now branches on the official API context, and tests cover middleware, adaptor, and task helpers. ChangesSeedance official API compatibility
Sequence Diagram(s)sequenceDiagram
participant Client
participant SeedanceRequestConvert
participant RelaySeedanceTask
participant RelayTask
participant RelaySeedanceTaskFetch
participant SeedanceTaskFetch
participant TaskAdaptor
Client->>SeedanceRequestConvert: POST /seedance/api/v3/contents/generations/tasks
SeedanceRequestConvert->>RelaySeedanceTask: c.Next() after setting KeySeedanceOfficialAPI, rewriting POST path, and setting relay_mode
RelaySeedanceTask->>RelayTask: RelayTask(c)
RelayTask->>TaskAdaptor: BuildRequestBody / DoResponse
Client->>SeedanceRequestConvert: GET /seedance/api/v3/contents/generations/tasks or /tasks/:task_id
SeedanceRequestConvert->>RelaySeedanceTaskFetch: c.Next() after setting KeySeedanceOfficialAPI
RelaySeedanceTaskFetch->>SeedanceTaskFetch: fetch task or list payload
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
relay/seedance_task.go (1)
44-106: 🚀 Performance & Scalability | 🔵 TrivialUnbounded in-memory load before pagination.
seedanceFetchTaskListfetches every matching task for the user (7-day window, noLIMIT) and then filters and paginates entirely in memory. The same pattern appears inseedanceGetTaskByID(Lines 114-128), which loads all candidate tasks to match an upstream ID. For users with high task volume this scales poorly in memory and DB transfer, and frequent polling amplifies it.If pure status/model/service_tier filtering dominates, consider pushing those into the query (and an index on
(user_id, platform, submit_time)), keeping only thefilter.*JSON-derived predicates in memory.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@relay/seedance_task.go` around lines 44 - 106, The task list retrieval in seedanceFetchTaskList is loading all matching tasks into memory before applying pagination, causing unbounded DB transfer and memory use. Push the simple filters that can be expressed in SQL (user_id, platform, submit_time, and any direct status/model/service_tier conditions) into the GORM query, then apply offset/limit there instead of slicing the full result set in Go. Keep only the JSON-derived predicates from seedanceTaskIDFilters and any helper-based matching in memory, and apply the same approach to seedanceGetTaskByID where it scans candidates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@relay/channel/task/doubao/adaptor.go`:
- Around line 204-227: In BuildRequestBody’s Seedance official API branch,
info.UpstreamModelName is only used when already populated, so it can stay empty
even though the request body contains the model. Before rewriting bodyMap,
explicitly read bodyMap["model"] and assign it to info.UpstreamModelName when it
is currently unset, then continue with the existing override/Marshal flow so
billing attribution always has the upstream model name.
---
Nitpick comments:
In `@relay/seedance_task.go`:
- Around line 44-106: The task list retrieval in seedanceFetchTaskList is
loading all matching tasks into memory before applying pagination, causing
unbounded DB transfer and memory use. Push the simple filters that can be
expressed in SQL (user_id, platform, submit_time, and any direct
status/model/service_tier conditions) into the GORM query, then apply
offset/limit there instead of slicing the full result set in Go. Keep only the
JSON-derived predicates from seedanceTaskIDFilters and any helper-based matching
in memory, and apply the same approach to seedanceGetTaskByID where it scans
candidates.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3d30af99-be54-49c5-9478-7a7021582d66
📒 Files selected for processing (9)
common/gin.gocontroller/seedance.gomiddleware/seedance_adapter.gomiddleware/seedance_adapter_test.gorelay/channel/task/doubao/adaptor.gorelay/channel/task/doubao/adaptor_test.gorelay/relay_task_seedance_test.gorelay/seedance_task.gorouter/video-router.go
| if c.GetBool(common.KeySeedanceOfficialAPI) { | ||
| storage, err := common.GetBodyStorage(c) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| cachedBody, err := storage.Bytes() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| var bodyMap map[string]interface{} | ||
| if err := common.Unmarshal(cachedBody, &bodyMap); err != nil { | ||
| return bytes.NewReader(cachedBody), nil | ||
| } | ||
| if info.UpstreamModelName != "" { | ||
| bodyMap["model"] = info.UpstreamModelName | ||
| } | ||
| data, err := common.Marshal(bodyMap) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return bytes.NewReader(data), nil | ||
| } | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find where UpstreamModelName / OriginModelName are populated in the task relay flow
rg -nP '\b(UpstreamModelName|OriginModelName)\s*=' --type=go -g '!**/*_test.go' relay/
# Inspect how TaskSubmitReq.Model feeds into RelayInfo
rg -nP -C3 '\bTaskSubmitReq\b' --type=go relay/common/Repository: QuantumNous/new-api
Length of output: 7991
🏁 Script executed:
sed -n '119,145p' relay/channel/task/doubao/adaptor.goRepository: QuantumNous/new-api
Length of output: 1116
🏁 Script executed:
sed -n '204,230p' relay/channel/task/doubao/adaptor.goRepository: QuantumNous/new-api
Length of output: 799
Ensure info.UpstreamModelName is populated for Seedance billing attribution.
In the Seedance branch (BuildRequestBody), info.UpstreamModelName is only used to override the request model if it has already been set (e.g., by model mapping). However, if no mapping occurred, info.UpstreamModelName remains empty despite the model being present in the request body.
While the code correctly omits the override in this case, relying solely on implicit behavior risks missing the upstream model name for billing and usage tracking if it hasn't been populated elsewhere. It is safer to explicitly extract and assign the model from the body to info if it is currently unset.
Consider adding the following logic before modifying bodyMap:
if info.UpstreamModelName == "" {
if m, ok := bodyMap["model"].(string); ok && m != "" {
info.UpstreamModelName = m
}
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@relay/channel/task/doubao/adaptor.go` around lines 204 - 227, In
BuildRequestBody’s Seedance official API branch, info.UpstreamModelName is only
used when already populated, so it can stay empty even though the request body
contains the model. Before rewriting bodyMap, explicitly read bodyMap["model"]
and assign it to info.UpstreamModelName when it is currently unset, then
continue with the existing override/Marshal flow so billing attribution always
has the upstream model name.
|
@QuentinHsu |
|
官方赶紧合并啊 |
|
这个没有大佬审核下吗 |
从上游 QuantumNous/new-api PR QuantumNous#5737 (@ChenYichener) 应用。为 seedance 新增官方火山 ARK 原生入口 /seedance/api/v3/contents/generations(对齐 kling/jimeng 原生路由模式),原本直连 火山 ARK SDK 的用户仅换 base_url 即可接入。 - router:注册提交/按 ID 查询/列表查询路由(TokenAuth + Distribute)。 - middleware SeedanceRequestConvert:标记官方 API 上下文并把 POST 改写到 /v1/video/generations。 - controller/relay:seedance_task.go 按 id/列表查询(含分页与 filter.*,按 user_id 隔离)。 - doubao 适配器:新增官方 API 分支(校验 model/content、透传 content、回包 task_id), 并补 expired/cancelled 终态映射。 - Closes upstream QuantumNous#5735。 Review:构建/测试通过;JSON 走 common.*、GORM 跨库、查询按 user_id 隔离、路由带鉴权; 计费复用 doubao 现有链路(未新增计费代码)。小提示:列表查询在内存里过滤+分页(7 天窗口内有界)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR QuantumNous#5737 的列表查询原本把用户近 7 天全部任务读进内存再过滤+分页;重度视频用户 7 天 可能累积上千条,每次列表请求都全量载入并反序列化 Data JSON,浪费明显。 - 无过滤条件时走快路径:DB 级 COUNT + LIMIT/OFFSET,不再全量载入。 - model/service_tier 存于 task.Data JSON、跨库无法可靠下推,故带这些(或 status/ task_ids)过滤时仍回退内存路径,保证过滤+分页语义正确。 - 抽出 seedanceTaskBaseQuery(Count/Find 各取独立查询)与 seedanceMarshalTaskList, 顺带 dedup seedanceGetTaskByID 的相同基础查询。 - 新增 TestSeedanceFetchTaskListPagination:用内存 SQLite 同时护住两条路径的 用户/时间窗/平台过滤、id 倒序、分页与 total。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AIAI 视频渠道适配器 + Seedance 火山原生入口(上游 PR QuantumNous#5737)+ 任务计费倍率日志展示 + seedance 列表 DB 级分页。与 main 的 supplier/detector 修复文件集不重叠,无冲突。
|
请关注下这个PR |
|
合并进去了吗? |
Important
📝 变更描述 / Description
为视频生成接口新增一套 Seedance(火山引擎 ARK)原生请求/响应兼容入口,路径前缀为
/seedance/api/v3/contents/generations,与官方 ARK 视频接口对齐。设计动机:项目此前已通过
relay/channel/task/doubao适配器支持 Seedance 模型,但缺少面向客户端的原生入口;原本直接调用 Seedance/火山 ARK 视频 SDK 的用户无法像可灵(/kling)、即梦(/jimeng)用户那样仅靠切换base_url就接入。本 PR 补齐这一兼容层,请求与响应两端都保持 Seedance 原生形态,做到零代码改动、仅替换 base_url 即可迁移。实现要点(沿用现有 Kling/Jimeng 原生路由模式):
router/video-router.go):注册原生任务的提交、按 ID 查询、列表查询路由。middleware/seedance_adapter.go):SeedanceRequestConvert()标记官方 API 上下文并将 POST 路径改写到内部统一的/v1/video/generations。controller/seedance.go):RelaySeedanceTask/RelaySeedanceTaskFetch处理器。relay/seedance_task.go):按 ID / 列表查询任务,并以上游原生响应结构回包(含分页与filter.*过滤)。relay/channel/task/doubao/adaptor.go):新增官方 API 分支,透传原生content负载、回包上游task_id,并补充expired/cancelled状态映射。common/gin.go):新增KeySeedanceOfficialAPI上下文键。遵循项目规范:JSON 统一走
common.*;DB 查询使用 GORM 方法、兼容 SQLite/MySQL/PostgreSQL;未引入数据库特定特性。🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
📸 运行证明 / Proof of Work
本地验证(Go 1.26.4):
go vet改动涉及包无新增问题;改动包均编译通过。🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests