---
title: "使用 Express 为 Azure OpenAI Service API 编写一个兼容原版 OpenAI Service API 的代理服务"
date: 2023-08-13T00:55:49.000Z
author: Z.SHINCHVEN
tags: [Azure OpenAI Service, OpenAI Service, Express.js]
canonical: https://atlassc.net/2023/08/13/azure-openai-service-api-proxy-with-express
---
![Azure OpenAI Service](https://blogimgs.atlassc.net/images/azure-openai-service/azure-openai-service.jpeg)

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

如果你在开通原始版 OpenAI Service API 时遇到了困难，可以考虑使用 Azure OpenAI Service 作为替代，[它非常容易申请到](/2023/04/25/azure-openai-service)，并且目前是可以直连的！

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

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

如果你急着使用，请到 GitHub 上获取 [完整代码](https://github.com/ShinChven/cf-openai-azure-proxy/blob/main/express-openai-azure-proxy.js)。

目前已实测支持：
- [ChatBox](https://chatboxai.app/)
- [ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web)
- [OpenAI Translator](https://github.com/openai-translator/openai-translator)
- [WebPilot](https://www.webpilot.ai/)

## 1. 配置参数

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

```javascript
/**
 * 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。

```javascript
/**
 * 根据模型名称获取部署名称。
 * @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 对象。具体的代码实现如下：

```javascript
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 的响应，并将其转发给客户端。具体的代码实现如下：

```javascript
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 的相应部分。

```javascript
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);
});
```

2. **处理 /v1/completions 端点的请求**：
该端点用于处理常规的文本补完请求。与 `/v1/chat/completions` 类似，请求会被代理到 Azure OpenAI Service API 的相关部分。

```javascript
app.all('/v1/completions', async (req, res) => {
  const fetchAPI = getFetchAPI(req, '/completions');
  const response = await axios(getPayload(req, res, fetchAPI));
  handleResponse(req, res, response);
});
```

3. **处理 /v1/models 端点的请求**：
该端点返回可用模型的列表，允许客户端查询服务器支持哪些 GPT 模型。

```javascript
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);
});
```

4. **处理所有其他请求**：
任何未在上述路由中定义的请求都将返回 404 Not Found 错误。

```javascript
app.all('*', (req, res) => {
  res.status(404).send('404 Not Found');
});
```
