@lobehub/chat 1.106.8 → 1.107.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +9 -0
- package/CHANGELOG.md +50 -0
- package/Dockerfile +2 -0
- package/Dockerfile.database +2 -0
- package/Dockerfile.pglite +2 -0
- package/changelog/v1.json +18 -0
- package/docs/usage/providers/aihubmix.zh-CN.mdx +101 -0
- package/locales/ar/modelProvider.json +1 -0
- package/locales/ar/models.json +21 -0
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/modelProvider.json +1 -0
- package/locales/bg-BG/models.json +21 -0
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/modelProvider.json +1 -0
- package/locales/de-DE/models.json +21 -0
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/modelProvider.json +1 -0
- package/locales/en-US/models.json +21 -0
- package/locales/en-US/providers.json +3 -0
- package/locales/es-ES/modelProvider.json +1 -0
- package/locales/es-ES/models.json +21 -0
- package/locales/es-ES/providers.json +3 -0
- package/locales/fa-IR/modelProvider.json +1 -0
- package/locales/fa-IR/models.json +21 -0
- package/locales/fa-IR/providers.json +3 -0
- package/locales/fr-FR/modelProvider.json +1 -0
- package/locales/fr-FR/models.json +21 -0
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/modelProvider.json +1 -0
- package/locales/it-IT/models.json +21 -0
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/modelProvider.json +1 -0
- package/locales/ja-JP/models.json +21 -0
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/modelProvider.json +1 -0
- package/locales/ko-KR/models.json +21 -0
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/modelProvider.json +1 -0
- package/locales/nl-NL/models.json +21 -0
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/modelProvider.json +1 -0
- package/locales/pl-PL/models.json +21 -0
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/modelProvider.json +1 -0
- package/locales/pt-BR/models.json +21 -0
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/modelProvider.json +1 -0
- package/locales/ru-RU/models.json +21 -0
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/modelProvider.json +1 -0
- package/locales/tr-TR/models.json +21 -0
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/modelProvider.json +1 -0
- package/locales/vi-VN/models.json +21 -0
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/modelProvider.json +1 -0
- package/locales/zh-CN/models.json +21 -0
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-TW/modelProvider.json +1 -0
- package/locales/zh-TW/models.json +21 -0
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +2 -3
- package/scripts/cdnWorkflow/utils.ts +1 -1
- package/src/app/(backend)/middleware/auth/index.ts +2 -2
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +12 -12
- package/src/app/(backend)/webapi/chat/[provider]/route.ts +6 -6
- package/src/app/(backend)/webapi/chat/vertexai/route.ts +2 -2
- package/src/app/(backend)/webapi/models/[provider]/pull/route.ts +2 -2
- package/src/app/(backend)/webapi/models/[provider]/route.ts +2 -2
- package/src/app/(backend)/webapi/text-to-image/[provider]/route.ts +2 -2
- package/src/app/[variants]/(main)/settings/provider/(detail)/github/page.tsx +2 -2
- package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +17 -2
- package/src/config/aiModels/aihubmix.ts +164 -0
- package/src/config/aiModels/index.ts +3 -0
- package/src/config/llm.ts +6 -0
- package/src/config/modelProviders/aihubmix.ts +18 -0
- package/src/config/modelProviders/huggingface.ts +1 -0
- package/src/config/modelProviders/index.ts +4 -0
- package/src/libs/model-runtime/ModelRuntime.test.ts +9 -10
- package/src/libs/model-runtime/ModelRuntime.ts +2 -3
- package/src/libs/model-runtime/RouterRuntime/baseRuntimeMap.ts +15 -0
- package/src/libs/model-runtime/RouterRuntime/createRuntime.ts +193 -0
- package/src/libs/model-runtime/RouterRuntime/index.ts +9 -0
- package/src/libs/model-runtime/aihubmix/index.ts +118 -0
- package/src/libs/model-runtime/index.ts +1 -1
- package/src/libs/model-runtime/openrouter/index.ts +2 -2
- package/src/libs/model-runtime/runtimeMap.ts +2 -0
- package/src/libs/model-runtime/types/type.ts +1 -0
- package/src/libs/trpc/client/async.ts +2 -1
- package/src/libs/trpc/client/edge.ts +2 -1
- package/src/libs/trpc/client/lambda.ts +4 -2
- package/src/libs/trpc/client/tools.ts +2 -1
- package/src/locales/default/modelProvider.ts +1 -0
- package/src/server/modules/{AgentRuntime → ModelRuntime}/index.test.ts +64 -67
- package/src/server/modules/{AgentRuntime → ModelRuntime}/index.ts +3 -3
- package/src/server/routers/async/file.ts +2 -2
- package/src/server/routers/async/image.ts +2 -2
- package/src/server/routers/async/ragEval.ts +2 -2
- package/src/server/routers/lambda/chunk.ts +3 -3
- package/src/services/__tests__/chat.test.ts +21 -21
- package/src/services/chat.ts +2 -2
- package/src/types/aiProvider.ts +1 -0
- package/src/types/llm.ts +4 -0
- package/src/types/user/settings/keyVaults.ts +1 -0
- package/src/app/[variants]/(main)/settings/provider/(detail)/huggingface/page.tsx +0 -67
- /package/src/server/modules/{AgentRuntime → ModelRuntime}/apiKeyManager.test.ts +0 -0
- /package/src/server/modules/{AgentRuntime → ModelRuntime}/apiKeyManager.ts +0 -0
- /package/src/server/modules/{AgentRuntime → ModelRuntime}/trace.ts +0 -0
@@ -3,7 +3,7 @@ import { NextResponse } from 'next/server';
|
|
3
3
|
import { checkAuth } from '@/app/(backend)/middleware/auth';
|
4
4
|
import { ChatCompletionErrorPayload } from '@/libs/model-runtime';
|
5
5
|
import { TextToImagePayload } from '@/libs/model-runtime/types';
|
6
|
-
import {
|
6
|
+
import { initModelRuntimeWithUserPayload } from '@/server/modules/ModelRuntime';
|
7
7
|
import { ChatErrorType } from '@/types/fetch';
|
8
8
|
import { createErrorResponse } from '@/utils/errorResponse';
|
9
9
|
|
@@ -52,7 +52,7 @@ export const POST = checkAuth(async (req: Request, { params, jwtPayload }) => {
|
|
52
52
|
|
53
53
|
try {
|
54
54
|
// ============ 1. init chat model ============ //
|
55
|
-
const agentRuntime = await
|
55
|
+
const agentRuntime = await initModelRuntimeWithUserPayload(provider, jwtPayload);
|
56
56
|
|
57
57
|
// ============ 2. create chat completion ============ //
|
58
58
|
|
@@ -48,10 +48,10 @@ const useProviderCard = (): ProviderItem => {
|
|
48
48
|
),
|
49
49
|
desc: (
|
50
50
|
<Markdown className={styles.markdown} fontSize={12} variant={'chat'}>
|
51
|
-
{t(
|
51
|
+
{t('github.personalAccessToken.desc')}
|
52
52
|
</Markdown>
|
53
53
|
),
|
54
|
-
label: t(
|
54
|
+
label: t('github.personalAccessToken.title'),
|
55
55
|
name: [KeyVaultsConfigKey, LLMProviderApiTokenKey],
|
56
56
|
},
|
57
57
|
],
|
@@ -97,6 +97,7 @@ const useStyles = createStyles(({ css, prefixCls, responsive, token }) => ({
|
|
97
97
|
|
98
98
|
export interface ProviderConfigProps extends Omit<AiProviderDetailItem, 'enabled' | 'source'> {
|
99
99
|
apiKeyItems?: FormItemProps[];
|
100
|
+
apiKeyUrl?: string;
|
100
101
|
canDeactivate?: boolean;
|
101
102
|
checkErrorRender?: CheckErrorRender;
|
102
103
|
className?: string;
|
@@ -127,6 +128,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
127
128
|
showAceGcm = true,
|
128
129
|
extra,
|
129
130
|
source = AiProviderSourceEnum.Builtin,
|
131
|
+
apiKeyUrl,
|
130
132
|
}) => {
|
131
133
|
const {
|
132
134
|
proxyUrl,
|
@@ -184,7 +186,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
184
186
|
) : (
|
185
187
|
<FormPassword
|
186
188
|
autoComplete={'new-password'}
|
187
|
-
placeholder={t(
|
189
|
+
placeholder={t('providerModels.config.apiKey.placeholder', { name })}
|
188
190
|
suffix={
|
189
191
|
configUpdating && (
|
190
192
|
<Icon icon={Loader2Icon} spin style={{ color: theme.colorTextTertiary }} />
|
@@ -192,7 +194,20 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
192
194
|
}
|
193
195
|
/>
|
194
196
|
),
|
195
|
-
desc:
|
197
|
+
desc: apiKeyUrl ? (
|
198
|
+
<Trans
|
199
|
+
i18nKey="providerModels.config.apiKey.descWithUrl"
|
200
|
+
ns={'modelProvider'}
|
201
|
+
value={{ name }}
|
202
|
+
>
|
203
|
+
请填写你的 {{ name }} API Key,
|
204
|
+
<Link href={apiKeyUrl} target={'_blank'}>
|
205
|
+
点此获取
|
206
|
+
</Link>
|
207
|
+
</Trans>
|
208
|
+
) : (
|
209
|
+
t(`providerModels.config.apiKey.desc`, { name })
|
210
|
+
),
|
196
211
|
label: t(`providerModels.config.apiKey.title`),
|
197
212
|
name: [KeyVaultsConfigKey, LLMProviderApiTokenKey],
|
198
213
|
},
|
@@ -0,0 +1,164 @@
|
|
1
|
+
import { AIChatModelCard } from '@/types/aiModel';
|
2
|
+
|
3
|
+
const aihubmixModels: AIChatModelCard[] = [
|
4
|
+
{
|
5
|
+
abilities: {
|
6
|
+
functionCall: true,
|
7
|
+
reasoning: true,
|
8
|
+
},
|
9
|
+
contextWindowTokens: 65_536,
|
10
|
+
description: 'DeepSeek R1 推理模型,具有强大的推理能力',
|
11
|
+
displayName: 'DeepSeek R1',
|
12
|
+
enabled: true,
|
13
|
+
id: 'DeepSeek-R1',
|
14
|
+
type: 'chat',
|
15
|
+
},
|
16
|
+
{
|
17
|
+
abilities: {
|
18
|
+
functionCall: true,
|
19
|
+
reasoning: true,
|
20
|
+
vision: true,
|
21
|
+
},
|
22
|
+
contextWindowTokens: 200_000,
|
23
|
+
description:
|
24
|
+
'Claude Opus 4 是 Anthropic 迄今为止最强大的模型,专为处理复杂、长时间运行的任务而设计。',
|
25
|
+
displayName: 'Claude Opus 4',
|
26
|
+
enabled: true,
|
27
|
+
id: 'claude-opus-4-20250514',
|
28
|
+
type: 'chat',
|
29
|
+
},
|
30
|
+
{
|
31
|
+
abilities: {
|
32
|
+
functionCall: true,
|
33
|
+
reasoning: true,
|
34
|
+
vision: true,
|
35
|
+
},
|
36
|
+
contextWindowTokens: 200_000,
|
37
|
+
description:
|
38
|
+
'Claude Sonnet 4 是一款高效且性价比高的模型,作为 Claude Sonnet 3.7 的升级版,适合日常任务和中等复杂度的应用。',
|
39
|
+
displayName: 'Claude Sonnet 4',
|
40
|
+
enabled: true,
|
41
|
+
id: 'claude-sonnet-4-20250514',
|
42
|
+
type: 'chat',
|
43
|
+
},
|
44
|
+
{
|
45
|
+
abilities: {
|
46
|
+
functionCall: true,
|
47
|
+
reasoning: true,
|
48
|
+
vision: true,
|
49
|
+
},
|
50
|
+
contextWindowTokens: 200_000,
|
51
|
+
description: 'OpenAI o3 推理模型,具有强大的推理能力',
|
52
|
+
displayName: 'o3',
|
53
|
+
enabled: true,
|
54
|
+
id: 'o3',
|
55
|
+
type: 'chat',
|
56
|
+
},
|
57
|
+
{
|
58
|
+
abilities: {
|
59
|
+
functionCall: true,
|
60
|
+
reasoning: true,
|
61
|
+
vision: true,
|
62
|
+
},
|
63
|
+
contextWindowTokens: 200_000,
|
64
|
+
description: 'OpenAI o4-mini 小型推理模型,高效且经济',
|
65
|
+
displayName: 'o4-mini',
|
66
|
+
enabled: true,
|
67
|
+
id: 'o4-mini',
|
68
|
+
type: 'chat',
|
69
|
+
},
|
70
|
+
{
|
71
|
+
abilities: {
|
72
|
+
functionCall: true,
|
73
|
+
vision: true,
|
74
|
+
},
|
75
|
+
contextWindowTokens: 1_047_576,
|
76
|
+
description: 'GPT-4.1 旗舰模型,适用于复杂任务',
|
77
|
+
displayName: 'GPT-4.1',
|
78
|
+
enabled: true,
|
79
|
+
id: 'gpt-4.1',
|
80
|
+
type: 'chat',
|
81
|
+
},
|
82
|
+
{
|
83
|
+
abilities: {
|
84
|
+
functionCall: true,
|
85
|
+
vision: true,
|
86
|
+
},
|
87
|
+
contextWindowTokens: 1_047_576,
|
88
|
+
description: 'GPT-4.1 mini 平衡智能、速度和成本',
|
89
|
+
displayName: 'GPT-4.1 mini',
|
90
|
+
enabled: true,
|
91
|
+
id: 'gpt-4.1-mini',
|
92
|
+
type: 'chat',
|
93
|
+
},
|
94
|
+
{
|
95
|
+
abilities: {
|
96
|
+
functionCall: true,
|
97
|
+
reasoning: true,
|
98
|
+
search: true,
|
99
|
+
vision: true,
|
100
|
+
},
|
101
|
+
contextWindowTokens: 1_048_576 + 65_536,
|
102
|
+
description:
|
103
|
+
'Gemini 2.5 Pro 是 Google 最先进的思维模型,能够对代码、数学和STEM领域的复杂问题进行推理,以及使用长上下文分析大型数据集、代码库和文档。',
|
104
|
+
displayName: 'Gemini 2.5 Pro',
|
105
|
+
enabled: true,
|
106
|
+
id: 'gemini-2.5-pro',
|
107
|
+
maxOutput: 65_536,
|
108
|
+
pricing: {
|
109
|
+
input: 1.25, // prompts <= 200k tokens
|
110
|
+
output: 10, // prompts <= 200k tokens
|
111
|
+
},
|
112
|
+
releasedAt: '2025-06-17',
|
113
|
+
settings: {
|
114
|
+
extendParams: ['thinkingBudget'],
|
115
|
+
searchImpl: 'params',
|
116
|
+
searchProvider: 'google',
|
117
|
+
},
|
118
|
+
type: 'chat',
|
119
|
+
},
|
120
|
+
{
|
121
|
+
abilities: {
|
122
|
+
functionCall: true,
|
123
|
+
reasoning: true,
|
124
|
+
search: true,
|
125
|
+
vision: true,
|
126
|
+
},
|
127
|
+
contextWindowTokens: 1_000_000,
|
128
|
+
description: 'Gemini 2.5 Flash 预览版,快速高效的多模态模型',
|
129
|
+
displayName: 'Gemini 2.5 Flash',
|
130
|
+
enabled: true,
|
131
|
+
id: 'gemini-2.5-flash',
|
132
|
+
releasedAt: '2025-06-17',
|
133
|
+
settings: {
|
134
|
+
extendParams: ['thinkingBudget'],
|
135
|
+
searchImpl: 'params',
|
136
|
+
searchProvider: 'google',
|
137
|
+
},
|
138
|
+
type: 'chat',
|
139
|
+
},
|
140
|
+
{
|
141
|
+
abilities: {
|
142
|
+
functionCall: true,
|
143
|
+
},
|
144
|
+
contextWindowTokens: 235_000,
|
145
|
+
description: 'Qwen3 235B 大型语言模型',
|
146
|
+
displayName: 'Qwen3 235B',
|
147
|
+
enabled: true,
|
148
|
+
id: 'Qwen/Qwen3-235B-A22B',
|
149
|
+
type: 'chat',
|
150
|
+
},
|
151
|
+
{
|
152
|
+
abilities: {
|
153
|
+
functionCall: true,
|
154
|
+
},
|
155
|
+
contextWindowTokens: 32_000,
|
156
|
+
description: 'Qwen3 32B 中型语言模型',
|
157
|
+
displayName: 'Qwen3 32B',
|
158
|
+
enabled: true,
|
159
|
+
id: 'Qwen/Qwen3-32B',
|
160
|
+
type: 'chat',
|
161
|
+
},
|
162
|
+
];
|
163
|
+
|
164
|
+
export default aihubmixModels;
|
@@ -2,6 +2,7 @@ import { AiFullModelCard, LobeDefaultAiModelListItem } from '@/types/aiModel';
|
|
2
2
|
|
3
3
|
import { default as ai21 } from './ai21';
|
4
4
|
import { default as ai360 } from './ai360';
|
5
|
+
import { default as aihubmix } from './aihubmix';
|
5
6
|
import { default as anthropic } from './anthropic';
|
6
7
|
import { default as azure } from './azure';
|
7
8
|
import { default as azureai } from './azureai';
|
@@ -78,6 +79,7 @@ const buildDefaultModelList = (map: ModelsMap): LobeDefaultAiModelListItem[] =>
|
|
78
79
|
export const LOBE_DEFAULT_MODEL_LIST = buildDefaultModelList({
|
79
80
|
ai21,
|
80
81
|
ai360,
|
82
|
+
aihubmix,
|
81
83
|
anthropic,
|
82
84
|
azure,
|
83
85
|
azureai,
|
@@ -135,6 +137,7 @@ export const LOBE_DEFAULT_MODEL_LIST = buildDefaultModelList({
|
|
135
137
|
|
136
138
|
export { default as ai21 } from './ai21';
|
137
139
|
export { default as ai360 } from './ai360';
|
140
|
+
export { default as aihubmix } from './aihubmix';
|
138
141
|
export { default as anthropic } from './anthropic';
|
139
142
|
export { default as azure } from './azure';
|
140
143
|
export { default as azureai } from './azureai';
|
package/src/config/llm.ts
CHANGED
@@ -171,6 +171,9 @@ export const getLLMConfig = () => {
|
|
171
171
|
|
172
172
|
ENABLED_V0: z.boolean(),
|
173
173
|
V0_API_KEY: z.string().optional(),
|
174
|
+
|
175
|
+
ENABLED_AIHUBMIX: z.boolean(),
|
176
|
+
AIHUBMIX_API_KEY: z.string().optional(),
|
174
177
|
},
|
175
178
|
runtimeEnv: {
|
176
179
|
API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
|
@@ -340,6 +343,9 @@ export const getLLMConfig = () => {
|
|
340
343
|
|
341
344
|
ENABLED_V0: !!process.env.V0_API_KEY,
|
342
345
|
V0_API_KEY: process.env.V0_API_KEY,
|
346
|
+
|
347
|
+
ENABLED_AIHUBMIX: !!process.env.AIHUBMIX_API_KEY,
|
348
|
+
AIHUBMIX_API_KEY: process.env.AIHUBMIX_API_KEY,
|
343
349
|
},
|
344
350
|
});
|
345
351
|
};
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { ModelProviderCard } from '@/types/llm';
|
2
|
+
|
3
|
+
const AiHubMix: ModelProviderCard = {
|
4
|
+
apiKeyUrl: 'https://lobe.li/9mZhb4T',
|
5
|
+
chatModels: [],
|
6
|
+
checkModel: 'gpt-4.1-mini',
|
7
|
+
description: 'AiHubMix 通过统一的 API 接口提供对多种 AI 模型的访问。',
|
8
|
+
id: 'aihubmix',
|
9
|
+
modelsUrl: 'https://docs.aihubmix.com/cn/api/Model-List',
|
10
|
+
name: 'AiHubMix',
|
11
|
+
settings: {
|
12
|
+
sdkType: 'router',
|
13
|
+
showModelFetcher: true,
|
14
|
+
},
|
15
|
+
url: 'https://aihubmix.com?utm_source=lobehub',
|
16
|
+
};
|
17
|
+
|
18
|
+
export default AiHubMix;
|
@@ -2,6 +2,7 @@ import { ChatModelCard, ModelProviderCard } from '@/types/llm';
|
|
2
2
|
|
3
3
|
import Ai21Provider from './ai21';
|
4
4
|
import Ai360Provider from './ai360';
|
5
|
+
import AiHubMixProvider from './aihubmix';
|
5
6
|
import AnthropicProvider from './anthropic';
|
6
7
|
import AzureProvider from './azure';
|
7
8
|
import AzureAIProvider from './azureai';
|
@@ -94,6 +95,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [
|
|
94
95
|
TaichuProvider.chatModels,
|
95
96
|
CloudflareProvider.chatModels,
|
96
97
|
Ai360Provider.chatModels,
|
98
|
+
AiHubMixProvider.chatModels,
|
97
99
|
SiliconCloudProvider.chatModels,
|
98
100
|
GiteeAIProvider.chatModels,
|
99
101
|
UpstageProvider.chatModels,
|
@@ -163,6 +165,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
|
|
163
165
|
GiteeAIProvider,
|
164
166
|
TaichuProvider,
|
165
167
|
Ai360Provider,
|
168
|
+
AiHubMixProvider,
|
166
169
|
Search1APIProvider,
|
167
170
|
InfiniAIProvider,
|
168
171
|
QiniuProvider,
|
@@ -179,6 +182,7 @@ export const isProviderDisableBrowserRequest = (id: string) => {
|
|
179
182
|
|
180
183
|
export { default as Ai21ProviderCard } from './ai21';
|
181
184
|
export { default as Ai360ProviderCard } from './ai360';
|
185
|
+
export { default as AiHubMixProviderCard } from './aihubmix';
|
182
186
|
export { default as AnthropicProviderCard } from './anthropic';
|
183
187
|
export { default as AzureProviderCard } from './azure';
|
184
188
|
export { default as AzureAIProviderCard } from './azureai';
|
@@ -6,10 +6,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
import * as langfuseCfg from '@/config/langfuse';
|
7
7
|
import { ClientSecretPayload } from '@/const/auth';
|
8
8
|
import { TraceNameMap } from '@/const/trace';
|
9
|
-
import {
|
9
|
+
import { ChatStreamPayload, LobeOpenAI, ModelProvider, ModelRuntime } from '@/libs/model-runtime';
|
10
10
|
import { providerRuntimeMap } from '@/libs/model-runtime/runtimeMap';
|
11
11
|
import { CreateImagePayload } from '@/libs/model-runtime/types/image';
|
12
|
-
import { createTraceOptions } from '@/server/modules/
|
12
|
+
import { createTraceOptions } from '@/server/modules/ModelRuntime';
|
13
13
|
|
14
14
|
import { AgentChatOptions } from './ModelRuntime';
|
15
15
|
|
@@ -52,7 +52,7 @@ const testRuntime = (providerId: string, payload?: any) => {
|
|
52
52
|
describe(`${providerId} provider runtime`, () => {
|
53
53
|
it('should initialize correctly', async () => {
|
54
54
|
const jwtPayload: ClientSecretPayload = { apiKey: 'user-key', ...payload };
|
55
|
-
const runtime = await
|
55
|
+
const runtime = await ModelRuntime.initializeWithProvider(providerId, jwtPayload);
|
56
56
|
|
57
57
|
// @ts-ignore
|
58
58
|
expect(runtime['_runtime']).toBeInstanceOf(providerRuntimeMap[providerId]);
|
@@ -64,16 +64,15 @@ const testRuntime = (providerId: string, payload?: any) => {
|
|
64
64
|
});
|
65
65
|
};
|
66
66
|
|
67
|
-
let mockModelRuntime:
|
67
|
+
let mockModelRuntime: ModelRuntime;
|
68
68
|
beforeEach(async () => {
|
69
69
|
const jwtPayload: ClientSecretPayload = { apiKey: 'user-openai-key', baseURL: 'user-endpoint' };
|
70
|
-
mockModelRuntime = await
|
70
|
+
mockModelRuntime = await ModelRuntime.initializeWithProvider(ModelProvider.OpenAI, jwtPayload);
|
71
71
|
});
|
72
72
|
|
73
|
-
describe('
|
73
|
+
describe('ModelRuntime', () => {
|
74
74
|
describe('should initialize with various providers', () => {
|
75
75
|
const providers = Object.values(ModelProvider).filter((i) => i !== 'lobehub');
|
76
|
-
|
77
76
|
const specialProviderIds = [ModelProvider.VertexAI, ...specialProviders.map((p) => p.id)];
|
78
77
|
|
79
78
|
const generalTestProviders = providers.filter(
|
@@ -87,7 +86,7 @@ describe('AgentRuntime', () => {
|
|
87
86
|
specialProviders.forEach(({ id, payload }) => testRuntime(id, payload));
|
88
87
|
});
|
89
88
|
|
90
|
-
describe('
|
89
|
+
describe('ModelRuntime chat method', () => {
|
91
90
|
it('should run correctly', async () => {
|
92
91
|
const payload: ChatStreamPayload = {
|
93
92
|
messages: [{ role: 'user', content: 'Hello, world!' }],
|
@@ -243,7 +242,7 @@ describe('AgentRuntime', () => {
|
|
243
242
|
});
|
244
243
|
});
|
245
244
|
|
246
|
-
describe('
|
245
|
+
describe('ModelRuntime createImage method', () => {
|
247
246
|
it('should run correctly', async () => {
|
248
247
|
const payload: CreateImagePayload = {
|
249
248
|
model: 'dall-e-3',
|
@@ -292,7 +291,7 @@ describe('AgentRuntime', () => {
|
|
292
291
|
});
|
293
292
|
});
|
294
293
|
|
295
|
-
describe('
|
294
|
+
describe('ModelRuntime models method', () => {
|
296
295
|
it('should run correctly', async () => {
|
297
296
|
const mockModels = [
|
298
297
|
{ id: 'gpt-4', name: 'GPT-4' },
|
@@ -25,7 +25,7 @@ export interface AgentChatOptions {
|
|
25
25
|
trace?: TracePayload;
|
26
26
|
}
|
27
27
|
|
28
|
-
class ModelRuntime {
|
28
|
+
export class ModelRuntime {
|
29
29
|
private _runtime: LobeRuntimeAI;
|
30
30
|
|
31
31
|
constructor(runtime: LobeRuntimeAI) {
|
@@ -113,10 +113,9 @@ class ModelRuntime {
|
|
113
113
|
) {
|
114
114
|
// @ts-expect-error runtime map not include vertex so it will be undefined
|
115
115
|
const providerAI = providerRuntimeMap[provider] ?? LobeOpenAI;
|
116
|
+
|
116
117
|
const runtimeModel: LobeRuntimeAI = new providerAI(params);
|
117
118
|
|
118
119
|
return new ModelRuntime(runtimeModel);
|
119
120
|
}
|
120
121
|
}
|
121
|
-
|
122
|
-
export default ModelRuntime;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { LobeAnthropicAI } from '../anthropic';
|
2
|
+
import { LobeAzureAI } from '../azureai';
|
3
|
+
import { LobeCloudflareAI } from '../cloudflare';
|
4
|
+
import { LobeFalAI } from '../fal';
|
5
|
+
import { LobeGoogleAI } from '../google';
|
6
|
+
import { LobeOpenAI } from '../openai';
|
7
|
+
|
8
|
+
export const baseRuntimeMap = {
|
9
|
+
anthropic: LobeAnthropicAI,
|
10
|
+
azure: LobeAzureAI,
|
11
|
+
cloudflare: LobeCloudflareAI,
|
12
|
+
fal: LobeFalAI,
|
13
|
+
google: LobeGoogleAI,
|
14
|
+
openai: LobeOpenAI,
|
15
|
+
};
|
@@ -0,0 +1,193 @@
|
|
1
|
+
/**
|
2
|
+
* @see https://github.com/lobehub/lobe-chat/discussions/6563
|
3
|
+
*/
|
4
|
+
import OpenAI, { ClientOptions } from 'openai';
|
5
|
+
import { Stream } from 'openai/streaming';
|
6
|
+
|
7
|
+
import { ILobeAgentRuntimeErrorType } from '@/libs/model-runtime';
|
8
|
+
import { CreateImagePayload, CreateImageResponse } from '@/libs/model-runtime/types/image';
|
9
|
+
import {
|
10
|
+
CreateImageOptions,
|
11
|
+
CustomClientOptions,
|
12
|
+
} from '@/libs/model-runtime/utils/openaiCompatibleFactory';
|
13
|
+
import type { ChatModelCard } from '@/types/llm';
|
14
|
+
|
15
|
+
import { LobeRuntimeAI } from '../BaseAI';
|
16
|
+
import { LobeOpenAI } from '../openai';
|
17
|
+
import {
|
18
|
+
type ChatCompletionErrorPayload,
|
19
|
+
ChatMethodOptions,
|
20
|
+
ChatStreamCallbacks,
|
21
|
+
ChatStreamPayload,
|
22
|
+
EmbeddingsOptions,
|
23
|
+
EmbeddingsPayload,
|
24
|
+
TextToImagePayload,
|
25
|
+
TextToSpeechPayload,
|
26
|
+
} from '../types';
|
27
|
+
import { baseRuntimeMap } from './baseRuntimeMap';
|
28
|
+
|
29
|
+
export interface RuntimeItem {
|
30
|
+
id: string;
|
31
|
+
models?: string[];
|
32
|
+
runtime: LobeRuntimeAI;
|
33
|
+
}
|
34
|
+
|
35
|
+
interface ProviderIniOptions extends Record<string, any> {
|
36
|
+
accessKeyId?: string;
|
37
|
+
accessKeySecret?: string;
|
38
|
+
apiKey?: string;
|
39
|
+
apiVersion?: string;
|
40
|
+
baseURL?: string;
|
41
|
+
baseURLOrAccountID?: string;
|
42
|
+
dangerouslyAllowBrowser?: boolean;
|
43
|
+
region?: string;
|
44
|
+
sessionToken?: string;
|
45
|
+
}
|
46
|
+
|
47
|
+
interface RouterInstance {
|
48
|
+
apiType: keyof typeof baseRuntimeMap;
|
49
|
+
models?: string[];
|
50
|
+
options: ProviderIniOptions;
|
51
|
+
runtime?: typeof LobeOpenAI;
|
52
|
+
}
|
53
|
+
|
54
|
+
type ConstructorOptions<T extends Record<string, any> = any> = ClientOptions & T;
|
55
|
+
|
56
|
+
interface CreateRouterRuntimeOptions<T extends Record<string, any> = any> {
|
57
|
+
apiKey?: string;
|
58
|
+
chatCompletion?: {
|
59
|
+
excludeUsage?: boolean;
|
60
|
+
handleError?: (
|
61
|
+
error: any,
|
62
|
+
options: ConstructorOptions<T>,
|
63
|
+
) => Omit<ChatCompletionErrorPayload, 'provider'> | undefined;
|
64
|
+
handlePayload?: (
|
65
|
+
payload: ChatStreamPayload,
|
66
|
+
options: ConstructorOptions<T>,
|
67
|
+
) => OpenAI.ChatCompletionCreateParamsStreaming;
|
68
|
+
handleStream?: (
|
69
|
+
stream: Stream<OpenAI.ChatCompletionChunk> | ReadableStream,
|
70
|
+
{ callbacks, inputStartAt }: { callbacks?: ChatStreamCallbacks; inputStartAt?: number },
|
71
|
+
) => ReadableStream;
|
72
|
+
handleStreamBizErrorType?: (error: {
|
73
|
+
message: string;
|
74
|
+
name: string;
|
75
|
+
}) => ILobeAgentRuntimeErrorType | undefined;
|
76
|
+
handleTransformResponseToStream?: (
|
77
|
+
data: OpenAI.ChatCompletion,
|
78
|
+
) => ReadableStream<OpenAI.ChatCompletionChunk>;
|
79
|
+
noUserId?: boolean;
|
80
|
+
};
|
81
|
+
constructorOptions?: ConstructorOptions<T>;
|
82
|
+
createImage?: (
|
83
|
+
payload: CreateImagePayload,
|
84
|
+
options: CreateImageOptions,
|
85
|
+
) => Promise<CreateImageResponse>;
|
86
|
+
customClient?: CustomClientOptions<T>;
|
87
|
+
debug?: {
|
88
|
+
chatCompletion: () => boolean;
|
89
|
+
responses?: () => boolean;
|
90
|
+
};
|
91
|
+
errorType?: {
|
92
|
+
bizError: ILobeAgentRuntimeErrorType;
|
93
|
+
invalidAPIKey: ILobeAgentRuntimeErrorType;
|
94
|
+
};
|
95
|
+
id: string;
|
96
|
+
models?:
|
97
|
+
| ((params: { client: OpenAI }) => Promise<ChatModelCard[]>)
|
98
|
+
| {
|
99
|
+
transformModel?: (model: OpenAI.Model) => ChatModelCard;
|
100
|
+
};
|
101
|
+
responses?: {
|
102
|
+
handlePayload?: (
|
103
|
+
payload: ChatStreamPayload,
|
104
|
+
options: ConstructorOptions<T>,
|
105
|
+
) => ChatStreamPayload;
|
106
|
+
};
|
107
|
+
routers: RouterInstance[];
|
108
|
+
}
|
109
|
+
|
110
|
+
export const createRouterRuntime = ({
|
111
|
+
id,
|
112
|
+
routers,
|
113
|
+
apiKey: DEFAULT_API_LEY,
|
114
|
+
...params
|
115
|
+
}: CreateRouterRuntimeOptions) => {
|
116
|
+
return class UniformRuntime implements LobeRuntimeAI {
|
117
|
+
private _runtimes: RuntimeItem[];
|
118
|
+
private _options: ClientOptions & Record<string, any>;
|
119
|
+
|
120
|
+
constructor(options: ClientOptions & Record<string, any> = {}) {
|
121
|
+
const _options = {
|
122
|
+
...options,
|
123
|
+
apiKey: options.apiKey?.trim() || DEFAULT_API_LEY,
|
124
|
+
baseURL: options.baseURL?.trim(),
|
125
|
+
};
|
126
|
+
|
127
|
+
if (routers.length === 0) {
|
128
|
+
throw new Error('empty providers');
|
129
|
+
}
|
130
|
+
|
131
|
+
this._runtimes = routers.map((router) => {
|
132
|
+
const providerAI = router.runtime ?? baseRuntimeMap[router.apiType] ?? LobeOpenAI;
|
133
|
+
|
134
|
+
const finalOptions = { ...router.options, ...options };
|
135
|
+
// @ts-ignore
|
136
|
+
const runtime: LobeRuntimeAI = new providerAI({ ...params, ...finalOptions, id });
|
137
|
+
|
138
|
+
return { id: router.apiType, models: router.models, runtime };
|
139
|
+
});
|
140
|
+
|
141
|
+
this._options = _options;
|
142
|
+
}
|
143
|
+
|
144
|
+
// 检查下是否能匹配到特定模型,否则默认使用最后一个 runtime
|
145
|
+
getRuntimeByModel(model: string) {
|
146
|
+
const runtimeItem =
|
147
|
+
this._runtimes.find((runtime) => runtime.models && runtime.models.includes(model)) ||
|
148
|
+
this._runtimes.at(-1)!;
|
149
|
+
|
150
|
+
return runtimeItem.runtime;
|
151
|
+
}
|
152
|
+
|
153
|
+
async chat(payload: ChatStreamPayload, options?: ChatMethodOptions) {
|
154
|
+
try {
|
155
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
156
|
+
|
157
|
+
return await runtime.chat!(payload, options);
|
158
|
+
} catch (e) {
|
159
|
+
if (this._options.chat?.handleError) {
|
160
|
+
const error = this._options.chat.handleError(e);
|
161
|
+
|
162
|
+
if (error) {
|
163
|
+
throw error;
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
throw e;
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
async textToImage(payload: TextToImagePayload) {
|
172
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
173
|
+
|
174
|
+
return runtime.textToImage!(payload);
|
175
|
+
}
|
176
|
+
|
177
|
+
async models() {
|
178
|
+
return this._runtimes[0].runtime.models?.();
|
179
|
+
}
|
180
|
+
|
181
|
+
async embeddings(payload: EmbeddingsPayload, options?: EmbeddingsOptions) {
|
182
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
183
|
+
|
184
|
+
return runtime.embeddings!(payload, options);
|
185
|
+
}
|
186
|
+
|
187
|
+
async textToSpeech(payload: TextToSpeechPayload, options?: EmbeddingsOptions) {
|
188
|
+
const runtime = this.getRuntimeByModel(payload.model);
|
189
|
+
|
190
|
+
return runtime.textToSpeech!(payload, options);
|
191
|
+
}
|
192
|
+
};
|
193
|
+
};
|