From de6b79eeba48c76698f0f8a46a55ad4e58a4d181 Mon Sep 17 00:00:00 2001 From: Patrick Lamar <32331521+skyf0cker@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:52:17 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20support=20using=20?= =?UTF-8?q?env=20variable=20to=20set=20regions=20for=20OpenAI=20Edge=20Fun?= =?UTF-8?q?ctions.=20(#473)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: support using env variable to set preferredRegion for OpenAI edge function * fix: fix the import problems * ⚡️ feat: add preferredRegion support for tts and stt * ✅ test: add more unit test for config.ts and server.ts --------- Co-authored-by: vophan --- README.md | 11 +++++---- README.zh-CN.md | 11 +++++---- src/app/api/config.test.ts | 38 +++++++++++++++++++++++++++++ src/app/api/config.ts | 15 ++++++++++++ src/app/api/openai/chat/route.ts | 2 ++ src/app/api/openai/stt/route.ts | 2 ++ src/app/api/openai/tts/route.ts | 2 ++ src/config/__tests__/server.test.ts | 6 +++++ src/config/server.ts | 7 ++++++ 9 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 src/app/api/config.test.ts create mode 100644 src/app/api/config.ts diff --git a/README.md b/README.md index 79c4d01f7cad..3824ec2897fd 100644 --- a/README.md +++ b/README.md @@ -390,11 +390,12 @@ $ docker run -d -p 3210:3210 \ This project provides some additional configuration items set with environment variables: -| Environment Variable | Required | Description | Example | -| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `OPENAI_API_KEY` | Yes | This is the API key you apply on the OpenAI account page | `sk-xxxxxx...xxxxxx` | -| `OPENAI_PROXY_URL` | No | If you manually configure the OpenAI interface proxy, you can use this configuration item to override the default OpenAI API request base URL | `https://api.chatanywhere.cn/v1`
The default value is
`https://api.openai.com/v1` | -| `ACCESS_CODE` | No | Add a password to access this service; the password should be a 6-digit number or letter | `awCT74` or `e3@09!` | +| Environment Variable | Required | Description | Example | +| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `OPENAI_API_KEY` | Yes | This is the API key you apply on the OpenAI account page | `sk-xxxxxx...xxxxxx` | +| `OPENAI_PROXY_URL` | No | If you manually configure the OpenAI interface proxy, you can use this configuration item to override the default OpenAI API request base URL | `https://api.chatanywhere.cn/v1`
The default value is
`https://api.openai.com/v1` | +| `OPENAI_FUNCTION_REGIONS` | No | When you deploy Lobe-Chat using Vercel and need to specify the region for the Edge Function that handles requests to the OpenAI API, you can use this configuration. The value should be a comma-separated array of strings. | `iad1,sfo1` | +| `ACCESS_CODE` | No | Add a password to access this service; the password should be a 6-digit number or letter | `awCT74` or `e3@09!` | > \[!NOTE] > diff --git a/README.zh-CN.md b/README.zh-CN.md index fe37b67e8188..37dd1233c7ea 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -364,11 +364,12 @@ $ docker run -d -p 3210:3210 \ 本项目提供了一些额外的配置项,使用环境变量进行设置: -| 环境变量 | 类型 | 描述 | 示例 | -| ------------------ | ---- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -| `OPENAI_API_KEY` | 必选 | 这是你在 OpenAI 账户页面申请的 API 密钥 | `sk-xxxxxx...xxxxxx` | -| `OPENAI_PROXY_URL` | 可选 | 如果你手动配置了 OpenAI 接口代理,可以使用此配置项来覆盖默认的 OpenAI API 请求基础 URL | `https://api.chatanywhere.cn/v1`
默认值:
`https://api.openai.com/v1` | -| `ACCESS_CODE` | 可选 | 添加访问此服务的密码,密码应为 6 位数字或字母 | `awCT74` 或 `e3@09!` | +| 环境变量 | 类型 | 描述 | 示例 | +| ------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| `OPENAI_API_KEY` | 必选 | 这是你在 OpenAI 账户页面申请的 API 密钥 | `sk-xxxxxx...xxxxxx` | +| `OPENAI_PROXY_URL` | 可选 | 如果你手动配置了 OpenAI 接口代理,可以使用此配置项来覆盖默认的 OpenAI API 请求基础 URL | `https://api.chatanywhere.cn/v1`
默认值:
`https://api.openai.com/v1` | +| `OPENAI_FUNCTION_REGIONS` | 可选 | 当你使用 Vercel 部署 Lobe-Chat,而且有需求指定响应调用 OpenAI 接口的请求的 Edge Function 的 Region 时,可以使用该配置进行配置,该值的类型为逗号分隔的字符串数组 | `iad1,sfo1` | +| `ACCESS_CODE` | 可选 | 添加访问此服务的密码,密码应为 6 位数字或字母 | `awCT74` 或 `e3@09!` | > \[!NOTE] > diff --git a/src/app/api/config.test.ts b/src/app/api/config.test.ts new file mode 100644 index 000000000000..654f1391c915 --- /dev/null +++ b/src/app/api/config.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { getPreferredRegion } from './config'; + +// Stub the global process object to safely mock environment variables +vi.stubGlobal('process', { + ...process, // Preserve the original process object + env: { ...process.env }, // Clone the environment variables object for modification +}); + +describe('getPreferredRegion', () => { + beforeEach(() => { + // Reset environment variables before each test case + vi.restoreAllMocks(); + }); + + it('returns default value when get config error', () => { + const originalProcess = global.process; + // @ts-ignore + global.process = undefined; + const preferredRegion = getPreferredRegion(); + expect(preferredRegion).toBe('auto'); + + global.process = originalProcess; + }); + + it('return default value when preferredRegion is empty', () => { + process.env.OPENAI_FUNCTION_REGIONS = ''; + const preferredRegion = getPreferredRegion(); + expect(preferredRegion).toBe('auto'); + }); + + it('return correct list values when preferredRegion is correctly passed', () => { + process.env.OPENAI_FUNCTION_REGIONS = 'ida1,sfo1'; + const preferredRegion = getPreferredRegion(); + expect(preferredRegion).toStrictEqual(['ida1', 'sfo1']); + }); +}); diff --git a/src/app/api/config.ts b/src/app/api/config.ts new file mode 100644 index 000000000000..e12c12153a51 --- /dev/null +++ b/src/app/api/config.ts @@ -0,0 +1,15 @@ +import { getServerConfig } from '@/config/server'; + +export const getPreferredRegion = () => { + try { + const cfg = getServerConfig(); + if (cfg.OPENAI_FUNCTION_REGIONS.length <= 0) { + return 'auto'; + } + + return cfg.OPENAI_FUNCTION_REGIONS; + } catch (error) { + console.error('get server config failed, error:', error); + return 'auto'; + } +}; diff --git a/src/app/api/openai/chat/route.ts b/src/app/api/openai/chat/route.ts index 7d38c6e5cd71..ba9a6a589db8 100644 --- a/src/app/api/openai/chat/route.ts +++ b/src/app/api/openai/chat/route.ts @@ -1,9 +1,11 @@ import { OpenAIChatStreamPayload } from '@/types/openai/chat'; +import { getPreferredRegion } from '../../config'; import { createBizOpenAI } from '../createBizOpenAI'; import { createChatCompletion } from './createChatCompletion'; export const runtime = 'edge'; +export const preferredRegion = getPreferredRegion(); export const POST = async (req: Request) => { const payload = (await req.json()) as OpenAIChatStreamPayload; diff --git a/src/app/api/openai/stt/route.ts b/src/app/api/openai/stt/route.ts index 13e163d57eef..b927e5274de4 100644 --- a/src/app/api/openai/stt/route.ts +++ b/src/app/api/openai/stt/route.ts @@ -1,9 +1,11 @@ import { OpenAISTTPayload } from '@lobehub/tts'; import { createOpenaiAudioTranscriptions } from '@lobehub/tts/server'; +import { getPreferredRegion } from '../../config'; import { createBizOpenAI } from '../createBizOpenAI'; export const runtime = 'edge'; +export const preferredRegion = getPreferredRegion(); export const POST = async (req: Request) => { const formData = await req.formData(); diff --git a/src/app/api/openai/tts/route.ts b/src/app/api/openai/tts/route.ts index f878d75dd988..09954361bb25 100644 --- a/src/app/api/openai/tts/route.ts +++ b/src/app/api/openai/tts/route.ts @@ -1,9 +1,11 @@ import { OpenAITTSPayload } from '@lobehub/tts'; import { createOpenaiAudioSpeech } from '@lobehub/tts/server'; +import { getPreferredRegion } from '../../config'; import { createBizOpenAI } from '../createBizOpenAI'; export const runtime = 'edge'; +export const preferredRegion = getPreferredRegion(); export const POST = async (req: Request) => { const payload = (await req.json()) as OpenAITTSPayload; diff --git a/src/config/__tests__/server.test.ts b/src/config/__tests__/server.test.ts index 239b8c163f38..bda70cfcace4 100644 --- a/src/config/__tests__/server.test.ts +++ b/src/config/__tests__/server.test.ts @@ -39,6 +39,12 @@ describe('getServerConfig', () => { expect(config.USE_AZURE_OPENAI).toBe(false); }); + it('correctly handles values for OPENAI_FUNCTION_REGIONS', () => { + process.env.OPENAI_FUNCTION_REGIONS = 'iad1,sfo1'; + const config = getServerConfig(); + expect(config.OPENAI_FUNCTION_REGIONS).toStrictEqual(['iad1', 'sfo1']); + }); + it('returns default IMGUR_CLIENT_ID when no environment variable is set', () => { const config = getServerConfig(); expect(config.IMGUR_CLIENT_ID).toBe('e415f320d6e24f9'); diff --git a/src/config/server.ts b/src/config/server.ts index ce44d96ffcdb..a67ab4050c77 100644 --- a/src/config/server.ts +++ b/src/config/server.ts @@ -27,11 +27,18 @@ export const getServerConfig = () => { throw new Error('[Server Config] you are importing a nodejs-only module outside of nodejs'); } + // region format: iad1,sfo1 + let regions: string[] = []; + if (process.env.OPENAI_FUNCTION_REGIONS) { + regions = process.env.OPENAI_FUNCTION_REGIONS.split(','); + } + return { ACCESS_CODE: process.env.ACCESS_CODE, OPENAI_API_KEY: process.env.OPENAI_API_KEY, OPENAI_PROXY_URL: process.env.OPENAI_PROXY_URL, + OPENAI_FUNCTION_REGIONS: regions, AZURE_API_KEY: process.env.AZURE_API_KEY, AZURE_API_VERSION: process.env.AZURE_API_VERSION,