diff --git a/docs/deploy-with-azure-openai.zh-CN.md b/docs/deploy-with-azure-openai.zh-CN.md new file mode 100644 index 00000000..1213ea1f --- /dev/null +++ b/docs/deploy-with-azure-openai.zh-CN.md @@ -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 diff --git a/next.config.mjs b/next.config.mjs index 7172072c..10e4c8dd 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -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, diff --git a/package.json b/package.json index 00dafdd9..2e900a60 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/config/client.ts b/src/config/client.ts new file mode 100644 index 00000000..60eb3dff --- /dev/null +++ b/src/config/client.ts @@ -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, + }; +}; diff --git a/src/config/server.ts b/src/config/server.ts index 30079315..8e01469b 100644 --- a/src/config/server.ts +++ b/src/config/server.ts @@ -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; } @@ -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, }; diff --git a/src/const/settings.ts b/src/const/settings.ts index 32be379d..081f1ea8 100644 --- a/src/const/settings.ts +++ b/src/const/settings.ts @@ -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'; @@ -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, }, }; diff --git a/src/locales/default/setting.ts b/src/locales/default/setting.ts index 1863cb3f..d6396f7c 100644 --- a/src/locales/default/setting.ts +++ b/src/locales/default/setting.ts @@ -73,6 +73,7 @@ export default { useAzure: { desc: '使用 Azure 提供的 OpenAI 服务', fetch: '获取列表', + serverConfig: '管理员在服务端配置开启了 Azure OpenAI,禁止切换', title: 'Azure OpenAI', }, }, diff --git a/src/pages/api/openai/chat.api.ts b/src/pages/api/openai/chat.api.ts index d64ae7bb..3dd6dd34 100644 --- a/src/pages/api/openai/chat.api.ts +++ b/src/pages/api/openai/chat.api.ts @@ -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'; @@ -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); } diff --git a/src/pages/api/openai/createAzureOpenai.ts b/src/pages/api/openai/createAzureOpenai.ts index 1108a33b..a4a02c7b 100644 --- a/src/pages/api/openai/createAzureOpenai.ts +++ b/src/pages/api/openai/createAzureOpenai.ts @@ -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); diff --git a/src/pages/settings/features/Settings/LLM/index.tsx b/src/pages/settings/features/Settings/LLM/index.tsx index 1c61bc38..2799e20b 100644 --- a/src/pages/settings/features/Settings/LLM/index.tsx +++ b/src/pages/settings/features/Settings/LLM/index.tsx @@ -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'; @@ -81,7 +82,17 @@ const LLM = memo(() => { name: [configKey, 'openAI', 'endpoint'], }, { - children: , + children: ( + + // + //
+ // + //
+ // {getClientConfig().USE_AZURE_OPENAI && ( + //
{t('llm.OpenAI.useAzure.serverConfig')}
+ // )} + //
+ ), desc: t('llm.OpenAI.useAzure.desc'), label: t('llm.OpenAI.useAzure.title'), name: [configKey, 'openAI', 'useAzure'], diff --git a/src/services/_header.ts b/src/services/_header.ts index 82d79a4f..7111f49f 100644 --- a/src/services/_header.ts +++ b/src/services/_header.ts @@ -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', }); } diff --git a/src/store/global/selectors.test.ts b/src/store/global/selectors.test.ts index 4979b981..ea2f2a8a 100644 --- a/src/store/global/selectors.test.ts +++ b/src/store/global/selectors.test.ts @@ -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'], },