Skip to content

Instantly share code, notes, and snippets.

@BlueSkyXN
Created February 19, 2025 13:05
Show Gist options
  • Select an option

  • Save BlueSkyXN/0f4a825b3694909fac3db58b648881cb to your computer and use it in GitHub Desktop.

Select an option

Save BlueSkyXN/0f4a825b3694909fac3db58b648881cb to your computer and use it in GitHub Desktop.
cerebras-api.js
const CEREBRAS_API_URL = 'https://gateway.ai.cloudflare.com/v1/xxx/cerebras/cerebras';
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
// Debug 函数
async function debugFetch(url, options) {
console.log('Request URL:', url);
console.log('Request Headers:', JSON.stringify(options.headers));
try {
const response = await fetch(url, options);
console.log('Response Status:', response.status);
const contentType = response.headers.get('content-type');
console.log('Response Content-Type:', contentType);
const responseClone = response.clone();
try {
const text = await responseClone.text();
console.log('Response Body:', text.substring(0, 200) + '...');
} catch (e) {
console.log('Failed to read response body:', e);
}
return response;
} catch (error) {
console.log('Fetch Error:', error);
throw error;
}
}
// 重试逻辑
async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
try {
const response = await debugFetch(url, options);
if (!response.ok) {
const contentType = response.headers.get('content-type');
let errorMessage;
if (contentType && contentType.includes('application/json')) {
const error = await response.json();
errorMessage = error.message || error.error || 'Unknown error';
} else {
errorMessage = await response.text();
}
throw new Error(`HTTP error! status: ${response.status}, message: ${errorMessage}`);
}
return response;
} catch (error) {
if (retries > 0) {
console.log(`Retry attempt ${MAX_RETRIES - retries + 1}, Error: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
return fetchWithRetry(url, options, retries - 1);
}
throw error;
}
}
// 转换请求参数
function convertRequestParams(openaiParams) {
const cereParams = { ...openaiParams };
if (cereParams.max_tokens) {
cereParams.max_completion_tokens = cereParams.max_tokens;
delete cereParams.max_tokens;
}
return cereParams;
}
// 转换单个数据块的响应
function convertChunkResponse(cereChunk) {
if (cereChunk === '[DONE]') {
return 'data: [DONE]\n\n';
}
try {
const chunk = JSON.parse(cereChunk);
const {time_info, system_fingerprint, ...restChunk} = chunk;
return `data: ${JSON.stringify(restChunk)}\n\n`;
} catch (error) {
console.error('Error parsing chunk:', error);
return '';
}
}
// 转换完整响应
function convertFullResponse(cereResponse) {
const {time_info, system_fingerprint, ...openaiResponse} = cereResponse;
return openaiResponse;
}
// 处理流式响应
async function handleStreamResponse(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
return new ReadableStream({
async start(controller) {
try {
while (true) {
const {done, value} = await reader.read();
if (done) {
if (buffer) {
const chunk = convertChunkResponse(buffer);
if (chunk) {
controller.enqueue(new TextEncoder().encode(chunk));
}
}
controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
break;
}
buffer += decoder.decode(value, {stream: true});
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('data: ')) {
const chunk = convertChunkResponse(trimmedLine.slice(6));
if (chunk) {
controller.enqueue(new TextEncoder().encode(chunk));
}
}
}
}
} catch (error) {
controller.error(error);
} finally {
controller.close();
}
},
});
}
// 错误处理
function handleError(error) {
console.error('Error details:', error);
let status = 500;
let message = error.message || 'Internal server error';
if (error.message.includes('Failed to fetch') || error.message.includes('ECONNREFUSED')) {
status = 502;
message = 'Failed to connect to Cerebras API: Connection refused';
} else if (error.message.includes('Missing API key')) {
status = 401;
} else if (error.message.includes('Missing required parameters')) {
status = 400;
}
return new Response(
JSON.stringify({
error: {
message: message,
type: 'adapter_error',
code: status,
details: error.message
}
}),
{
status: status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Cerebras-Key'
}
}
);
}
// 主处理函数
async function handleRequest(request) {
try {
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Cerebras-Key',
'Access-Control-Max-Age': '86400'
}
});
}
if (request.method !== 'POST') {
throw new Error('Method not allowed');
}
const requestBody = await request.json();
const isStream = requestBody.stream === true;
if (!requestBody.model || !requestBody.messages) {
throw new Error('Missing required parameters');
}
const cereParams = convertRequestParams(requestBody);
const cereKey = request.headers.get('X-Cerebras-Key') ||
request.headers.get('Authorization')?.replace('Bearer ', '');
if (!cereKey) {
throw new Error('Missing API key');
}
// 发送请求到 Cloudflare AI Gateway
const cereResponse = await fetchWithRetry(
`${CEREBRAS_API_URL}/chat/completions`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cereKey}`,
'Accept': isStream ? 'text/event-stream' : 'application/json'
},
body: JSON.stringify(cereParams)
}
);
if (isStream) {
const stream = await handleStreamResponse(cereResponse);
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
}
});
}
const cereData = await cereResponse.json();
const openaiData = convertFullResponse(cereData);
return new Response(JSON.stringify(openaiData), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
});
} catch (error) {
return handleError(error);
}
}
// Worker 入口点
addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.pathname.startsWith('/v1/chat/completions')) {
event.respondWith(handleRequest(event.request));
} else {
event.respondWith(new Response(
JSON.stringify({
error: {
message: 'Not found',
type: 'adapter_error',
code: 404
}
}),
{
status: 404,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
}
));
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment