一个独立开发者在上线前一天发来日志截图:/api/chat 路由在本地 next dev 跑得好好的,部署到 Vercel 之后每次问到长 prompt 就 504,短句没问题。Vercel Dashboard 的 Functions 日志里只写了 Task timed out after 25.0 seconds,没有任何额外错误码。

OpenAI 的 GPT-4o 生成一段 1000 token 的回复通常只需要 5-15 秒,为什么还会超时?答案在于 Vercel Edge Function 计时的是「你的代码开始执行到第一个响应字节发出」的总时长,而不是 OpenAI API 本身的生成时间。

EDGE_FUNCTION_INVOCATION_TIMEOUT 触发的三条路径

Vercel 的 Edge 超时报错不止一种。同一个现象(504)背后可能对应三个不同的触发路径,排查顺序错了会浪费几个小时。

错误码触发条件日志里怎么识别
EDGE_FUNCTION_INVOCATION_TIMEOUT25 秒内没有任何响应字节发出Task timed out after 25.0 seconds
INTERNAL_EDGE_FUNCTION_INVOCATION_TIMEOUT函数甚至没有开始执行(冷启动 + 队列等待)Function did not begin responding within 25s
504 Gateway Timeout(idle 版)已经开始 streaming,但连续 10-15 秒没有新 chunk函数日志不报错,客户端收到截断的响应

最常见的场景是第一种:你在 POST handler 里 await 了整个 OpenAI 非流式调用,API 耗时 18 秒 + Vercel 到 OpenAI 的网络往返 4 秒 + 冷启动 2 秒 = 24 秒,刚好擦过 25 秒上限。长 prompt 或 tool call 多一轮就直接超。

30 秒自检

在开始改代码之前,先确认这四件事:

  1. 确认 runtime 类型:打开 /api/chat/route.ts,检查是否有 export const runtime = 'edge';。如果没有这一行,你跑的是 Node.js runtime,超时原因完全不同(看下一节)。
  2. 检查 OpenAI 调用方式:是 stream: true 还是 stream: false?如果是 stream: false,改这里是第一步。
  3. 排查 tool call 循环:如果用了 Vercel AI SDK 的 streamText 且接了 tools,检查是否设了 maxSteps。没设的话,一次 tool call 往返可能吃掉 8-15 秒。
  4. 确认 region:Edge Function 默认跑在离用户最近的节点;如果你在国内开发、Vercel 把请求路由到了美西节点,网络延迟会多 200-400ms,但这种量级通常不是 timeout 主因。

streaming 三件套:先发 header、pipe 原流、加 keep-alive

Edge Function 的 25 秒只在意「第一个响应字节」。一旦 new Response(stream) 返回,函数就不再受这个硬上限约束——它可以持续运行,只在连续空闲(idle timeout)时才会被终止。

// app/api/chat/route.ts
export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      // 第一步:立即发出一个初始化 chunk,打破 25 秒倒计时
      controller.enqueue(encoder.encode('{"status":"started"}\n'));

      // 第二步:设置 10 秒一次的 keep-alive,防 idle timeout
      const keepAlive = setInterval(() => {
        controller.enqueue(encoder.encode(': ping\n\n'));
      }, 10000);

      try {
        const openaiRes = await fetch('https://api.openai.com/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
          },
          body: JSON.stringify({
            model: 'gpt-4o',
            messages,
            stream: true,
          }),
        });

        const reader = openaiRes.body?.getReader();
        if (!reader) throw new Error('No readable body');

        clearInterval(keepAlive); // OpenAI 流到达后停掉人工心跳

        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          controller.enqueue(value);
        }
      } finally {
        clearInterval(keepAlive);
        controller.close();
      }
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache, no-transform',
      'Connection': 'keep-alive',
    },
  });
}

这里 Content-Type: text/event-stream 是关键:Vercel 的 CDN 看到这个 header 才会启用流式传输模式,否则可能 buffer 完整的响应体再一次性发出去——这就等于主动放弃了 streaming 的优势。

单纯 OpenAI 生成慢 vs tool call 循环,别混在一起排查

如果你用的是 Vercel AI SDK,超时可能出现在两个不同的阶段:

  • OpenAI 生成慢:模型收到 prompt 后一直在推理,但 stream 里已经有 token 在输出。这种情况加上 keep-alive 就够了。
  • tool call 循环卡住:模型输出了一个 tool call 然后停下来等结果;你的代码拿到 tool 结果后又调了一次 generateText,而不是把它喂回同一个 stream 实例。这导致 25 秒倒计时被重置。

第二种情况用 maxSteps 解决:

const result = await streamText({
  model: openai('gpt-4o'),
  messages,
  tools: { search: tool({ ... }) },
  maxSteps: 5, // SDK 自动完成 tool → 模型 → 回复,最多 5 轮
});

什么时候应该放弃 Edge,切到 Node.js + Fluid Compute

Edge Function 的 streaming 方案足以应对 90% 的 OpenAI 调用场景,但有些情况应该直接选择更长的超时预算。

场景推荐 runtime最长超时理由
单次问答,无 tool callEdge + streaming不限(只要持续发 chunk)延迟最低,CDN 边缘节点直接处理
tool call 1-3 轮Edge + maxSteps + keep-alive同上可以覆盖,但注意每轮 tool 执行时间
tool call 4 轮以上Node.js + maxDuration: 300300 秒多轮 tool call 累计容易触发 idle timeout
需要调第三方 API > 15 秒Node.js + Fluid Compute800 秒例如调 Google Search、数据库批量查询
部署在 Pro 套餐上Node.js + Fluid Compute800 秒Pro 才有 Fluid Compute,Hobby 最长 60 秒

如果你部署在 Hobby 套餐上且确实需要超过 60 秒的后端处理(比如长期运行的 agent 循环),一种折中方案是把 Edge Function 拆成两个路由:一个流式 API 路由负责向客户端推流,另一个后台任务路由用 Node.js runtime 做重计算,两者之间通过 Vercel KV 或 Redis 通信。

独立开发者在 Vercel 和 OpenAI 之间调试超时问题,经常需要反复部署、改环境变量、看日志。如果网络本身不稳定,Vercel 到 OpenAI API 的 HTTPS 请求会额外增加 1-3 秒的 TCP + TLS 握手,这一部分也会计入 25 秒预算。需要一个海外银行 + Stripe + AI 工具全场景的稳定开发环境来减少这类额外延迟。

Hobby 计划下再被截断怎么办:Serverless Function 也是备选项

有些独立开发者第一反应是把 Edge runtime 全改为 Node.js runtime,但这不一定对。

Node.js runtime 的默认 maxDuration 在 Hobby 计划下是 10 秒,比 Edge 的 25 秒更短。你需要在 vercel.json 或路由段配置里显式声明:

{
  "functions": {
    "app/api/chat/route.ts": {
      "maxDuration": 60
    }
  }
}

Hobby 计划最大 60 秒,Pro 最大 300 秒(Serverless)或 800 秒(Fluid Compute)。如果你的 Hobby 计划 60 秒仍然不够,有两个选择:升级 Pro 开 Fluid Compute,或者把任务拆成异步 job 队列,即刻返回 202 Accepted 并在后台处理。

不过 Node.js runtime 还有一个隐形成本:冷启动。Hobby 计划下冷启动通常在 500ms-2s,对 OpenAI API 的 5-15 秒生成时间来说占比不大,但如果你的 route 是低频访问的 api/chat,每次冷启动都会把这 2 秒算进你的 25/60 秒预算。

相关阅读