Skip to content

Commit

Permalink
✨ feat: Support Azure OpenAI Deploy env (lobehub#183)
Browse files Browse the repository at this point in the history
* ✨ feat: support azure deploy config

* ♻️ refactor: refactor code

* 🐛 fix: fix error

* ✅ test: fix test
  • Loading branch information
arvinxx authored Sep 11, 2023
1 parent 03284dd commit bda6732
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 25 deletions.
45 changes: 45 additions & 0 deletions docs/deploy-with-azure-openai.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 使用 Azure OpenAI 部署

LobeChat 支持使用 [Azure OpenAI][azure-openai-url] 作为 OpenAI 的模型服务商,本文将介绍如何配置 Azure OpenAI。

## 使用限制

从研发成本考虑([#178][rfc]),目前阶段的 LobeChat 并没有 100% 完全符合 Azure OpenAI 的实现模型,采用了以 `openai` 为基座,兼容 Azure OpeAI 的解决方案。因此会带来以下局限性:

- OpenAI 与 Azure OpenAI 只能二选一,当你开启使用 Azure OpenAI 后,将无法使用 OpenAI 作为模型服务商;
- LobeChat 约定了与模型同名的部署名才能正常使用,比如 `gpt-35-turbo` 模型的部署名,必须为 `gpt-35-turbo`,否则 LobeChat 将无法正常正确匹配到相应模型
![](https://github-production-user-asset-6210df.s3.amazonaws.com/28616219/267082091-d89d53d3-1c8c-40ca-ba15-0a9af2a79264.png)
- 由于 Azure OpenAI 的 SDK 接入复杂度,当前无法查询配置资源的模型列表;

## 在界面中配置

点击左下角「操作」 -「设置」,切到 「语言模型」 Tab 后通过开启「Azure OpenAI」开关,即可开启使用 Azure OpenAI。

![](https://github-production-user-asset-6210df.s3.amazonaws.com/28616219/267083420-422a3714-627e-4bef-9fbc-141a2a8ca916.png)

你按需填写相应的配置项:

- **APIKey**:你在 Azure OpenAI 账户页面申请的 API 密钥,可在“密钥和终结点”部分中找到此值
- **API 地址**:Azure API 地址,从 Azure 门户检查资源时,可在“密钥和终结点”部分中找到此值
- **Azure Api Version**: Azure 的 API 版本,遵循 YYYY-MM-DD 格式,查阅[最新版本][azure-api-verion-url]

完成上述字段配置后,点击「检查」,如果提示「检查通过」,则说明配置成功。

## 在部署时配置

如果你希望部署的版本直接配置好 Azure OpenAI,让终端用户直接使用,那么你需要在部署时配置以下环境变量:

| 环境变量 | 类型 | 描述 | 默认值 | 示例 |
| ------------------- | ---- | ------------------------------------------------------------------------- | ------------------ | -------------------------------------------------- |
| `USE_AZURE_OPENAI` | 必选 | 设置改值为 `1` 开启 Azure OpenAI 配置 | - | `1` |
| `AZURE_API_KEY` | 必选 | 这是你在 Azure OpenAI 账户页面申请的 API 密钥 | - | `c55168be3874490ef0565d9779ecd5a6` |
| `OPENAI_PROXY_URL` | 必选 | Azure API 地址,从 Azure 门户检查资源时,可在“密钥和终结点”部分中找到此值 | - | `https://docs-test-001.openai.azure.com` |
| `AZURE_API_VERSION` | 可选 | Azure 的 API 版本,遵循 YYYY-MM-DD 格式 | 2023-08-01-preview | `2023-05-15`,查阅[最新版本][azure-api-verion-url] |
| `ACCESS_CODE` | 可选 | 添加访问此服务的密码,密码应为 6 位数字或字母 | - | `awCT74``e3@09!` |

> **Note**\
> 当你在服务端开启 `USE_AZURE_OPENAI` 后,用户将无法在前端配置中修改并使用 OpenAI key。
[azure-api-verion-url]: https://learn.microsoft.com/zh-cn/azure/ai-services/openai/reference#chat-completions
[azure-openai-url]: https://learn.microsoft.com/zh-cn/azure/ai-services/openai/concepts/models
[rfc]: https://github.com/lobehub/lobe-chat/discussions/178
4 changes: 3 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ const nextConfig = {
reactStrictMode: true,
pageExtensions: ['page.tsx', 'api.ts'],
transpilePackages: ['@lobehub/ui'],

env: {
USE_AZURE_OPENAI: process.env.USE_AZURE_OPENAI === '1',
},
webpack(config) {
config.experiments = {
asyncWebAssembly: true,
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
"dependencies": {
"@ant-design/colors": "^7",
"@ant-design/icons": "^5",
"@azure/openai": "latest",
"@emoji-mart/data": "^1",
"@emoji-mart/react": "^1",
"@icons-pack/react-simple-icons": "^9",
Expand Down
14 changes: 14 additions & 0 deletions src/config/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface ProcessEnv {
USE_AZURE_OPENAI?: boolean;
}
}
}

export const getClientConfig = () => {
return {
USE_AZURE_OPENAI: process.env.USE_AZURE_OPENAI,
};
};
5 changes: 5 additions & 0 deletions src/config/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ declare global {
interface ProcessEnv {
ACCESS_CODE?: string;
AZURE_API_KEY?: string;
AZURE_API_VERSION?: string;
OPENAI_API_KEY?: string;
OPENAI_PROXY_URL?: string;
}
Expand All @@ -17,7 +18,11 @@ export const getServerConfig = () => {

return {
ACCESS_CODE: process.env.ACCESS_CODE,
/* eslint-disable sort-keys-fix/sort-keys-fix */
AZURE_API_KEY: process.env.AZURE_API_KEY,
AZURE_API_VERSION: process.env.AZURE_API_VERSION,
/* eslint-enabled */

OPENAI_API_KEY: process.env.OPENAI_API_KEY,
OPENAI_PROXY_URL: process.env.OPENAI_PROXY_URL,
};
Expand Down
3 changes: 2 additions & 1 deletion src/const/settings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getClientConfig } from '@/config/client';
import { DEFAULT_OPENAI_MODEL_LIST } from '@/const/llm';
import { DEFAULT_AGENT_META } from '@/const/meta';
import { LanguageModel } from '@/types/llm';
Expand Down Expand Up @@ -36,8 +37,8 @@ export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
export const DEFAULT_LLM_CONFIG: GlobalLLMConfig = {
openAI: {
OPENAI_API_KEY: '',
azureApiVersion: '2023-08-01-preview',
models: DEFAULT_OPENAI_MODEL_LIST,
useAzure: getClientConfig().USE_AZURE_OPENAI,
},
};

Expand Down
1 change: 1 addition & 0 deletions src/locales/default/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default {
useAzure: {
desc: '使用 Azure 提供的 OpenAI 服务',
fetch: '获取列表',
serverConfig: '管理员在服务端配置开启了 Azure OpenAI,禁止切换',
title: 'Azure OpenAI',
},
},
Expand Down
16 changes: 6 additions & 10 deletions src/pages/api/openai/chat.api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import OpenAI from 'openai';

import { getClientConfig } from '@/config/client';
import { getOpenAIAuthFromRequest } from '@/const/fetch';
import { ChatErrorType, ErrorType } from '@/types/fetch';
import { ErrorType } from '@/types/fetch';
import { OpenAIStreamPayload } from '@/types/openai';

import { checkAuth } from '../auth';
Expand All @@ -24,17 +25,12 @@ export default async function handler(req: Request) {
}

let openai: OpenAI;
if (useAzure) {
if (!apiVersion) return createErrorResponse(ChatErrorType.BadRequest);

// `https://test-001.openai.azure.com/openai/deployments/gpt-35-turbo`,
const url = `${endpoint}/openai/deployments/${payload.model.replace('.', '')}`;
const { USE_AZURE_OPENAI } = getClientConfig();
const useAzureOpenAI = useAzure || USE_AZURE_OPENAI;

openai = createAzureOpenai({
apiVersion,
endpoint: url,
userApiKey: apiKey,
});
if (useAzureOpenAI) {
openai = createAzureOpenai({ apiVersion, endpoint, model: payload.model, userApiKey: apiKey });
} else {
openai = createOpenai(apiKey, endpoint);
}
Expand Down
22 changes: 13 additions & 9 deletions src/pages/api/openai/createAzureOpenai.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import OpenAI, { ClientOptions } from 'openai';
import urlJoin from 'url-join';

import { getServerConfig } from '@/config/server';

// 创建 Azure OpenAI 实例
export const createAzureOpenai = (params: {
apiVersion: string;
endpoint: string;
apiVersion?: string | null;
endpoint?: string | null;
model: string;
userApiKey?: string | null;
}) => {
const { AZURE_API_KEY } = getServerConfig();
const { OPENAI_PROXY_URL = '', AZURE_API_VERSION, AZURE_API_KEY } = getServerConfig();

const baseURL = params.endpoint;
const apiKey = !params.userApiKey ? AZURE_API_KEY : params.userApiKey;
const endpoint = !params.endpoint ? OPENAI_PROXY_URL : params.endpoint;
const baseURL = urlJoin(endpoint, `/openai/deployments/${params.model.replace('.', '')}`); // refs: https://test-001.openai.azure.com/openai/deployments/gpt-35-turbo

const defaultApiVersion = AZURE_API_VERSION || '2023-08-01-preview';
const apiVersion = !params.apiVersion ? defaultApiVersion : params.apiVersion;
const apiKey = !params.userApiKey ? AZURE_API_KEY ?? '' : params.userApiKey;

const config: ClientOptions = {
apiKey: apiKey,
apiKey,
baseURL,
defaultHeaders: { 'api-key': apiKey },
defaultQuery: {
'api-version': params.apiVersion,
},
defaultQuery: { 'api-version': apiVersion },
};

return new OpenAI(config);
Expand Down
13 changes: 12 additions & 1 deletion src/pages/settings/features/Settings/LLM/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { memo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';

import { getClientConfig } from '@/config/client';
import { FORM_STYLE } from '@/const/layoutTokens';
import { globalSelectors, useEffectAfterGlobalHydrated, useGlobalStore } from '@/store/global';

Expand Down Expand Up @@ -81,7 +82,17 @@ const LLM = memo(() => {
name: [configKey, 'openAI', 'endpoint'],
},
{
children: <Switch />,
children: (
<Switch disabled={getClientConfig().USE_AZURE_OPENAI} />
// <Flexbox gap={4}>
// <div>
//
// </div>
// {getClientConfig().USE_AZURE_OPENAI && (
// <div className={styles.tip}>{t('llm.OpenAI.useAzure.serverConfig')}</div>
// )}
// </Flexbox>
),
desc: t('llm.OpenAI.useAzure.desc'),
label: t('llm.OpenAI.useAzure.title'),
name: [configKey, 'openAI', 'useAzure'],
Expand Down
2 changes: 1 addition & 1 deletion src/services/_header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {

if (openai.useAzure) {
Object.assign(result, {
[AZURE_OPENAI_API_VERSION]: openai.azureApiVersion,
[AZURE_OPENAI_API_VERSION]: openai.azureApiVersion || '',
[USE_AZURE_OPENAI]: '1',
});
}
Expand Down
1 change: 0 additions & 1 deletion src/store/global/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ describe('globalSelectors', () => {
languageModel: {
openAI: {
OPENAI_API_KEY: 'openai-api-key',
azureApiVersion: '2023-08-01-preview',
endpoint: 'https://openai-endpoint.com',
models: ['gpt-3.5-turbo'],
},
Expand Down

0 comments on commit bda6732

Please sign in to comment.