知识库首页 知识库-世界 2026-01-16-image-load-failed-retry-mechanism.md

2026 01 16 image load failed retry mechanism

本地来源:Knowledge/World/项目/Practice/BUG-SOLUTIONS/2026-01-16-image-load-failed-retry-mechanism.md

2026-01-16 Image Load Failed 自动重试机制问题报告


TL;DR 速查表

问题速览

# 问题 原因 解决方案
1 Image load failed 任务直接失败 FAL API 网络波动 自动重试 3 次,间隔 2 秒
2 用户输入网址而非图片 URL 用户误解 isLikelyImageUrl() 检测,跳过重试
3 轮询端点不发飞书通知 代码遗漏 /api/ai/query 加入通知逻辑
4 retryCount 信息丢失 FAL 响应覆盖 合并保留 retryCount 字段
5 多语言错误提示缺失 新增场景 10 个语言文件各加 3 条翻译

关键函数速查

// 判断错误是否可重试
isRetryableError(errorMessage)  // 检测 "image load failed" 等关键词

// 判断 URL 是否像图片
isLikelyImageUrl(url)           // 检测扩展名、CDN 域名、路径特征

// 获取重试次数
getRetryCount(taskInfo)         // 从 JSON 解析 retryCount

// 发送飞书错误通知
sendErrorNotification({...})    // 在重试耗尽或不可重试时调用

重试决策流程图

任务失败
  ↓
是可重试错误? ─否→ 直接失败 + 发通知
  ↓是
URL 像图片? ─否→ 跳过重试 + 发通知(标注用户输入错误)
  ↓是
重试次数 < 3? ─否→ 最终失败 + 发通知(标注已重试 N 次)
  ↓是
等待 2 秒 → 重新提交任务 → 继续轮询

修改文件清单

