Skip to content

Instantly share code, notes, and snippets.

@xmaihh
Created July 16, 2024 01:47
Show Gist options
  • Save xmaihh/b49fdfdf2649a8615be645fa51604568 to your computer and use it in GitHub Desktop.
Save xmaihh/b49fdfdf2649a8615be645fa51604568 to your computer and use it in GitHub Desktop.
Cloudflare Workers免费部署GCP Claude3.5 Sonnet Vertex无损转Anthropic官方版本API可用NextChat、酒馆等
curl https://small-frog-a298.luccid.workers.dev/v1/messages \
--header "x-api-key: sk-xxxx" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data \
'{
"model": "claude-3-5-sonnet-20240620",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "大家对于新出的 Realme GT6 怎么看"}
]
}'
const MODEL = 'claude-3-5-sonnet@20240620';
const PROJECT_ID = 'static-resource-428308-c5';
const CLIENT_ID = '6403879322-886qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com';
const CLIENT_SECRET = 'd-FL43Q12q1MQmFpd77hHD0Ty0';
const REFRESH_TOKEN = '1//7e8fdCNhXTHXPCgYIARAAGA4SNwF-L6IrJKht7WmsH-AsOiTM-XIgQKlxxd-0KOrJGqgFYZRkSvj11SOMFzFS8kWwrRwAIqhY9qAts';
//这个作为API的KEY
const API_KEY = 'sk-pass'
const TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token';
const LOCATIONS = ['europe-west1', 'us-east5'];
let currentLocationIndex = 0;
let requestCount = 0;
async function getAccessToken() {
try {
const response = await fetch(TOKEN_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
refresh_token: REFRESH_TOKEN,
grant_type: 'refresh_token'
})
});
if (!response.ok) {
const errorDetail = await response.text();
console.error(`HTTP Error: ${response.status}, Error details: ${errorDetail}`);
throw new Error(`HTTP Error: ${response.status}, Error details: ${errorDetail}`);
}
const data = await response.json();
return data.access_token;
} catch (error) {
console.error('Error obtaining access token:', error);
throw new Error('Error obtaining access token');
}
}
function rotateLocation() {
requestCount++;
if (requestCount >= 3) {
requestCount = 0;
currentLocationIndex = (currentLocationIndex + 1) % LOCATIONS.length;
}
console.log(`Rotating to location: ${LOCATIONS[currentLocationIndex]}, request count: ${requestCount}`);
}
function getLocation() {
return LOCATIONS[currentLocationIndex];
}
function constructApiUrl(location) {
return `https://${location}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${location}/publishers/anthropic/models/${MODEL}:streamRawPredict`;
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
if (request.method !== 'POST' || !request.url.endsWith('/v1/messages')) {
return new Response(JSON.stringify({
type: "error",
error: {
type: "not_found",
message: "The requested resource was not found."
}
}), { status: 404 });
}
const apiKey = request.headers.get('x-api-key');
if (apiKey !== API_KEY) {
return new Response(JSON.stringify({
type: "error",
error: {
type: "permission_error",
message: "Invalid API key."
}
}), { status: 403 });
}
try {
const accessToken = await getAccessToken();
const location = getLocation();
const apiUrl = constructApiUrl(location);
let requestBody = await request.json();
requestBody.anthropic_version = "vertex-2023-10-16";
delete requestBody.model;
console.log(`Using location: ${location}, request count: ${requestCount}`);
console.log('Request body:', JSON.stringify(requestBody, null, 2));
console.log('API URL:', apiUrl);
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorText = await response.text();
console.error('API Error:', errorText);
throw new Error(`API request failed with status ${response.status}: ${errorText}`);
}
if (requestBody.stream) {
const { readable, writable } = new TransformStream();
response.body.pipeTo(writable);
return new Response(readable, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
});
} else {
const data = await response.json();
return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } });
}
rotateLocation();
} catch (error) {
console.error('Error in request:', error);
return new Response(JSON.stringify({
type: "error",
error: {
type: "internal_error",
message: "An internal error occurred. Please try again later."
}
}), { status: 500 });
}
}
// 添加GCP ID:
const PROJECT_ID = 'static-resource-428308-c5';
const CLIENT_ID = '6403879322-886qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com';
const CLIENT_SECRET = 'd-FL43Q12q1MQmFpd77hHD0Ty0';
const REFRESH_TOKEN = '1//7e8fdCNhXTHXPCgYIARAAGA4SNwF-L6IrJKht7WmsH-AsOiTM-XIgQKlxxd-0KOrJGqgFYZRkSvj11SOMFzFS8kWwrRwAIqhY9qAts';
// 相当于密码的功能,接口密钥
const API_KEY = 'sk-f1e09e889ea64d7e88a2eb1aee64266exxxx'
const TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token';
let tokenCache = {
accessToken: '',
expiry: 0,
refreshPromise: null
};
/**
*
6/26 更新:
~ 修复请求量大时 accessToken 几率获取失败
导致accessToken为空值问题
*/
async function getAccessToken(){
const now = Date.now() / 1000;
// 如果 token 仍然有效,直接返回
if (tokenCache.accessToken && now < tokenCache.expiry - 120) {
return tokenCache.accessToken;
}
// 如果已经有一个刷新操作在进行中,等待它完成
if (tokenCache.refreshPromise) {
await tokenCache.refreshPromise;
return tokenCache.accessToken;
}
// 开始新的刷新操作
tokenCache.refreshPromise = (async () => {
try {
const response = await fetch(TOKEN_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
refresh_token: REFRESH_TOKEN,
grant_type: 'refresh_token'
})
});
const data = await response.json();
tokenCache.accessToken = data.access_token;
tokenCache.expiry = now + data.expires_in;
} finally {
tokenCache.refreshPromise = null;
}
})();
await tokenCache.refreshPromise;
return tokenCache.accessToken;
}
// 选择区域
function getLocation() {
const currentSeconds = new Date().getSeconds();
return currentSeconds < 30 ? 'europe-west1' : 'us-east5';
}
// 构建 API URL
function constructApiUrl(location, MODEL) {
return `https://${location}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${location}/publishers/anthropic/models/${MODEL}:streamRawPredict`;
}
function convertModelName(modelName) {
// 使用更灵活的正则表达式来匹配各种可能的模型名称格式
const match = modelName.match(/^(claude-\d+(?:-\w+)+)-(\d+)$/);
if (match) {
// 如果匹配成功,返回转换后的格式
return `${match[1]}@${match[2]}`;
}
// 如果不匹配预期格式,返回原始名称
return modelName;
}
// 处理请求
async function handleRequest(request) {
if (request.method === 'OPTIONS') {
return handleOptions();
}
// 检查x-api-key
const apiKey = request.headers.get('x-api-key');
if (apiKey !== API_KEY) {
const errorResponse = new Response(JSON.stringify({
type: "error",
error: {
type: "permission_error",
message: "Your API key does not have permission to use the specified resource."
}
}), {
status: 403,
headers: {
'Content-Type': 'application/json'
}
});
errorResponse.headers.set('Access-Control-Allow-Origin', '*');
errorResponse.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, HEAD');
errorResponse.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-api-key, anthropic-version, model');
return errorResponse;
}
const accessToken = await getAccessToken();
const location = getLocation();
let requestBody = await request.json();
let MODEL = requestBody.model;
MODEL = convertModelName(MODEL);
const apiUrl = constructApiUrl(location, MODEL);
// 删除原始请求中的"anthropic_version"字段(如果存在)
if (requestBody.anthropic_version) {
delete requestBody.anthropic_version;
}
// 删除原始请求中的"model"字段(如果存在)
if (requestBody.model) {
delete requestBody.model;
}
// 添加新的"anthropic_version"字段
requestBody.anthropic_version = "vertex-2023-10-16";
const modifiedHeaders = new Headers(request.headers);
modifiedHeaders.set('Authorization', `Bearer ${accessToken}`);
modifiedHeaders.set('Content-Type', 'application/json; charset=utf-8');
modifiedHeaders.delete('anthropic-version');
const modifiedRequest = new Request(apiUrl, {
headers: modifiedHeaders,
method: request.method,
body: JSON.stringify(requestBody),
redirect: 'follow'
});
const response = await fetch(modifiedRequest);
const modifiedResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
modifiedResponse.headers.set('Access-Control-Allow-Origin', '*');
modifiedResponse.headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
modifiedResponse.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-api-key, anthropic-version, model');
return modifiedResponse;
}
function handleOptions() {
const headers = new Headers();
headers.set('Access-Control-Allow-Origin', '*');
headers.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, x-api-key, anthropic-version, model');
return new Response(null, {
status: 204,
headers: headers
});
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment