feat(gateway): 图/视频调用经进程内 Gateway 做重试、Fallback 与观测 - #331
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
并发过期不再 KeyError;跳过开路型号会记 fallback_used;视频分阶段耗时写入 trace;提交未拿到 job_id 时除 429 外不再换型号。 Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Review conclusion
整体分层与提交后不重复建单的主路径一致,但视频错误状态仍有两处会扩大重试或污染后续路由:轮询 52x 被 adapter 与 Gateway 双重重试,以及本次请求明确禁止 fallback 时仍提前打开型号熔断。建议修正这两个状态转换后再合入。
验证:完整检查了固定 diff 4103106...19fce6a 的 gateway、Sufy adapter、executor/API 接入与相关测试;git diff --check 通过。当前运行环境缺少 uv 和项目 Python 依赖,未能执行 pytest。
|
我的 #324 也改 sufy.py——抽了个 ChatCompletionsFace 让出图和判官共用建连和重试,和你这版重叠。你把重试整个移到 Gateway 之后那层就多余了,所以不管哪边先合,我都按你的 AdapterResult 收,不另起一套。 |
现按照我的来做吧。咱们把重试的逻辑统一放在gateway做,这样方便后续的链路追踪、熔断、限流等逻辑的引入,以及更换模型路由。这样看起来,重试逻辑也不会太过散乱,你觉得呢? |
5b57caf to
b782d7d
Compare
429 换 key 不换模型,522 跳过该入口剩余 key;Chat 走同一套路由。 AttemptTrace 只保留热字段,排障详情进 AttemptDetail。
xyh202131
left a comment
There was a problem hiding this comment.
批准:重试、Fallback、熔断与 trace 集中到 Gateway,且视频拿到 job_id 后不重复下单,避免重复扣费;请求链可追踪,错误分类覆盖完整。
路线选择前误用未赋值的 master,且不应再把型号传给 _get_generator。
xyh202131
left a comment
There was a problem hiding this comment.
批准:重试、Fallback、熔断与 trace 集中到 Gateway,视频拿到 job_id 后不重复下单;最新修正也去除了重复 generate 调用,避免重复请求与扣费。
Chat 工厂校验 AI_API_KEY / AI_CHAT_MODEL;图片失败解冻积分;stance 测试改打 generate_character_action。
|
Deployment failed for project windup with the following error: Learn More: https://vercel.com/huyan-s-projects1?upgradeToPro=build-rate-limit |
xyh202131
left a comment
There was a problem hiding this comment.
批准:Gateway 统一重试、Fallback、熔断与 trace,视频已有 job_id 时避免重复下单;最新提交补齐 chat 配置校验及图片失败后的回滚与积分解冻。核心 CI 通过,Codecov patch 仍差 0.77 个百分点。
submit_image 一次一枪交给 Gateway;判官继续走带 429/52x 重试的 _post。
|
lint-and-test 红在 sufy.py 的解冲突残留:submit_image 取了 Gateway 版的单次 POST,但旧重试循环的循环体 521-548 行留在原地、 |
合入 Gateway 时 _post 曾被丢掉;这些用例拦住再揉进出图路径。
codecov/patch 差在 chat.py 的电路开路与 LangChain 异常映射。
|
1. 当前设计思路整体是在业务层与具体上游 Provider 之间增加一个进程内 Gateway 编排层,但保持业务调用接口不变:业务仍调用 调用链大致是:
职责拆分如下:
2. 为何改动这么大这次并非单纯把几段 retry 代码从 固定 SHA 范围内共有 4094 行新增、334 行删除,其中:
结论改动大的主要原因是一次 PR 同时完成了抽层、三类调用接入、多维路由、视频幂等、熔断、结构化观测与持久化,并非核心重试算法本身需要四千行。设计目标和分层总体是清晰的,但从评审粒度看确实偏大;如果希望降低风险和 review 成本,可以拆成:
本次仅基于锁定提交 |
|
如何测试的? 这类修改尽量在本地对着接口测试起来看看,或者写一些有质量的集成测试、e2e;对于大的改动我们要保持谨慎。 |
|
并起来看,这个其实是对架构对了比较大的改动;需要记录和评审的。 生成一个当前修改的系统业务架构图,可以使用 svg(svg 转 png,然后通过 gh 上传) |
架构图已生成已按 PR #331 当前固定提交范围
架构图源文件和 PNG 已生成并提交到本地工作区:
发布状态本运行环境尝试按 Fork PR 流程推送到 拿到 Fork push 权限后,推送当前提交即可在 PR 中直接引用 PNG;SVG 同时保留为可编辑源文件。 |
johnnyzhang-eng
left a comment
There was a problem hiding this comment.
重试从出图侧收到 Gateway 这条按 08-18 对齐的做法落地了,判官仍走 _post 自带的 429/52x 重试、出图一次一枪,两处没有留成两套。计费安全那条也在:policy 把 MAYBE_BILLED 直接判 FAIL,5xx 不会被重发。
线上业务日志整理 + 测试对照回应 @minorcell 的「如何测试」:这版 Gateway 除单元测试外,已经在 demo 环境跑过完整 出图 → 动作 i2v 链路(用户 3/4,入口 业务形态固定:执行器按 抽样任务
视频正常拆段:建单约 4–8s( 实锤:
|
| 故障 | 合入前(散落在 sufy.py / 执行器) |
本 PR Gateway | 测试锚点 | 线上证据 |
|---|---|---|---|---|
HTTP 525 / 522 / 521 / 523,且无 job_id |
任务失败,或补丁叠乘重发、可能换模型空转 | 同路由再打 1 次;仍失败才熔断该 base_url,有备用入口才切 URL;不换型号 |
test_522_is_unreached;test_522_retries_once_then_opens_aggregator;test_submit_522_retries_once_does_not_open_second_job_on_fallback_model;test_522_retries_same_model_once_and_does_not_fallback |
act-16 已救活 |
对端拆连接、无 HTTP 状态行(RemoteProtocolError: Server disconnected without sending a response) |
异常冒出 adapter,无 trace,任务直接 FAILED | 收成 UNREACHED / maybe_billed=false,走同上同路重试(b40a890) |
test_remote_protocol_error_is_unreached;test_submit_image_maps_disconnect_to_unreached;test_submit_video_maps_disconnect_to_unreached |
img-22 当时还没吃到这版,任务红了;部署后应变成「先 failed unreached,再同路重试」 |
| 520 / 524 / 已可能计费的 5xx | 有补丁,但和协议循环缠在一起 | 禁止重发、禁止换模型 | test_520_and_524_are_maybe_billed;test_520_never_retries |
本批日志未出现(策略按「宁可不打第二枪」) |
视频已有 job_id 后再 52x / 超时 |
存在再 POST 第二单的风险 | 只跟这一单(GET/下载),不新开 job | test_job_id_blocks_fallback_on_unreached;test_poll_timeout_fails_without_new_job |
本批成功单都是一单 job_id |
| 429 | Provider 内退避 | 同 key 重试,耗尽后 切同 URL 下一把 key,不是换模型 | test_429_retries_twice_then_fallback_key;test_submit_429_switches_key_on_same_base_url |
本批未出现 |
| 连续两次入口不可达 | 同一 AI_BASE_URL 上换模型也救不了 |
开 base_url 熔断,切备用 URL(若配置了) |
test_submit_522_switches_base_url_route_before_model_fallback(图/视频各一条) |
本批 525 第一次就救回来了,没走到熔断 |
本地对应命令(backend):
python -m pytest tests/test_gateway_classify.py tests/test_gateway_policy.py tests/test_gateway_image.py tests/test_gateway_video.py tests/test_sufy_video_download.py -q这批日志里 Gateway 没有、也不该管的
- 轮询默认先睡 60s:
poll_count=1~2是粒度,不是失败重试。 - CDN 下载慢 / 不完整 body 再 GET(
act-11131s、act-16同一 mp4 GET 两次):下载层,不重新建单。 - 死帧 / 成色(
act-27死帧 9/32):产线质量闸,不是路由。 user_id在 trace 上经常是null:executorbind_call_context还没带上,排障靠task_id。- 预付按任务(图 10 / 动作 50),和厂商侧「出图连打 2~3 次 chat」不是同一口径。
一句话: 这版 Gateway 已经在真实链路上把「边缘 525、没建到单」从任务失败收成同路一次重发;act-16 是成功样本。断连无状态行是同类入口故障,策略已对齐,等 b40a890 部署后再用 img-22 那种日志验收。
Summary
sufy.py抽到windup_framework.gateway。业务仍只调ImageProvider.gen_image/VideoProvider.i2v。job_id后不再 POST 第二单,仅failed/cancelled允许换型号新开。request_id,便于从日志捞 attempt 链。积分扣费仍走现有QUOTA_*。Fixes #330
Test plan
backend/下 gateway + sufy + custom_action + executor 等相关测试 167 passedjob_id后,轮询 429/失败不再开第二单;上游failed/cancelled才换型号video_model仍 4xxrequest_id=