Joye Dev

Back

refactor(task-worker): call gpt-image-2 via the AI Gateway OpenAI route, not fal

**PR:**refactor(task-worker): call gpt-image-2 via the AI Gateway OpenAI route, not fal (#4314)


正文来源:飞书学习文档。以下为通过个人 Feishu API 获取并转换后的完整 Markdown 正文。

今日选择#

**PR:**refactor(task-worker): call gpt-image-2 via the AI Gateway OpenAI route, not fal (#4314)

**作者:**Horcrux / magicismight

**Merge 时间:**2026-07-04T04:25:15Z

链接:https://github.com/adastralab-ai/voyager/pull/4314

**模块:**backend/workers/task, backend/go/internal/image

**学习标签:**task-worker, image-generation, cloudflare-ai-gateway, vendor-boundary, safety-error, multipart-order, selftest

为什么值得学#

  • 它把 gpt-image-2 从 fal 代理迁到 Cloudflare AI Gateway 的 provider-native OpenAI route,减少一个供应商中转,同时没有把 OpenAI key 下发给 worker,凭证边界更清楚。
  • 它没有只做 endpoint 替换,而是把返回形态从 fal URL 改成 OpenAI inline base64,顺手删除结果 CDN 二次拉取,让 executor 的失败阶段更贴近真实责任边界。
  • 它把内容安全拒绝映射到既有的 SafetyError / image_safety 语义,避免 moderation_blocked 被当成可重试 model_error。
  • 它为 multipart edit 路径保留 referenceImages 顺序:并行 fetch 可以保留,但 append 顺序必须由输入数组决定,因为首图在 edit 端点里有主图语义。

关键代码#

Go 侧只传 provider-native model id#

case api.GptImage2:
    return "gpt-image-2"
plaintext

**位置:**backend/go/internal/image/ops_generate.go:36-44。这里把旧的 openai/gpt-image-2 改成 gpt-image-2,说明 provider 路由选择被下沉到 worker vendor 层;Go 服务只负责公共模型枚举到 provider 模型名的契约,不再泄露 fal 的命名空间。

Executor 不再拉 fal CDN URL#

const result = await runStage("model_error", () =>
  vendors.openai.generateGptImage({
    model: payload.model,
    prompt: payload.prompt,
    imageUrls: payload.referenceImages.map((ref) => ref.url),
    imageSize,
  }),
);
return result.buffer;
plaintext

**位置:**backend/workers/task/src/executor/ops/generate.ts:77-85。旧路径是 vendors.fal.generateGptImage 返回 imageUrl,再用 fetchImageBytes 拉结果;新路径直接拿 buffer。这减少了一段外部依赖,也让 source_fetch_failed 不再混进模型结果下载失败。

OpenAI vendor 把网关认证、错误分类和返回解码集中起来#

headers: { "cf-aig-authorization": authHeader, ...init.headers },
...
if (isContentModerationError(body)) {
  throw new SafetyError(`OpenAI gpt-image-2 refused: ${body}`);
}
...
return Buffer.from(b64, "base64");
plaintext

**位置:**backend/workers/task/src/vendors/openai.ts:63-83。这个 vendor 把 Cloudflare AI Gateway 的认证头、OpenAI error body 解析、base64 解码放在同一外部边界里,executor 只看到业务语义:成功是图片 bytes,安全拒绝是 SafetyError,其他失败是普通 Error。

Reference image 并行拉取,但按输入顺序 append#

const blobs = await Promise.all(options.imageUrls!.map(async (imageUrl) => {
  const res = await fetch(imageUrl);
  if (!res.ok) throw new Error(`reference image fetch failed: ${res.status}`);
  return res.blob();
}));
blobs.forEach((blob, i) => {
  form.append("image[]", blob, `reference-${i}.${ext}`);
});
plaintext

**位置:**backend/workers/task/src/vendors/openai.ts:101-113。这里的取舍是并发和确定性都要:fetch 可以并行,但 multipart 字段顺序必须回到原数组顺序,避免哪个 URL 先返回就变成主图。

Selftest 覆盖错误语义,而不是只测 happy path#

stubStatus(400, JSON.stringify({ error: { code: "moderation_blocked" } }));
check("openai vendor: moderation_blocked → SafetyError", (await capture()) instanceof SafetyError);

stubStatus(400, JSON.stringify({ error: { code: "invalid_value" } }));
check("openai vendor: non-moderation 400 → plain Error (retryable)", other instanceof Error && !(other instanceof SafetyError));
plaintext

**位置:**backend/workers/task/scripts/selftest.ts:488-513。测试没有 mock 掉内部 vendor 逻辑,只 mock 外部 fetch 边界,因此覆盖的是迁移最容易出错的错误分类契约。

和最近学习记录的关系#

和昨天记录的 PR #4124(sheet cell editor 对齐)没有直接模块关系。和 2026-07-02 记录的 PR #4156 有间接关系:#4156 学的是后台 worker 回调 API 的 workspace-scoped token,今天 #4314 学的是 task worker 调模型的出站供应商边界。两者都在收紧 worker runtime 的能力边界:一个是回调 API 的授权范围,一个是模型供应商凭证和错误语义的封装。

同日还有 #4316 移除 per-user image concurrency gate、#4307 改进 agent image placement,都是 image/agent/task 方向的主线演进;我选 #4314 是因为它的边界设计、错误语义和测试覆盖更集中。

我会怎么吸收#

  • 供应商迁移不要只替换 SDK 调用;要同时审查凭证边界、返回形态、失败分类、重试语义和测试入口。
  • 跨服务 payload 里尽量传稳定的领域模型标识,把 provider route / vendor namespace 关在 runtime adapter 里。
  • 并发获取外部资源时,如果下游 API 对顺序有语义,必须显式恢复输入顺序再提交。

边界 / 风险#

未看到明显架构风险,但有两个边界值得盯:第一,live selftest 依赖 dev 凭证和真实网络,本次我没有运行,只阅读了 PR 的 test plan 和代码;第二,GitHub 上有一个同主题的 #4340,但它 merge 到 cy-gpt-image-ref-fetch-stage 分支,不是 origin/main,因此不计入今天主线候选。这提示 gpt-image reference fetch/selftest 方向仍在快速修补,后续如果相关修复进入 main,值得继续追。

候选说明#

按 Melbourne 2026-07-04 对应窗口(2026-07-03T14:00:00Z 到 2026-07-04T14:00:00Z)查到 10 个 merged PR,其中进入 origin/main 的高价值候选包括 #4314、#4316、#4307、#4334、#4304、#4211 等;#4340 被排除,因为 base 不是 main。昨天窗口查到 18 个 merged PR,但今天已有合适未讲过候选,所以没有回退选择昨天。去重方面,学习日志里已有 #4156 和 #4124,本次没有重复选择。

🗂️ 这是知识库中的🔬 研究。

内容可能仍在补充或修订中。

← Back