@lobehub/chat 1.3.6 → 1.4.0
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/CHANGELOG.md +25 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/netlify.toml +1 -1
- package/package.json +1 -1
- package/src/app/(main)/settings/llm/ProviderList/providers.tsx +7 -0
- package/src/app/api/chat/agentRuntime.ts +7 -0
- package/src/components/ModelIcon/index.tsx +2 -0
- package/src/components/ModelProviderIcon/index.tsx +5 -0
- package/src/components/ModelTag/ModelIcon.tsx +2 -0
- package/src/config/llm.ts +6 -0
- package/src/config/modelProviders/ai360.ts +38 -0
- package/src/config/modelProviders/index.ts +4 -0
- package/src/const/settings/llm.ts +5 -0
- package/src/features/Conversation/Error/APIKeyForm/ProviderAvatar.tsx +5 -0
- package/src/libs/agent-runtime/AgentRuntime.ts +7 -0
- package/src/libs/agent-runtime/ai360/index.test.ts +255 -0
- package/src/libs/agent-runtime/ai360/index.ts +10 -0
- package/src/libs/agent-runtime/types/type.ts +1 -0
- package/src/server/globalConfig/index.ts +2 -0
- package/src/types/user/settings/keyVaults.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 1.4.0](https://github.com/lobehub/lobe-chat/compare/v1.3.6...v1.4.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-07-12**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Add 360AI model provider.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: Add 360AI model provider, closes [#3130](https://github.com/lobehub/lobe-chat/issues/3130) ([79c5f86](https://github.com/lobehub/lobe-chat/commit/79c5f86))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 1.3.6](https://github.com/lobehub/lobe-chat/compare/v1.3.5...v1.3.6)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2024-07-11**</sup>
|
package/README.md
CHANGED
|
@@ -267,12 +267,12 @@ Our marketplace is not just a showcase platform but also a collaborative space.
|
|
|
267
267
|
|
|
268
268
|
| Recent Submits | Description |
|
|
269
269
|
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
270
|
+
| [Ducky Programming Assistant](https://chat-preview.lobehub.com/market?agent=rubber-duck-programming)<br/><sup>By **[JiyuShao](https://github.com/JiyuShao)** on **2024-07-10**</sup> | Ducky Programming Assistant<br/>`programming` |
|
|
270
271
|
| [AOSP Source Code Expert](https://chat-preview.lobehub.com/market?agent=aosp-development)<br/><sup>By **[viruscoding](https://github.com/viruscoding)** on **2024-06-24**</sup> | An expert in AOSP (Android Open Source Project) for Android, with a deep understanding and analytical ability of the latest AOSP source code.<br/>`aosp` |
|
|
271
272
|
| [Fastapi Project Development Assistant](https://chat-preview.lobehub.com/market?agent=fastapi-development)<br/><sup>By **[xwxw098](https://github.com/xwxw098)** on **2024-06-19**</sup> | Proficient in Python modular development, skilled in using FastAPI, PostgreSQL, Tortoise-ORM, and other technologies, able to provide clear code structure and detailed comments for large projects.<br/>`fast-api` `python` `modular-development` |
|
|
272
273
|
| [IT Systems Architect](https://chat-preview.lobehub.com/market?agent=it-system-architect)<br/><sup>By **[a562314](https://github.com/a562314)** on **2024-06-19**</sup> | Senior IT architect specializing in requirements analysis, system design, technology selection, and cross-platform system optimization. With over 5 years of experience, proficient in Windows, macOS, and Linux operating systems, skilled in troubleshooting, and security protection.<br/>`it-architecture-design` `problem-solving` `agile-development` `system-optimization` `cross-platform-skills` `teamwork` |
|
|
273
|
-
| [Linux Kernel Expert](https://chat-preview.lobehub.com/market?agent=linux-kernel)<br/><sup>By **[wming126](https://github.com/wming126)** on **2024-06-19**</sup> | Role Description: I am an expert in Linux kernel, with a deep understanding and analytical ability of the latest kernel source code (as of June 2024). I can provide users with detailed and accurate information about the Linux kernel.<br/>`linux` `kernel` |
|
|
274
274
|
|
|
275
|
-
> 📊 Total agents: [<kbd>**
|
|
275
|
+
> 📊 Total agents: [<kbd>**294**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
276
276
|
|
|
277
277
|
<!-- AGENT LIST -->
|
|
278
278
|
|
package/README.zh-CN.md
CHANGED
|
@@ -256,12 +256,12 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
|
|
256
256
|
|
|
257
257
|
| 最近新增 | 助手说明 |
|
|
258
258
|
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
259
|
+
| [小黄鸭编程助手](https://chat-preview.lobehub.com/market?agent=rubber-duck-programming)<br/><sup>By **[JiyuShao](https://github.com/JiyuShao)** on **2024-07-10**</sup> | 小黄鸭编程助手<br/>`programming` |
|
|
259
260
|
| [AOSP 源码专家](https://chat-preview.lobehub.com/market?agent=aosp-development)<br/><sup>By **[viruscoding](https://github.com/viruscoding)** on **2024-06-24**</sup> | 一位精通 AOSP(Android Open Source Project)安卓的专家,对最新 AOSP 源代码有着深入的理解和分析能力。<br/>`aosp` |
|
|
260
261
|
| [Fastapi 项目开发助手](https://chat-preview.lobehub.com/market?agent=fastapi-development)<br/><sup>By **[xwxw098](https://github.com/xwxw098)** on **2024-06-19**</sup> | 擅长 Python 模块化开发,熟练运用 FastAPI、PostgreSQL、Tortoise-ORM 等技术栈,能为大型项目提供清晰的代码结构并添加详细注释。<br/>`fast-api` `python` `模块化开发` |
|
|
261
262
|
| [IT 系统架构师](https://chat-preview.lobehub.com/market?agent=it-system-architect)<br/><sup>By **[a562314](https://github.com/a562314)** on **2024-06-19**</sup> | 资深 IT 架构师,擅长需求分析、系统设计、技术选型和跨平台系统优化。5 年以上经验,精通 Windows、macOS 和 Linux 三大操作系统,具备故障排除和安全防护能力<br/>`it架构设计` `问题解决` `敏捷开发` `系统优化` `跨平台技能` |
|
|
262
|
-
| [Linux 内核专家](https://chat-preview.lobehub.com/market?agent=linux-kernel)<br/><sup>By **[wming126](https://github.com/wming126)** on **2024-06-19**</sup> | 角色描述: 我是一位精通 Linux 内核的专家,对最新内核源代码(截至 2024 年 6 月)有着深入的理解和分析能力。我可以为用户提供关于 Linux 内核的详细、准确的信息。<br/>`linux` `kernel` |
|
|
263
263
|
|
|
264
|
-
> 📊 Total agents: [<kbd>**
|
|
264
|
+
> 📊 Total agents: [<kbd>**294**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
265
265
|
|
|
266
266
|
<!-- AGENT LIST -->
|
|
267
267
|
|
package/netlify.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Ai360,
|
|
2
3
|
AiMass,
|
|
3
4
|
Anthropic,
|
|
4
5
|
Baichuan,
|
|
@@ -25,6 +26,7 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
25
26
|
import urlJoin from 'url-join';
|
|
26
27
|
|
|
27
28
|
import {
|
|
29
|
+
Ai360ProviderCard,
|
|
28
30
|
AnthropicProviderCard,
|
|
29
31
|
BaichuanProviderCard,
|
|
30
32
|
DeepSeekProviderCard,
|
|
@@ -184,6 +186,11 @@ export const useProviderList = (): ProviderItem[] => {
|
|
|
184
186
|
docUrl: urlJoin(BASE_DOC_URL, 'taichu'),
|
|
185
187
|
title: <AiMass.Combine size={ 28 } type={ 'color' } />,
|
|
186
188
|
},
|
|
189
|
+
{
|
|
190
|
+
...Ai360ProviderCard,
|
|
191
|
+
docUrl: urlJoin(BASE_DOC_URL, 'ai360'),
|
|
192
|
+
title: <Ai360.Combine size={ 20 } type={ 'color' } />,
|
|
193
|
+
},
|
|
187
194
|
],
|
|
188
195
|
[azureProvider, ollamaProvider, ollamaProvider, bedrockProvider],
|
|
189
196
|
);
|
|
@@ -184,6 +184,13 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
|
|
|
184
184
|
|
|
185
185
|
const apiKey = apiKeyManager.pick(payload?.apiKey || TAICHU_API_KEY);
|
|
186
186
|
|
|
187
|
+
return { apiKey };
|
|
188
|
+
}
|
|
189
|
+
case ModelProvider.Ai360: {
|
|
190
|
+
const { AI360_API_KEY } = getLLMConfig();
|
|
191
|
+
|
|
192
|
+
const apiKey = apiKeyManager.pick(payload?.apiKey || AI360_API_KEY);
|
|
193
|
+
|
|
187
194
|
return { apiKey };
|
|
188
195
|
}
|
|
189
196
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
AiMass,
|
|
3
3
|
Adobe,
|
|
4
4
|
Ai21,
|
|
5
|
+
Ai360,
|
|
5
6
|
Aws,
|
|
6
7
|
Azure,
|
|
7
8
|
Baichuan,
|
|
@@ -68,6 +69,7 @@ const ModelIcon = memo<ModelProviderIconProps>(({ model: originModel, size = 12
|
|
|
68
69
|
if (model.includes('dbrx')) return <Dbrx.Avatar size={size} />;
|
|
69
70
|
if (model.includes('step')) return <Stepfun.Avatar size={size} />;
|
|
70
71
|
if (model.includes('taichu')) return <AiMass.Avatar size={size} />;
|
|
72
|
+
if (model.includes('360gpt')) return <Ai360.Avatar size={size} />;
|
|
71
73
|
|
|
72
74
|
// below: To be supported in providers, move up if supported
|
|
73
75
|
if (model.includes('baichuan'))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Ai360,
|
|
2
3
|
AiMass,
|
|
3
4
|
Anthropic,
|
|
4
5
|
Azure,
|
|
@@ -124,6 +125,10 @@ const ModelProviderIcon = memo<ModelProviderIconProps>(({ provider }) => {
|
|
|
124
125
|
return <AiMass size={20} />;
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
case ModelProvider.Ai360: {
|
|
129
|
+
return <Ai360 size={20} />;
|
|
130
|
+
}
|
|
131
|
+
|
|
127
132
|
default: {
|
|
128
133
|
return null;
|
|
129
134
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AdobeFirefly,
|
|
3
3
|
Ai21,
|
|
4
|
+
Ai360,
|
|
4
5
|
AiMass,
|
|
5
6
|
Aws,
|
|
6
7
|
Azure,
|
|
@@ -63,6 +64,7 @@ const ModelIcon = memo<ModelIconProps>(({ model, size = 12 }) => {
|
|
|
63
64
|
if (model.includes('command')) return <Cohere size={size} />;
|
|
64
65
|
if (model.includes('dbrx')) return <Dbrx size={size} />;
|
|
65
66
|
if (model.includes('taichu')) return <AiMass size={size} />;
|
|
67
|
+
if (model.includes('360gpt')) return <Ai360 size={size} />;
|
|
66
68
|
|
|
67
69
|
// below: To be supported in providers, move up if supported
|
|
68
70
|
if (model.includes('baichuan')) return <Baichuan size={size} />;
|
package/src/config/llm.ts
CHANGED
|
@@ -88,6 +88,9 @@ export const getLLMConfig = () => {
|
|
|
88
88
|
|
|
89
89
|
ENABLED_TAICHU: z.boolean(),
|
|
90
90
|
TAICHU_API_KEY: z.string().optional(),
|
|
91
|
+
|
|
92
|
+
ENABLED_AI360: z.boolean(),
|
|
93
|
+
AI360_API_KEY: z.string().optional(),
|
|
91
94
|
},
|
|
92
95
|
runtimeEnv: {
|
|
93
96
|
API_KEY_SELECT_MODE: process.env.API_KEY_SELECT_MODE,
|
|
@@ -167,6 +170,9 @@ export const getLLMConfig = () => {
|
|
|
167
170
|
|
|
168
171
|
ENABLED_TAICHU: !!process.env.TAICHU_API_KEY,
|
|
169
172
|
TAICHU_API_KEY: process.env.TAICHU_API_KEY,
|
|
173
|
+
|
|
174
|
+
ENABLED_AI360: !!process.env.AI360_API_KEY,
|
|
175
|
+
AI360_API_KEY: process.env.AI360_API_KEY,
|
|
170
176
|
},
|
|
171
177
|
});
|
|
172
178
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ModelProviderCard } from '@/types/llm';
|
|
2
|
+
|
|
3
|
+
// ref https://ai.360.cn/platform/docs/overview
|
|
4
|
+
const Ai360: ModelProviderCard = {
|
|
5
|
+
chatModels: [
|
|
6
|
+
{
|
|
7
|
+
displayName: '360GPT Pro',
|
|
8
|
+
enabled: true,
|
|
9
|
+
functionCall: false,
|
|
10
|
+
id: '360gpt-pro',
|
|
11
|
+
maxOutput: 7000,
|
|
12
|
+
tokens: 8192,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
displayName: '360GPT Turbo',
|
|
16
|
+
enabled: true,
|
|
17
|
+
functionCall: false,
|
|
18
|
+
id: '360gpt-turbo',
|
|
19
|
+
maxOutput: 8192,
|
|
20
|
+
tokens: 8192,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
displayName: '360GPT Turbo Responsibility 8K',
|
|
24
|
+
enabled: true,
|
|
25
|
+
functionCall: false,
|
|
26
|
+
id: '360gpt-turbo-responsibility-8k',
|
|
27
|
+
maxOutput: 2048,
|
|
28
|
+
tokens: 8192,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
checkModel: '360gpt-turbo',
|
|
32
|
+
disableBrowserRequest: true,
|
|
33
|
+
id: 'ai360',
|
|
34
|
+
modelList: { showModelFetcher: true },
|
|
35
|
+
name: '360智脑',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default Ai360;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChatModelCard, ModelProviderCard } from '@/types/llm';
|
|
2
2
|
|
|
3
|
+
import Ai360Provider from './ai360';
|
|
3
4
|
import AnthropicProvider from './anthropic';
|
|
4
5
|
import AzureProvider from './azure';
|
|
5
6
|
import BaichuanProvider from './baichuan';
|
|
@@ -41,6 +42,7 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [
|
|
|
41
42
|
StepfunProvider.chatModels,
|
|
42
43
|
BaichuanProvider.chatModels,
|
|
43
44
|
TaichuProvider.chatModels,
|
|
45
|
+
Ai360Provider.chatModels,
|
|
44
46
|
].flat();
|
|
45
47
|
|
|
46
48
|
export const DEFAULT_MODEL_PROVIDER_LIST = [
|
|
@@ -64,6 +66,7 @@ export const DEFAULT_MODEL_PROVIDER_LIST = [
|
|
|
64
66
|
StepfunProvider,
|
|
65
67
|
BaichuanProvider,
|
|
66
68
|
TaichuProvider,
|
|
69
|
+
Ai360Provider,
|
|
67
70
|
];
|
|
68
71
|
|
|
69
72
|
export const filterEnabledModels = (provider: ModelProviderCard) => {
|
|
@@ -75,6 +78,7 @@ export const isProviderDisableBroswerRequest = (id: string) => {
|
|
|
75
78
|
return !!provider;
|
|
76
79
|
};
|
|
77
80
|
|
|
81
|
+
export { default as Ai360ProviderCard } from './ai360';
|
|
78
82
|
export { default as AnthropicProviderCard } from './anthropic';
|
|
79
83
|
export { default as AzureProviderCard } from './azure';
|
|
80
84
|
export { default as BaichuanProviderCard } from './baichuan';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Ai360ProviderCard,
|
|
2
3
|
AnthropicProviderCard,
|
|
3
4
|
BaichuanProviderCard,
|
|
4
5
|
BedrockProviderCard,
|
|
@@ -24,6 +25,10 @@ import { ModelProvider } from '@/libs/agent-runtime';
|
|
|
24
25
|
import { UserModelProviderConfig } from '@/types/user/settings';
|
|
25
26
|
|
|
26
27
|
export const DEFAULT_LLM_CONFIG: UserModelProviderConfig = {
|
|
28
|
+
ai360: {
|
|
29
|
+
enabled: false,
|
|
30
|
+
enabledModels: filterEnabledModels(Ai360ProviderCard),
|
|
31
|
+
},
|
|
27
32
|
anthropic: {
|
|
28
33
|
enabled: false,
|
|
29
34
|
enabledModels: filterEnabledModels(AnthropicProviderCard),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Ai360,
|
|
2
3
|
AiMass,
|
|
3
4
|
Anthropic,
|
|
4
5
|
Baichuan,
|
|
@@ -94,6 +95,10 @@ const ProviderAvatar = memo<ProviderAvatarProps>(({ provider }) => {
|
|
|
94
95
|
return <ZeroOne color={ZeroOne.colorPrimary} size={56} />;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
case ModelProvider.Ai360: {
|
|
99
|
+
return <Ai360 color={Ai360.colorPrimary} size={56} />;
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
default:
|
|
98
103
|
case ModelProvider.OpenAI: {
|
|
99
104
|
return <OpenAI color={theme.colorText} size={64} />;
|
|
@@ -3,6 +3,7 @@ import { ClientOptions } from 'openai';
|
|
|
3
3
|
import type { TracePayload } from '@/const/trace';
|
|
4
4
|
|
|
5
5
|
import { LobeRuntimeAI } from './BaseAI';
|
|
6
|
+
import { LobeAi360AI } from './ai360';
|
|
6
7
|
import { LobeAnthropicAI } from './anthropic';
|
|
7
8
|
import { LobeAzureOpenAI } from './azureOpenai';
|
|
8
9
|
import { LobeBaichuanAI } from './baichuan';
|
|
@@ -103,6 +104,7 @@ class AgentRuntime {
|
|
|
103
104
|
static async initializeWithProviderOptions(
|
|
104
105
|
provider: string,
|
|
105
106
|
params: Partial<{
|
|
107
|
+
ai360: Partial<ClientOptions>;
|
|
106
108
|
anthropic: Partial<ClientOptions>;
|
|
107
109
|
azure: { apiVersion?: string; apikey?: string; endpoint?: string };
|
|
108
110
|
baichuan: Partial<ClientOptions>;
|
|
@@ -233,6 +235,11 @@ class AgentRuntime {
|
|
|
233
235
|
runtimeModel = new LobeTaichuAI(params.taichu);
|
|
234
236
|
break;
|
|
235
237
|
}
|
|
238
|
+
|
|
239
|
+
case ModelProvider.Ai360: {
|
|
240
|
+
runtimeModel = new LobeAi360AI(params.ai360 ?? {});
|
|
241
|
+
break
|
|
242
|
+
}
|
|
236
243
|
}
|
|
237
244
|
|
|
238
245
|
return new AgentRuntime(runtimeModel);
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import OpenAI from 'openai';
|
|
3
|
+
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ChatStreamCallbacks,
|
|
7
|
+
LobeOpenAICompatibleRuntime,
|
|
8
|
+
ModelProvider,
|
|
9
|
+
} from '@/libs/agent-runtime';
|
|
10
|
+
|
|
11
|
+
import * as debugStreamModule from '../utils/debugStream';
|
|
12
|
+
import { LobeAi360AI } from './index';
|
|
13
|
+
|
|
14
|
+
const provider = ModelProvider.Ai360;
|
|
15
|
+
const defaultBaseURL = 'https://ai.360.cn/v1';
|
|
16
|
+
|
|
17
|
+
const bizErrorType = 'ProviderBizError';
|
|
18
|
+
const invalidErrorType = 'InvalidProviderAPIKey';
|
|
19
|
+
|
|
20
|
+
// Mock the console.error to avoid polluting test output
|
|
21
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
22
|
+
|
|
23
|
+
let instance: LobeOpenAICompatibleRuntime;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
instance = new LobeAi360AI({ apiKey: 'test' });
|
|
27
|
+
|
|
28
|
+
// 使用 vi.spyOn 来模拟 chat.completions.create 方法
|
|
29
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
|
|
30
|
+
new ReadableStream() as any,
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('LobeAi360AI', () => {
|
|
39
|
+
describe('init', () => {
|
|
40
|
+
it('should correctly initialize with an API key', async () => {
|
|
41
|
+
const instance = new LobeAi360AI({ apiKey: 'test_api_key' });
|
|
42
|
+
expect(instance).toBeInstanceOf(LobeAi360AI);
|
|
43
|
+
expect(instance.baseURL).toEqual(defaultBaseURL);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('chat', () => {
|
|
48
|
+
describe('Error', () => {
|
|
49
|
+
it('should return OpenAIBizError with an openai error response when OpenAI.APIError is thrown', async () => {
|
|
50
|
+
// Arrange
|
|
51
|
+
const apiError = new OpenAI.APIError(
|
|
52
|
+
400,
|
|
53
|
+
{
|
|
54
|
+
status: 400,
|
|
55
|
+
error: {
|
|
56
|
+
message: 'Bad Request',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
'Error message',
|
|
60
|
+
{},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
|
|
64
|
+
|
|
65
|
+
// Act
|
|
66
|
+
try {
|
|
67
|
+
await instance.chat({
|
|
68
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
69
|
+
model: '360gpt-turbo',
|
|
70
|
+
temperature: 0,
|
|
71
|
+
});
|
|
72
|
+
} catch (e) {
|
|
73
|
+
expect(e).toEqual({
|
|
74
|
+
endpoint: defaultBaseURL,
|
|
75
|
+
error: {
|
|
76
|
+
error: { message: 'Bad Request' },
|
|
77
|
+
status: 400,
|
|
78
|
+
},
|
|
79
|
+
errorType: bizErrorType,
|
|
80
|
+
provider,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should throw AgentRuntimeError with NoOpenAIAPIKey if no apiKey is provided', async () => {
|
|
86
|
+
try {
|
|
87
|
+
new LobeAi360AI({});
|
|
88
|
+
} catch (e) {
|
|
89
|
+
expect(e).toEqual({ errorType: invalidErrorType });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return OpenAIBizError with the cause when OpenAI.APIError is thrown with cause', async () => {
|
|
94
|
+
// Arrange
|
|
95
|
+
const errorInfo = {
|
|
96
|
+
stack: 'abc',
|
|
97
|
+
cause: {
|
|
98
|
+
message: 'api is undefined',
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
|
|
102
|
+
|
|
103
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
|
|
104
|
+
|
|
105
|
+
// Act
|
|
106
|
+
try {
|
|
107
|
+
await instance.chat({
|
|
108
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
109
|
+
model: '360gpt-turbo',
|
|
110
|
+
temperature: 0,
|
|
111
|
+
});
|
|
112
|
+
} catch (e) {
|
|
113
|
+
expect(e).toEqual({
|
|
114
|
+
endpoint: defaultBaseURL,
|
|
115
|
+
error: {
|
|
116
|
+
cause: { message: 'api is undefined' },
|
|
117
|
+
stack: 'abc',
|
|
118
|
+
},
|
|
119
|
+
errorType: bizErrorType,
|
|
120
|
+
provider,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return OpenAIBizError with an cause response with desensitize Url', async () => {
|
|
126
|
+
// Arrange
|
|
127
|
+
const errorInfo = {
|
|
128
|
+
stack: 'abc',
|
|
129
|
+
cause: { message: 'api is undefined' },
|
|
130
|
+
};
|
|
131
|
+
const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
|
|
132
|
+
|
|
133
|
+
instance = new LobeAi360AI({
|
|
134
|
+
apiKey: 'test',
|
|
135
|
+
|
|
136
|
+
baseURL: 'https://api.abc.com/v1',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
|
|
140
|
+
|
|
141
|
+
// Act
|
|
142
|
+
try {
|
|
143
|
+
await instance.chat({
|
|
144
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
145
|
+
model: '360gpt-turbo',
|
|
146
|
+
temperature: 0,
|
|
147
|
+
});
|
|
148
|
+
} catch (e) {
|
|
149
|
+
expect(e).toEqual({
|
|
150
|
+
endpoint: 'https://api.***.com/v1',
|
|
151
|
+
error: {
|
|
152
|
+
cause: { message: 'api is undefined' },
|
|
153
|
+
stack: 'abc',
|
|
154
|
+
},
|
|
155
|
+
errorType: bizErrorType,
|
|
156
|
+
provider,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should throw an InvalidAi360APIKey error type on 401 status code', async () => {
|
|
162
|
+
// Mock the API call to simulate a 401 error
|
|
163
|
+
const error = new Error('Unauthorized') as any;
|
|
164
|
+
error.status = 401;
|
|
165
|
+
vi.mocked(instance['client'].chat.completions.create).mockRejectedValue(error);
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
await instance.chat({
|
|
169
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
170
|
+
model: '360gpt-turbo',
|
|
171
|
+
temperature: 0,
|
|
172
|
+
});
|
|
173
|
+
} catch (e) {
|
|
174
|
+
// Expect the chat method to throw an error with InvalidAi360APIKey
|
|
175
|
+
expect(e).toEqual({
|
|
176
|
+
endpoint: defaultBaseURL,
|
|
177
|
+
error: new Error('Unauthorized'),
|
|
178
|
+
errorType: invalidErrorType,
|
|
179
|
+
provider,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should return AgentRuntimeError for non-OpenAI errors', async () => {
|
|
185
|
+
// Arrange
|
|
186
|
+
const genericError = new Error('Generic Error');
|
|
187
|
+
|
|
188
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(genericError);
|
|
189
|
+
|
|
190
|
+
// Act
|
|
191
|
+
try {
|
|
192
|
+
await instance.chat({
|
|
193
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
194
|
+
model: '360gpt-turbo',
|
|
195
|
+
temperature: 0,
|
|
196
|
+
});
|
|
197
|
+
} catch (e) {
|
|
198
|
+
expect(e).toEqual({
|
|
199
|
+
endpoint: defaultBaseURL,
|
|
200
|
+
errorType: 'AgentRuntimeError',
|
|
201
|
+
provider,
|
|
202
|
+
error: {
|
|
203
|
+
name: genericError.name,
|
|
204
|
+
cause: genericError.cause,
|
|
205
|
+
message: genericError.message,
|
|
206
|
+
stack: genericError.stack,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('DEBUG', () => {
|
|
214
|
+
it('should call debugStream and return StreamingTextResponse when DEBUG_AI360_CHAT_COMPLETION is 1', async () => {
|
|
215
|
+
// Arrange
|
|
216
|
+
const mockProdStream = new ReadableStream() as any; // 模拟的 prod 流
|
|
217
|
+
const mockDebugStream = new ReadableStream({
|
|
218
|
+
start(controller) {
|
|
219
|
+
controller.enqueue('Debug stream content');
|
|
220
|
+
controller.close();
|
|
221
|
+
},
|
|
222
|
+
}) as any;
|
|
223
|
+
mockDebugStream.toReadableStream = () => mockDebugStream; // 添加 toReadableStream 方法
|
|
224
|
+
|
|
225
|
+
// 模拟 chat.completions.create 返回值,包括模拟的 tee 方法
|
|
226
|
+
(instance['client'].chat.completions.create as Mock).mockResolvedValue({
|
|
227
|
+
tee: () => [mockProdStream, { toReadableStream: () => mockDebugStream }],
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// 保存原始环境变量值
|
|
231
|
+
const originalDebugValue = process.env.DEBUG_AI360_CHAT_COMPLETION;
|
|
232
|
+
|
|
233
|
+
// 模拟环境变量
|
|
234
|
+
process.env.DEBUG_AI360_CHAT_COMPLETION = '1';
|
|
235
|
+
vi.spyOn(debugStreamModule, 'debugStream').mockImplementation(() => Promise.resolve());
|
|
236
|
+
|
|
237
|
+
// 执行测试
|
|
238
|
+
// 运行你的测试函数,确保它会在条件满足时调用 debugStream
|
|
239
|
+
// 假设的测试函数调用,你可能需要根据实际情况调整
|
|
240
|
+
await instance.chat({
|
|
241
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
242
|
+
model: '360gpt-turbo',
|
|
243
|
+
stream: true,
|
|
244
|
+
temperature: 0,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// 验证 debugStream 被调用
|
|
248
|
+
expect(debugStreamModule.debugStream).toHaveBeenCalled();
|
|
249
|
+
|
|
250
|
+
// 恢复原始环境变量值
|
|
251
|
+
process.env.DEBUG_AI360_CHAT_COMPLETION = originalDebugValue;
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ModelProvider } from '../types';
|
|
2
|
+
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
|
3
|
+
|
|
4
|
+
export const LobeAi360AI = LobeOpenAICompatibleFactory({
|
|
5
|
+
baseURL: 'https://ai.360.cn/v1',
|
|
6
|
+
debug: {
|
|
7
|
+
chatCompletion: () => process.env.DEBUG_AI360_CHAT_COMPLETION === '1',
|
|
8
|
+
},
|
|
9
|
+
provider: ModelProvider.Ai360,
|
|
10
|
+
});
|
|
@@ -36,6 +36,7 @@ export const getServerGlobalConfig = () => {
|
|
|
36
36
|
ENABLED_STEPFUN,
|
|
37
37
|
ENABLED_BAICHUAN,
|
|
38
38
|
ENABLED_TAICHU,
|
|
39
|
+
ENABLED_AI360,
|
|
39
40
|
|
|
40
41
|
ENABLED_AZURE_OPENAI,
|
|
41
42
|
AZURE_MODEL_LIST,
|
|
@@ -60,6 +61,7 @@ export const getServerGlobalConfig = () => {
|
|
|
60
61
|
enabledAccessCode: ACCESS_CODES?.length > 0,
|
|
61
62
|
enabledOAuthSSO: enableNextAuth,
|
|
62
63
|
languageModel: {
|
|
64
|
+
ai360: { enabled: ENABLED_AI360 },
|
|
63
65
|
anthropic: {
|
|
64
66
|
enabled: ENABLED_ANTHROPIC,
|
|
65
67
|
},
|