文件 改动
src/app/api/ai/query/route.ts +重试逻辑 +URL检测 +飞书通知
src/config/locale/messages/*/ai/image.json +3 条错误提示 ×10 语言

项目信息

  • 项目名称: longcat-video.org
  • 涉及模块: AI 图像/视频生成服务
  • AI 服务商: FAL API
  • 报告日期: 2026-01-16

问题一:Image load failed 错误无法自动恢复

具体问题

FAL API 在处理图生图/图生视频任务时,偶尔返回 "Image load failed" 错误,导致任务直接失败。

现象

  1. 用户提交图生图或图生视频任务
  2. FAL API 返回错误状态,错误信息为 "Image load failed"
  3. 任务被标记为 FAILED,用户积分被扣除但无产出
  4. 用户需要手动重新提交任务

本质

FAL API 服务端在下载用户提供的图片 URL 时,由于网络波动、CDN 延迟、或临时不可达等原因,导致图片加载失败。这是一个可恢复的临时性错误,而非永久性错误。

罪魁祸首

  1. FAL API 服务端网络不稳定 - 从用户图片 URL 下载图片时偶发失败
  2. 缺乏重试机制 - 原代码在收到失败状态后直接标记任务失败,没有尝试重试
  3. 错误处理过于简单 - 没有区分可恢复错误和不可恢复错误

解决办法

/api/ai/query/route.ts 中增加自动重试逻辑:

const MAX_RETRY_COUNT = 3;
const RETRY_DELAY_MS = 2000;

// 检测是否是可重试的错误
function isRetryableError(errorMessage?: string): boolean {
  if (!errorMessage) return false;
  const lower = errorMessage.toLowerCase();
  return (
    lower.includes('image load failed') ||
    lower.includes('failed to fetch') ||
    lower.includes('failed to download') ||
    lower.includes('url is not accessible') ||
    lower.includes('connection timeout') ||
    lower.includes('network error')
  );
}

// 重试逻辑
if (isFailed && canRetry) {
  await sleep(RETRY_DELAY_MS);
  const retryResult = await aiProvider.generate?.({ params: { ... } });
  if (retryResult?.taskId) {
    // 更新任务为新的 taskId,继续轮询
  }
}

关键代码修改

文件: src/app/api/ai/query/route.ts

  • 新增 isRetryableError() 函数判断错误是否可重试
  • 新增 getRetryCount() 函数从 taskInfo 获取已重试次数
  • 在检测到可重试错误时,等待 2 秒后重新调用 aiProvider.generate()
  • 重试成功后更新任务的 taskId,保留 retryCount 信息
  • 最多重试 3 次,超过后才标记为最终失败

问题二:用户输入网站地址而非图片 URL

具体问题

部分用户在图生图/图生视频功能中,输入的是网站首页地址(如 https://oleificioferreri.eu),而非实际的图片直链。

现象

  1. 用户输入 https://example.com 这样的网站地址
  2. FAL API 尝试下载该 URL,返回 "Image load failed"
  3. 系统误判为临时网络问题,触发自动重试
  4. 重试 3 次后仍然失败,浪费服务器资源

本质

用户不理解"图片 URL"的含义,误以为输入任意包含图片的网站地址即可。实际上需要输入图片的直接链接(如 https://example.com/image.jpg)。

罪魁祸首

  1. 前端校验不足 - 没有在提交前检测 URL 是否像图片
  2. 用户引导不清晰 - 提示文案没有明确说明需要直链
  3. 重试逻辑无差别对待 - 对明显错误的 URL 也进行重试

解决办法

增加 URL 有效性检测,跳过对明显非图片 URL 的重试:

function isLikelyImageUrl(url: string | undefined): boolean {
  if (!url) return false;
  const lower = url.toLowerCase();

  // 1. 检查图片扩展名
  const imageExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.avif', '.bmp', '.tiff'];
  if (imageExtensions.some((ext) => lower.includes(ext))) return true;

  // 2. 检查 CDN/存储域名
  const cdnDomains = ['r2.dev', 'cloudflare', 'amazonaws.com', 's3.', 'fal.media', ...];
  if (cdnDomains.some((domain) => lower.includes(domain))) return true;

  // 3. 检查 URL 路径特征
  if (lower.includes('/image') || lower.includes('/img') || lower.includes('/photo')) return true;

  // 4. 如果是网站首页,认为不是图片
  try {
    const parsed = new URL(url);
    if (parsed.pathname === '/' || parsed.pathname === '') return false;
  } catch {}

  return false;
}

判断逻辑

URL 示例 判断结果 原因
https://example.com/photo.jpg ✅ 是图片 包含 .jpg 扩展名
https://cdn.example.com/abc123 ✅ 是图片 CDN 域名
https://fal.media/files/xxx ✅ 是图片 fal.media 域名
https://example.com/ ❌ 不是图片 网站首页
https://oleificioferreri.eu ❌ 不是图片 网站首页,无图片特征

飞书通知增强

当检测到用户输入的 URL 不是图片时,在飞书错误通知中附加说明:

if (isRetryable && !imageUrlLooksValid && imageUrl) {
  errorSuffix = ` [用户输入的 URL 不是图片: ${imageUrl.slice(0, 60)}]`;
}

问题三:飞书错误通知只在 Webhook 端点发送

具体问题

原来的飞书错误通知只在 /api/ai/notify/[provider](Webhook 回调)中实现,但轮询端点 /api/ai/query 检测到失败时不发送通知。

现象

  1. 部分任务通过 Webhook 回调更新状态,错误会发送飞书通知
  2. 部分任务通过前端轮询 /api/ai/query 更新状态,错误不发送通知
  3. 运维无法及时发现通过轮询检测到的错误

本质

状态更新有两个入口(Webhook 和轮询),但错误通知只在 Webhook 入口实现,导致通知覆盖不全。

罪魁祸首

代码设计时只考虑了 Webhook 场景,忽略了轮询场景也需要发送通知。

解决办法

/api/ai/query/route.ts 中也加入飞书错误通知逻辑:

import { sendErrorNotification } from '@/extensions/notification';

// 检查是否需要发送错误通知
const shouldNotifyError =
  !isTerminalStatus(task.status) &&
  (result.taskStatus === AITaskStatus.FAILED || result.taskStatus === AITaskStatus.CANCELED) &&
  (!isRetryable || !imageUrlLooksValid || actualRetryCount >= MAX_RETRY_COUNT || (retryAttempted && !retrySucceeded));

if (shouldNotifyError) {
  await sendErrorNotification({
    email: user.email,
    name: user.name,
    apiEndpoint: errorDetails.apiEndpoint,
    apiProvider: task.provider,
    errorCode: errorDetails.errorCode,
    errorMessage: errorDetails.errorMessage + errorSuffix,
    prompt: task.prompt,
    type: buildTaskType(task),
    taskId: task.taskId || task.id,
  });
}

通知发送条件

只有满足以下条件才发送通知,避免重复告警:

  1. 任务从非终态变为 FAILED 或 CANCELED
  2. 且满足以下任一条件: - 不是可重试的错误(如 NSFW 被拦截) - 图片 URL 无效(用户输入错误) - 已达到最大重试次数(3 次) - 本次重试失败

问题四:重试次数信息丢失

具体问题

在重试失败后,retryCount 信息没有正确保存到 taskInfo 中。

现象

  1. 第一次重试失败后,retryCount 应该是 1
  2. 但查询 taskInfo 时发现 retryCount 丢失或为 0
  3. 导致通知中显示的重试次数不准确

本质

重试失败后,代码使用 result.taskInfo(来自 FAL API 的原始响应)构建 updateAITask,而 FAL API 的响应中不包含我们自定义的 retryCount 字段。

罪魁祸首

// 错误代码:直接使用 result.taskInfo,丢失了 retryCount
const updateAITask: UpdateAITask = {
  taskInfo: result.taskInfo ? JSON.stringify(result.taskInfo) : null,
  // ...
};

解决办法

在构建 updateAITask 前,合并保留 retryCount

// 正确代码:保留重试次数信息
const updatedTaskInfo = (() => {
  const base = isRecord(result.taskInfo) ? result.taskInfo : {};
  if (currentRetryCount > 0 || retryAttempted) {
    return {
      ...base,
      retryCount: retryAttempted ? currentRetryCount + 1 : currentRetryCount,
    };
  }
  return base;
})();

const updateAITask: UpdateAITask = {
  taskInfo: Object.keys(updatedTaskInfo).length > 0 ? JSON.stringify(updatedTaskInfo) : null,
  // ...
};

问题五:多语言错误提示缺失

具体问题

新增的错误场景(URL 无效、重试中、重试耗尽)没有对应的多语言翻译。

现象

前端显示英文错误提示,非英语用户体验差。

解决办法

在 10 个语言文件中添加 3 条新的错误消息:

新增字段: - error_invalid_image_url - 用户输入的不是图片链接 - error_retrying - 正在自动重试 ({current}/{max}) - error_retry_exhausted - 重试 {count} 次后仍失败

涉及文件: - src/config/locale/messages/en/ai/image.json - src/config/locale/messages/zh/ai/image.json - src/config/locale/messages/ar/ai/image.json - src/config/locale/messages/de/ai/image.json - src/config/locale/messages/es/ai/image.json - src/config/locale/messages/fr/ai/image.json - src/config/locale/messages/it/ai/image.json - src/config/locale/messages/ja/ai/image.json - src/config/locale/messages/ko/ai/image.json - src/config/locale/messages/pt/ai/image.json


查漏补缺

已确认不受影响的场景

场景 是否受影响 原因
视频 URL 被误判为图片 ❌ 不受影响 isLikelyImageUrl() 只检查 options.image_url,不检查 taskResult 中的视频输出
文生图任务 ❌ 不受影响 文生图没有 image_url 参数,isLikelyImageUrl() 返回 false,不触发重试
正常的图片 URL ✅ 正常工作 CDN 域名或带扩展名的 URL 会被正确识别

潜在改进点

  1. 前端校验 - 可以在前端提交前就检测 URL 是否像图片,提前提示用户
  2. 图片预加载验证 - 在提交任务前,先尝试 HEAD 请求验证图片可访问性
  3. 错误分类细化 - 可以进一步区分"网络超时"和"404 不存在"等不同错误类型

修改文件清单

文件路径 修改内容
src/app/api/ai/query/route.ts 新增自动重试逻辑、URL 有效性检测、飞书错误通知
src/app/api/ai/notify/[provider]/route.ts 已有飞书通知逻辑(本次未修改)
src/config/locale/messages/*/ai/image.json 新增 3 条多语言错误提示(10 个语言文件)

