Cloudflare Workers 调用 Gemini 时偶尔报 "User location is not supported":原因和几种解决办法

我在 Cloudflare Workers 上接入 Gemini API 时遇到一个问题:大多数请求都正常,但偶尔会直接收到 400 错误:

{
  "error": {
    "code": 400,
    "message": "User location is not supported for the API use.",
    "status": "FAILED_PRECONDITION"
  }
}

报错是随机出现的,本地调试也复现不了,所以一开始很难判断是哪里出了问题。查过一圈资料后发现原因并不复杂,解决办法也有好几种。下面把整理结果记录下来。

一、根因:Gemini 判断的是 Worker 所在机房的位置

Cloudflare Workers 默认运行在离访问者最近的数据中心。Gemini API(generativelanguage.googleapis.com)判断地区时,看的是发起请求那台机器的出口 IP,也就是 Worker 当时所在的机房。API Key 归属哪里、最终用户在哪里,它都不管。

问题出在香港:中国大陆和香港都不在 Gemini API 的支持地区内。只要某次请求被分配到 HKG(香港)机房,Google 就会返回上面的地区错误。

“偶尔出现”是因为 Cloudflare 的路由会变,而且受影响的不只是中国大陆和香港用户:

  • 一位在澳大利亚墨尔本的开发者发现,他访问的 Worker 跑在 4600 英里外的 HKG。这时直接调用和通过 AI Gateway 调用都会报同样的地区错误。来源

  • 一位韩国开发者反映,同一个付费用户有时被分到 NRT(东京),一切正常;有时被分到 HKG,就直接报错。来源

  • 菲律宾、越南的用户也有类似报告:本地直连 Gemini 正常,经过 Worker 就失败。来源 来源

一个最常见的误区要先说:改用 Cloudflare AI Gateway 不能解决这个问题。上面墨尔本那个案例里,AI Gateway 同样返回了地区错误。

怎么确认是不是这个原因

最省事的办法是看 Gemini 返回的响应头 cf-ray。它的末尾是机房代码,例如 88958fc801afb458-HKG,以 -HKG 结尾就基本可以确定。来源

也可以在 Worker 里把当前机房打到日志里:

console.log("colo:", request.cf?.colo);

二、解决方案

下面按我推荐的顺序排列,越靠前改动越小。

方案 1:Placement Hints,指定 Worker 的运行地区(首选)

Cloudflare 在 2026 年 1 月推出了显式放置提示(Placement Hints)。在 Wrangler 配置里声明一个云厂商区域,Worker 就会运行在离该区域延迟最低的 Cloudflare 机房,不再跟着访问者走。来源

# wrangler.toml
[placement]
region = "gcp:us-central1"

如果用 wrangler.jsonc:

{
  "placement": {
    "region": "gcp:us-central1"
  }
}

region 的格式是 {provider}:{region},支持 AWS、GCP 和 Azure 的区域代码,例如 aws:us-east-1、gcp:europe-west1、azure:westeurope。来源 要调 Gemini,选一个美国的 GCP 区域比较合适。

已经有人用这个办法解决了同样的问题:菲律宾访客经常被分到 HKG,配置成靠近 gcp:us-central1 之后报错就消失了。来源

需要注意两点:

  1. 它不是 100% 的硬保证。 官方文档写明,请求量极高时(每秒数十万请求以上),Cloudflare 可能把实例分散到更大的范围来均衡流量。来源 对普通规模的业务来说基本够用。

  2. Terraform 用户要留意一个已知问题。 cloudflare_worker_version 资源里的 placement 字段会被静默忽略,原因是 provider 把它发到了不支持该字段的 versions 接口。目前的绕行办法是部署后再调用一次脚本的 settings 接口,单独 PATCH placement。来源

顺带一提,旧的 Smart Placement(mode = "smart")是根据观测到的后端延迟自动放置的,它的目标是降低延迟,不负责避开受限地区,所以不建议拿它来解决这个问题。

方案 2:用 Durable Object 发出 Gemini 请求

如果需要更确定的出口位置,可以把“调用 Gemini”这一步单独交给一个 Durable Object,创建时通过 locationHint 指定地区(如 wnam 北美西部、enam 北美东部、weur 西欧)。Durable Object 创建后会停留在所在地区,外层 Worker 在哪个机房运行就无所谓了。

