avatar

ShīnChvën ✨

Effective Accelerationism

Powered by Druid

使用 Express 为 Azure OpenAI Service API 编写一个兼容原版 OpenAI Service API 的代理服务

Azure OpenAI Service

Azure OpenAI Service 是由 Microsoft Azure 面向全球提供的 OpenAI 服务。

如果你在开通原始版 OpenAI Service API 时遇到了困难,可以考虑使用 Azure OpenAI Service 作为替代,它非常容易申请到,并且目前是可以直连的!

Azure OpenAI Service API 与原始版 OpenAI Service API 的设计有一些不同,这导致了一些 GPT 第三方客户端无法直接兼容。

为了适配各种 GPT 第三方客户端,我们可以使用 Express.js 和 axios 来编写一个代理服务程序,以将 Azure OpenAI Service API 转换为与标准版 OpenAI Service API 兼容的格式,确保无缝集成。

如果你急着使用,请到 GitHub 上获取 完整代码

目前已实测支持:

1. 配置参数

为了对 Azure OpenAI Service API 进行适配,我们需要将一些参数映射到标准版 OpenAI Service API 的参数。下面这部分代码定义了服务器需要的一些配置参数,包括资源名称、模型到部署名称的映射以及 API 版本。

/**
 * Azure OpenAI Service 的资源名称。
 * @type {string}
 */
const resourceName = 'openai';
/**
 * 将模型名称映射到 Azure OpenAI Service 上的`模型部署名称`。
 * @type {Object.<string, string>}
 */
const mapper = {
  'gpt-3.5-turbo': 'gpt-35-turbo',
  'gpt-3.5-turbo-0301': 'gpt-35-turbo',
  'gpt-3.5-turbo-0613': 'gpt-35-turbo',
  'gpt-3.5-turbo-16k': 'gpt-35-turbo-16k',
  'gpt-3.5-turbo-16k-0613': 'gpt-35-turbo-16k',
  'gpt-4': 'gpt-4',
  'gpt-4-0301': 'gpt-4',
  'gpt-4-0613': 'gpt-4',
  'gpt-4-32k': 'gpt-4-32k',
  'gpt-4-32k-0301': 'gpt-4-32k',
  'gpt-4-32k-0613': 'gpt-4-32k'
};
/**
 * API 版本。
 * @type {string}
 */
const apiVersion = '2023-05-15';

2. 构造 Azure OpenAI Service API 的请求地址

接下来,我们将编写一个名为 getFetchAPI 的函数。这个函数的目的是利用由第三方ChatGPT客户端按照原版 OpenAI Service 协议发送的请求参数,构建一个用于调用 Azure OpenAI Service 的 URL。

/**
 * 根据模型名称获取部署名称。
 * @param {string} modelName 模型名称。
 */
const getModelDeployName = (modelName) => mapper[modelName];

/**
 * 根据请求对象和路径构建 API 端点 URL。
 * @param {Object} req 请求对象。
 * @param {string} path 路径。
 */
function getFetchAPI(req, path) {
  const modelName = req.body?.model;
  const deployName = getModelDeployName(modelName);

  if (deployName === '') {
    res.status(403).send('Missing model mapper');
  }

  return `https://${resourceName}.openai.azure.com/openai/deployments/${deployName}${path}?api-version=${apiVersion}`;
}

3. 构造一个 Azure OpenAI Service API 的请求

然后,我们将编写一个名为 getPayload 的函数。这个函数的任务是构建一个用于调用 Azure OpenAI Service 的请求 Payload 对象。具体的代码实现如下:

function getPayload(req, res, url) {
  const authKey = req.headers.authorization;
  if (!authKey) {
    res.status(403).send('Not allowed');
  }

  return {
    method: req.method, // 请求方法
    url, 
    headers: {
      'Content-Type': 'application/json',
      'api-key': authKey.replace('Bearer ', ''),
    },
    data: JSON.stringify(req.body) || '{}',
    responseType: 'stream',
  };
}

处理请求:handleResponse 函数

接下来,我们将编写一个名为 handleResponse 的函数。这个函数的任务是处理 Azure OpenAI Service API 的响应,并将其转发给客户端。具体的代码实现如下:

async function handleResponse(req, res, axiosResponse) {
  // 设置响应头和状态
  res.setHeader('Access-Control-Allow-Origin', '*');
  // 这里非常关键,如果不是 text/event-stream,将无法兼容一些 GPT 第三方客户端
  res.setHeader('Content-Type', 'text/event-stream'); 
  res.status(axiosResponse.status);

  // 将调用 Azure OpenAI Service API 的响应转发给客户端,
  // axiosResponse.data 是调用 Azure OpenAI Service API 的响应数据流,res 是客户端的响应流
  axiosResponse.data.pipe(res);
}

代理 OpenAI Service API 端点

最后,我们按照原始的 OpenAI Service API 设计,挂载 Middleware 路由以实现请求的代理。

  1. 处理 /v1/chat/completions 端点的请求: 该端点用于与特定的聊天完成模型进行交互。当客户端向此端点发出请求时,服务器会将其代理到 Azure OpenAI Service API 的相应部分。
app.all('/v1/chat/completions', async (req, res) => {
  const fetchAPI = getFetchAPI(req, '/chat/completions');
  const response = await axios(getPayload(req, res, fetchAPI));
  handleResponse(req, res, response);
});
  1. 处理 /v1/completions 端点的请求: 该端点用于处理常规的文本补完请求。与 /v1/chat/completions 类似,请求会被代理到 Azure OpenAI Service API 的相关部分。
app.all('/v1/completions', async (req, res) => {
  const fetchAPI = getFetchAPI(req, '/completions');
  const response = await axios(getPayload(req, res, fetchAPI));
  handleResponse(req, res, response);
});
  1. 处理 /v1/models 端点的请求: 该端点返回可用模型的列表,允许客户端查询服务器支持哪些 GPT 模型。
app.all('/v1/models', async (req, res) => {
  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);
  res.setHeader('Content-Type', 'application/json');
  res.send(json);
});
  1. 处理所有其他请求: 任何未在上述路由中定义的请求都将返回 404 Not Found 错误。
app.all('*', (req, res) => {
  res.status(404).send('404 Not Found');
});