测试建议

  1. 正常图片 URL 测试 - 提交有效的图片 URL,确认正常生成
  2. 网站地址测试 - 提交 https://example.com,确认不重试且收到飞书通知
  3. 临时网络错误模拟 - 如果可能,模拟临时网络错误,确认自动重试机制工作
  4. 多语言测试 - 切换不同语言,确认错误提示正确显示

总结

本次修改主要解决了 FAL API "Image load failed" 错误导致任务直接失败的问题,通过增加自动重试机制提高了任务成功率。同时针对用户输入错误(网站地址而非图片 URL)的场景进行了识别和处理,避免无意义的重试,并通过飞书通知及时告知运维人员。


SOP 检查清单

部署前检查

  • [ ] 代码编译通过 - npm run build 无错误
  • [ ] TypeScript 类型检查 - 无 type error
  • [ ] 环境变量配置 - 确认 FEISHU_WEBHOOK_ERRORS 已配置

功能验证清单

1. 正常流程测试

  • [ ] 图生图:有效图片 URL → 生成成功
  • [ ] 图生视频:有效图片 URL → 生成成功
  • [ ] 文生图:无 image_url → 生成成功(不触发重试逻辑)

2. 重试机制测试

  • [ ] 模拟网络错误 → 自动重试(检查日志 [AI Query] Retryable error detected
  • [ ] 重试成功 → 任务继续执行,taskInfo 包含 retryCount
  • [ ] 重试 3 次失败 → 最终标记 FAILED,发送飞书通知

3. 无效 URL 测试

  • [ ] 输入网站首页 https://example.com → 不重试,直接失败
  • [ ] 飞书通知包含 [用户输入的 URL 不是图片: ...]
  • [ ] 日志输出 [AI Query] Image URL does not look like an image, skipping retry

4. 飞书通知测试

  • [ ] Webhook 端点失败 → 收到飞书通知
  • [ ] 轮询端点失败 → 收到飞书通知
  • [ ] 重试中不发通知 → 只在最终失败时发送
  • [ ] 通知内容包含:用户邮箱、错误信息、taskId、重试次数

5. 多语言测试

  • [ ] 切换到中文 → 显示中文错误提示
  • [ ] 切换到英文 → 显示英文错误提示
  • [ ] 新增的 3 条 key 都有对应翻译:
  • error_invalid_image_url
  • error_retrying
  • error_retry_exhausted

监控与告警

  • [ ] 日志关键词监控(可选)
  • [AI Query] Retryable error detected - 触发重试
  • [AI Query] Retry successful - 重试成功
  • [AI Query] Retry failed - 重试失败
  • [AI Query] Image URL does not look like an image - 无效 URL

  • [ ] 飞书通知频率检查

  • 正常情况:偶发通知(网络问题导致的最终失败)
  • 异常情况:大量通知(可能是 FAL API 故障或配置问题)

回滚方案

如需回滚,直接 revert 以下 commit:

git revert e083b72  # feat: add auto-retry for image load failures

回滚后影响: - Image load failed 错误将不再自动重试 - 轮询端点将不再发送飞书错误通知 - 多语言错误提示将缺失(前端需要降级处理)


变更记录

日期 版本 变更内容 作者
2026-01-16 v1.0 初始版本:自动重试机制、URL 检测、多语言支持 Claude

本文档为站内渲染。原始文件本地路径:saas/source/knowledge-world/Knowledge-World-项目-Practice-BUG-SOLUTIONS-2026-01-16-image-l-10a286.md(仅本地保留,不入库不部署)