Durable Object 的定义:

import { DurableObject } from "cloudflare:workers";

export class GeminiProxy extends DurableObject {
  async fetch(request) {
    const url = new URL(request.url);
    const target = new URL(
      url.pathname + url.search,
      "https://generativelanguage.googleapis.com"
    );
    return fetch(new Request(target, request));
  }
}

外层 Worker 的转发:

export default {
  async fetch(request, env) {
    const id = env.GEMINI_PROXY.idFromName("us-proxy");
    const stub = env.GEMINI_PROXY.get(id, { locationHint: "wnam" });
    return stub.fetch(request);
  },
};

Wrangler 配置:

[[durable_objects.bindings]]
name = "GEMINI_PROXY"
class_name = "GeminiProxy"

[[migrations]]
tag = "v1"
new_sqlite_classes = ["GeminiProxy"]

这个方案的代价有三点:

  • 所有请求都要经过同一个(或少数几个)Durable Object 实例,并发高时要按 key 分片成多个实例。

  • 要额外承担 Durable Object 的计费。

  • locationHint 本身也是“提示”。它只在对象首次创建时起作用,Cloudflare 会尽量满足,但建议上线后用日志确认实际位置。

方案 3:识别地区错误后兜底重试

这个方案适合叠加在方案 1 或方案 2 之上:识别出地区错误后,换一个出口重发。

async function callGemini(geminiUrl, init, env) {
  const res = await fetch(geminiUrl, init);

  if (res.status === 400) {
    const body = await res.clone().text();
    if (body.includes("User location is not supported")) {
      // 换到固定在北美的 Durable Object 出口重试
      const id = env.GEMINI_PROXY.idFromName("us-proxy");
      const stub = env.GEMINI_PROXY.get(id, { locationHint: "wnam" });
      return stub.fetch(new Request(geminiUrl, init));
    }
  }
  return res;
}

注意,如果 init.body 是流(ReadableStream),读过一次就不能再用。重试前要先把请求体读成字符串或 ArrayBuffer 再复用。

方案 4:出口交给地区可控的服务

还有一种做法是让 Cloudflare 只负责入口、鉴权和限流,真正请求 Gemini 的那一步放到出口地区可控的地方:

  • 自建反向代理:在美国、日本、新加坡等受支持地区的 VPS 上跑一个转发服务。

  • 能固定区域的 Serverless 平台:比如把 Vercel Functions 固定在 iad1,或在 GCP Cloud Run 的 us-central1 部署一个转发服务。

  • 聚合平台:通过 OpenRouter 这类服务调用 Gemini,由它们的出口去请求 Google。

这是确定性最高的做法,代价是多一跳网络延迟,还多了一个需要维护的组件。

方案 5:改用 Vertex AI(需要自己验证)

通过 Vertex AI 调用 Gemini 时,走的是 GCP 项目鉴权,可以使用带区域的端点(例如 us-central1-aiplatform.googleapis.com),和面向开发者的 Gemini API 是两套体系。不过我没有查到 Vertex AI 怎样判断请求来源地区的公开资料,不能断定这样就能避开限制。如果考虑这条路,建议先从 HKG 出口实际测一下。

三、怎么选

场景 推荐做法
个人项目、中小流量,想用最小改动修好 方案 1(Placement Hints)+ 方案 3(兜底重试)
核心付费功能,不能容忍偶发失败 方案 2(Durable Object + locationHint)或方案 4(固定出口)
本来就在 GCP 上,或有企业合规要求 先实测方案 5(Vertex AI)

我的建议是先在 wrangler.toml 里加上 [placement] region = "gcp:us-central1",再在代码里加一层地区错误检测和重试。这两步改动都很小,大部分偶发的 HKG 报错就能消失。上线后在日志里观察一段时间,如果还有漏网的请求,再考虑方案 2 或方案 4。

这个问题社区里早有讨论。2024 年就有人在 Cloudflare 社区提议,给 Worker 加一个“AI API Worker”标记,让平台自动避开不能访问主流大模型 API 的地区。来源 这个功能目前还没有落地。在它出现之前,Placement Hints 是现成方案里改动最小、也最接近这个思路的一个。

参考资料