cf-openai-azure-proxy icon indicating copy to clipboard operation
cf-openai-azure-proxy copied to clipboard

如果你不同的模型在不同的resource下面的话,采用这样的代码

Open tedestar opened this issue 1 year ago • 0 comments

现在不同的模型在不同的地域可用,甚至我想用的最新的3.5和4居然没有一个可以同时用的区域,我不得不创建不同的resource,改了一下代码,感觉没必要发pr,直接贴在下面,需要用的自取。 要点如下

  • 将不同模型的resourceName, deployName和对应的apikey写成二维数组,有多少写多少。
  • 不同的modelname映射为models的索引,为数字。
  • authKey要设置下,代表客户端请求用的apikey,因为要使用不同的resource,所以apikey不同,不能直接用客户端的,只能单独约定一个跟客户端交互的key,服务端的用定义在脚本里面的

代码如下,要改的就前三个。

// 不同的模型写到下面的数组里面
const models = [
  ["resourceName1", "deployName1", "apikey1:*****7633c5e7289d"],
  ["resourceName2", "deployName2", "apikey2:*****7630a45b8531"]
];

//这是跟客户端的约定key,客户端设置成这个就行
const authKey = "sk-*************RtoJlKP9BfU0X5";

// 后面的数字代表在models中的索引位置
const mapper = {
    'gpt-3.5-turbo': 0,
    'gpt-3.5-turbo-0613': 0,
    'gpt-3.5-turbo-1106': 0,
    'gpt-3.5-turbo-16k': 0,
    'gpt-3.5-turbo-0125': 0,
    'gpt-4': 1,
    'gpt-4-turbo': 1,
    'gpt-4-0613': 1,
    'gpt-4-1106-preview': 1,
    'gpt-4-32k': 1,
    'gpt-4-turbo-2024-04-09': 1,
};

const apiVersion="2024-03-01-preview";

addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  if (request.method === 'OPTIONS') {
    return handleOPTIONS(request)
  }

  if (!request.headers.get('Authorization').includes(authKey)) {
    return new Response("Not allowed", {
      status: 403
    });
  }

  const url = new URL(request.url);
  if (url.pathname.startsWith("//")) {
    url.pathname = url.pathname.replace('/',"")
  }
  if (url.pathname === '/v1/chat/completions') {
    var path="chat/completions"
  } else if (url.pathname === '/v1/images/generations') {
    var path="images/generations"
  } else if (url.pathname === '/v1/completions') {
    var path="completions"
  } else if (url.pathname === '/v1/models') {
    return handleModels(request)
  } else {
    return new Response('404 Not Found', { status: 404 })
  }

  let body;
  if (request.method === 'POST') {
    body = await request.json();
  }

  const modelName = body?.model;  
  if (!(modelName in mapper)) {
    return new Response('Missing model mapper', {
        status: 403
    });
  }

  const model = models[mapper[modelName]];

  const fetchAPI = `https://${model[0]}.openai.azure.com/openai/deployments/${model[1]}/${path}?api-version=${apiVersion}`

  const payload = {
    method: request.method,
    headers: {
      "Content-Type": "application/json",
      "api-key": model[2],
    },
    body: typeof body === 'object' ? JSON.stringify(body) : '{}',
  };

  let response = await fetch(fetchAPI, payload);
  response = new Response(response.body, response);
  response.headers.set("Access-Control-Allow-Origin", "*");

  if (body?.stream != true){
    return response
  } 

  let { readable, writable } = new TransformStream()
  stream(response.body, writable);
  return new Response(readable, response);
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// support printer mode and add newline
async function stream(readable, writable) {
  const reader = readable.getReader();
  const writer = writable.getWriter();

  // const decoder = new TextDecoder();
  const encoder = new TextEncoder();
  const decoder = new TextDecoder();
// let decodedValue = decoder.decode(value);
  const newline = "\n";
  const delimiter = "\n\n"
  const encodedNewline = encoder.encode(newline);

  let buffer = "";
  while (true) {
    let { value, done } = await reader.read();
    if (done) {
      break;
    }
    buffer += decoder.decode(value, { stream: true }); // stream: true is important here,fix the bug of incomplete line
    let lines = buffer.split(delimiter);

    // Loop through all but the last line, which may be incomplete.
    for (let i = 0; i < lines.length - 1; i++) {
      await writer.write(encoder.encode(lines[i] + delimiter));
      await sleep(20);
    }

    buffer = lines[lines.length - 1];
  }

  if (buffer) {
    await writer.write(encoder.encode(buffer));
  }
  await writer.write(encodedNewline)
  await writer.close();
}

async function handleModels(request) {
  const data = {
    "object": "list",
    "data": []  
  };

  for (let key in mapper) {
    data.data.push({
      "id": key,
      "object": "model",
      "created": 1677610602,
      "owned_by": "openai",
      "permission": [{
        "id": "modelperm-M56FXnG1AsIr3SXq8BYPvXJA",
        "object": "model_permission",
        "created": 1679602088,
        "allow_create_engine": false,
        "allow_sampling": true,
        "allow_logprobs": true,
        "allow_search_indices": false,
        "allow_view": true,
        "allow_fine_tuning": false,
        "organization": "*",
        "group": null,
        "is_blocking": false
      }],
      "root": key,
      "parent": null
    });  
  }

  const json = JSON.stringify(data, null, 2);
  return new Response(json, {
    headers: { 'Content-Type': 'application/json' },
  });
}

async function handleOPTIONS(request) {
    return new Response(null, {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': '*',
        'Access-Control-Allow-Headers': '*'
      }
    })
}

tedestar avatar May 03 '24 17:05 tedestar