Skip to content

Instantly share code, notes, and snippets.

@ihciah
Created December 17, 2025 21:43
Show Gist options
  • Select an option

  • Save ihciah/3b6168acfffd41ef94e88cee7b5782f7 to your computer and use it in GitHub Desktop.

Select an option

Save ihciah/3b6168acfffd41ef94e88cee7b5782f7 to your computer and use it in GitHub Desktop.
Gemini API load balancer

基于 Cloudflare Workers 的 Gemini API 负载均衡器

这是一个部署在 Cloudflare Workers (Serverless) 上的轻量级 Gemini API 负载均衡器和代理。

本项目通过实现 随机密钥选择与重试 (Random Key Selection & Retry) 策略,解决了 Gemini API 免费层级严格的速率限制问题。它暴露了一个 兼容 OpenAI 的端点,允许与支持 OpenAI SDK 的客户端(如 EasyDict)集成。

部署

  1. 创建 Worker: 前往 Cloudflare Dashboard 并创建一个新的 Worker(点击"从 Helloworld 开始")。

  2. 部署代码: 将提供的代码粘贴到 Worker 编辑器中。

  3. 配置密钥: 修改脚本顶部的 API_KEYS 常量数组,填入你的 Gemini API 密钥列表。

    const API_KEYS = [
      "AIzaSy...",
      "AIzaSy...",
      // ...
    ];

客户端配置

配置你的应用程序(例如 EasyDict)以使用 OpenAI 协议:

  • Base URL / Endpoint: https://YOUR-WORKER-NAME.YOUR-SUBDOMAIN.workers.dev/v1beta/openai/chat/completions
  • API Key: 使用你 API_KEYS 数组中的 第一个密钥

模型可用性与命名

重要提示: Google AI Studio UI 中显示的模型名称可能与 API 实际要求的 model_id 不同。

  • 检查配额: 访问 Google AI Studio - Usage 并勾选 "Show All Models" 查看不同模型的免费配额。
  • 获取正确的 Model ID: 参考 Gemini API Models 文档 获取精确的 model_id
    • 示例: Google AI Studio Usage 页面可能显示 gemini-3-flash,但 API 实际需要的是 gemini-3-flash-preview

Gemini API Load Balancer on Cloudflare Workers

A lightweight Load Balancer and Proxy for the Gemini API, deployed on Cloudflare Workers (Serverless).

This project addresses the strict rate limits of the Gemini API free tier by implementing a random key selection and retry strategy. It exposes an OpenAI-compatible endpoint, allowing integration with clients that support the OpenAI SDK (e.g., EasyDict).

Deployment

  1. Create a Worker: Go to your Cloudflare Dashboard and create a new Worker(click "Start From Helloworld").

  2. Deploy Code: Paste the provided code into the Worker editor.

  3. Configure Keys: Modify the API_KEYS constant array at the top of the script. Populate it with your list of Gemini API keys.

    const API_KEYS = [
      "AIzaSy...",
      "AIzaSy...",
      // ...
    ];

Client Configuration

Configure your application (e.g., EasyDict) to use the OpenAI protocol:

  • Base URL / Endpoint: https://YOUR-WORKER-NAME.YOUR-SUBDOMAIN.workers.dev/v1beta/openai/chat/completions
  • API Key: Use the first key from your API_KEYS array.

Model Availability & Naming

Important: The model names displayed in the Google AI Studio UI may differ from the actual model_id required by the API.

  • Check Quotas: Visit Google AI Studio - Usage and check "Show All Models" to see quotas for all models available.
  • Get Correct Model IDs: Refer to the Gemini API Models Documentation for the precise model_id.
    • Example: The Google AI Studio Usage page may show gemini-3-flash, but the API requires gemini-3-flash-preview.
/**
* Config
* API_KEYS[0] is Master Key
*/
const API_KEYS = [
"TOKEN1",
"TOKEN2",
];
const TARGET_HOST = "generativelanguage.googleapis.com";
async function getHash(text) {
const encoder = new TextEncoder();
const data = encoder.encode(text);
return await crypto.subtle.digest('SHA-256', data);
}
function safeCompareBuffers(buf1, buf2) {
if (buf1.byteLength !== buf2.byteLength) return false;
return crypto.subtle.timingSafeEqual(buf1, buf2);
}
let MASTER_HASH_CACHE = null;
export default {
async fetch(request, env, ctx) {
// CORS
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
const errorResponse = (status, code, message, type = "api_error") => {
return new Response(JSON.stringify({
error: {
message: message,
type: type,
param: null,
code: code
}
}), {
status: status,
headers: { "Content-Type": "application/json; charset=utf-8" }
});
};
const authHeader = request.headers.get("Authorization");
if (!authHeader) {
return errorResponse(401, "invalid_api_key", "Missing Authorization header.");
}
if (!authHeader.startsWith("Bearer ")) {
return errorResponse(401, "invalid_api_key", "Invalid Authorization header format.");
}
const clientToken = authHeader.slice(7).trim();
const masterKey = API_KEYS[0];
if (!MASTER_HASH_CACHE) {
MASTER_HASH_CACHE = await getHash(masterKey);
}
try {
const clientHash = await getHash(clientToken);
if (!safeCompareBuffers(clientHash, MASTER_HASH_CACHE)) {
return errorResponse(401, "invalid_api_key", "Incorrect API key provided.");
}
} catch (e) {
return errorResponse(500, "internal_error", "Authentication processing failed.");
}
const requestBody = await request.text();
const url = new URL(request.url);
url.hostname = TARGET_HOST;
url.protocol = "https:";
let keyPool = [...API_KEYS];
let lastResponse = null;
while (keyPool.length > 0) {
const randomIndex = Math.floor(Math.random() * keyPool.length);
const currentKey = keyPool[randomIndex];
keyPool.splice(randomIndex, 1);
const newHeaders = new Headers(request.headers);
newHeaders.set("Authorization", `Bearer ${currentKey}`);
newHeaders.set("Host", TARGET_HOST);
try {
const response = await fetch(url.toString(), {
method: request.method,
headers: newHeaders,
body: requestBody,
});
if (response.status === 429 && keyPool.length > 0) {
lastResponse = response;
continue;
}
const newResponse = new Response(response.body, response);
newResponse.headers.set("Access-Control-Allow-Origin", "*");
return newResponse;
} catch (e) {
if (keyPool.length === 0) {
return errorResponse(502, "bad_gateway", `Upstream error: ${e.message}`);
}
}
}
if (lastResponse) {
const newResponse = new Response(lastResponse.body, lastResponse);
newResponse.headers.set("Access-Control-Allow-Origin", "*");
return newResponse;
}
return errorResponse(503, "service_unavailable", "All API keys exhausted.");
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment