@lobehub/chat 1.20.7 → 1.21.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 +42 -0
- package/locales/ar/error.json +1 -0
- package/locales/ar/modelProvider.json +20 -0
- package/locales/ar/models.json +73 -4
- package/locales/ar/providers.json +6 -0
- package/locales/bg-BG/error.json +1 -0
- package/locales/bg-BG/modelProvider.json +20 -0
- package/locales/bg-BG/models.json +73 -4
- package/locales/bg-BG/providers.json +6 -0
- package/locales/de-DE/error.json +1 -0
- package/locales/de-DE/modelProvider.json +20 -0
- package/locales/de-DE/models.json +73 -4
- package/locales/de-DE/providers.json +6 -0
- package/locales/en-US/error.json +1 -0
- package/locales/en-US/modelProvider.json +20 -0
- package/locales/en-US/models.json +73 -4
- package/locales/en-US/providers.json +6 -0
- package/locales/es-ES/error.json +1 -0
- package/locales/es-ES/modelProvider.json +20 -0
- package/locales/es-ES/models.json +73 -4
- package/locales/es-ES/providers.json +6 -0
- package/locales/fr-FR/error.json +1 -0
- package/locales/fr-FR/modelProvider.json +20 -0
- package/locales/fr-FR/models.json +73 -4
- package/locales/fr-FR/providers.json +6 -0
- package/locales/it-IT/error.json +1 -0
- package/locales/it-IT/modelProvider.json +20 -0
- package/locales/it-IT/models.json +73 -4
- package/locales/it-IT/providers.json +6 -0
- package/locales/ja-JP/error.json +1 -0
- package/locales/ja-JP/modelProvider.json +20 -0
- package/locales/ja-JP/models.json +73 -4
- package/locales/ja-JP/providers.json +6 -0
- package/locales/ko-KR/error.json +1 -0
- package/locales/ko-KR/modelProvider.json +20 -0
- package/locales/ko-KR/models.json +73 -4
- package/locales/ko-KR/providers.json +6 -0
- package/locales/nl-NL/error.json +1 -0
- package/locales/nl-NL/modelProvider.json +20 -0
- package/locales/nl-NL/models.json +73 -4
- package/locales/nl-NL/providers.json +6 -0
- package/locales/pl-PL/error.json +1 -0
- package/locales/pl-PL/modelProvider.json +20 -0
- package/locales/pl-PL/models.json +73 -4
- package/locales/pl-PL/providers.json +6 -0
- package/locales/pt-BR/error.json +1 -0
- package/locales/pt-BR/modelProvider.json +20 -0
- package/locales/pt-BR/models.json +73 -4
- package/locales/pt-BR/providers.json +6 -0
- package/locales/ru-RU/error.json +1 -0
- package/locales/ru-RU/modelProvider.json +20 -0
- package/locales/ru-RU/models.json +73 -4
- package/locales/ru-RU/providers.json +6 -0
- package/locales/tr-TR/error.json +1 -0
- package/locales/tr-TR/modelProvider.json +20 -0
- package/locales/tr-TR/models.json +73 -4
- package/locales/tr-TR/providers.json +6 -0
- package/locales/vi-VN/error.json +1 -0
- package/locales/vi-VN/modelProvider.json +20 -0
- package/locales/vi-VN/models.json +73 -4
- package/locales/vi-VN/providers.json +6 -0
- package/locales/zh-CN/error.json +1 -0
- package/locales/zh-CN/modelProvider.json +20 -0
- package/locales/zh-CN/models.json +76 -7
- package/locales/zh-CN/providers.json +6 -0
- package/locales/zh-TW/error.json +1 -0
- package/locales/zh-TW/modelProvider.json +20 -0
- package/locales/zh-TW/models.json +73 -4
- package/locales/zh-TW/providers.json +6 -0
- package/package.json +4 -3
- package/scripts/serverLauncher/startServer.js +6 -5
- package/src/app/(main)/settings/llm/ProviderList/Wenxin/index.tsx +46 -0
- package/src/app/(main)/settings/llm/ProviderList/providers.tsx +4 -1
- package/src/app/api/chat/agentRuntime.test.ts +21 -0
- package/src/app/api/chat/wenxin/route.test.ts +27 -0
- package/src/app/api/chat/wenxin/route.ts +30 -0
- package/src/app/api/errorResponse.ts +4 -0
- package/src/config/llm.ts +8 -0
- package/src/config/modelProviders/index.ts +4 -0
- package/src/config/modelProviders/wenxin.ts +159 -0
- package/src/const/auth.ts +4 -0
- package/src/const/settings/llm.ts +5 -0
- package/src/features/Conversation/Error/APIKeyForm/Wenxin.tsx +49 -0
- package/src/features/Conversation/Error/APIKeyForm/index.tsx +3 -0
- package/src/features/Conversation/Error/index.tsx +1 -0
- package/src/libs/agent-runtime/AgentRuntime.test.ts +1 -0
- package/src/libs/agent-runtime/error.ts +1 -0
- package/src/libs/agent-runtime/types/type.ts +1 -0
- package/src/libs/agent-runtime/utils/streams/wenxin.test.ts +149 -0
- package/src/libs/agent-runtime/utils/streams/wenxin.ts +46 -0
- package/src/libs/agent-runtime/wenxin/index.ts +106 -0
- package/src/libs/agent-runtime/wenxin/type.ts +84 -0
- package/src/locales/default/error.ts +2 -0
- package/src/locales/default/modelProvider.ts +20 -0
- package/src/server/globalConfig/index.ts +4 -1
- package/src/services/_auth.ts +14 -0
- package/src/store/user/slices/modelList/selectors/keyVaults.ts +2 -0
- package/src/types/user/settings/keyVaults.ts +6 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
import { ModelProviderCard } from '@/types/llm';
|
2
|
+
|
3
|
+
// ref https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu
|
4
|
+
const BaiduWenxin: ModelProviderCard = {
|
5
|
+
chatModels: [
|
6
|
+
{
|
7
|
+
description:
|
8
|
+
'百度自研的旗舰级大规模⼤语⾔模型,覆盖海量中英文语料,具有强大的通用能力,可满足绝大部分对话问答、创作生成、插件应用场景要求;支持自动对接百度搜索插件,保障问答信息时效。',
|
9
|
+
displayName: 'ERNIE 3.5 8K',
|
10
|
+
enabled: true,
|
11
|
+
id: 'ERNIE-3.5-8K',
|
12
|
+
pricing: {
|
13
|
+
currency: 'CNY',
|
14
|
+
input: 0.8,
|
15
|
+
output: 2,
|
16
|
+
},
|
17
|
+
tokens: 8192,
|
18
|
+
},
|
19
|
+
{
|
20
|
+
description:
|
21
|
+
'百度自研的旗舰级大规模⼤语⾔模型,覆盖海量中英文语料,具有强大的通用能力,可满足绝大部分对话问答、创作生成、插件应用场景要求;支持自动对接百度搜索插件,保障问答信息时效。',
|
22
|
+
displayName: 'ERNIE 3.5 8K Preview',
|
23
|
+
id: 'ERNIE-3.5-8K-Preview',
|
24
|
+
pricing: {
|
25
|
+
currency: 'CNY',
|
26
|
+
input: 0.8,
|
27
|
+
output: 2,
|
28
|
+
},
|
29
|
+
tokens: 8192,
|
30
|
+
},
|
31
|
+
{
|
32
|
+
description:
|
33
|
+
'百度自研的旗舰级大规模⼤语⾔模型,覆盖海量中英文语料,具有强大的通用能力,可满足绝大部分对话问答、创作生成、插件应用场景要求;支持自动对接百度搜索插件,保障问答信息时效。',
|
34
|
+
displayName: 'ERNIE 3.5 128K',
|
35
|
+
enabled: true,
|
36
|
+
id: 'ERNIE-3.5-128K',
|
37
|
+
pricing: {
|
38
|
+
currency: 'CNY',
|
39
|
+
input: 0.8,
|
40
|
+
output: 2,
|
41
|
+
},
|
42
|
+
tokens: 128_000,
|
43
|
+
},
|
44
|
+
{
|
45
|
+
description:
|
46
|
+
'百度自研的旗舰级超大规模⼤语⾔模型,相较ERNIE 3.5实现了模型能力全面升级,广泛适用于各领域复杂任务场景;支持自动对接百度搜索插件,保障问答信息时效。',
|
47
|
+
displayName: 'ERNIE 4.0 8K',
|
48
|
+
enabled: true,
|
49
|
+
id: 'ERNIE-4.0-8K-Latest',
|
50
|
+
pricing: {
|
51
|
+
currency: 'CNY',
|
52
|
+
input: 30,
|
53
|
+
output: 90,
|
54
|
+
},
|
55
|
+
tokens: 8192,
|
56
|
+
},
|
57
|
+
{
|
58
|
+
description:
|
59
|
+
'百度自研的旗舰级超大规模⼤语⾔模型,相较ERNIE 3.5实现了模型能力全面升级,广泛适用于各领域复杂任务场景;支持自动对接百度搜索插件,保障问答信息时效。',
|
60
|
+
displayName: 'ERNIE 4.0 8K Preview',
|
61
|
+
id: 'ERNIE-4.0-8K-Preview',
|
62
|
+
pricing: {
|
63
|
+
currency: 'CNY',
|
64
|
+
input: 30,
|
65
|
+
output: 90,
|
66
|
+
},
|
67
|
+
tokens: 8192,
|
68
|
+
},
|
69
|
+
{
|
70
|
+
description:
|
71
|
+
'百度自研的旗舰级超大规模⼤语⾔模型,综合效果表现出色,广泛适用于各领域复杂任务场景;支持自动对接百度搜索插件,保障问答信息时效。相较于ERNIE 4.0在性能表现上更优秀',
|
72
|
+
displayName: 'ERNIE 4.0 Turbo 8K',
|
73
|
+
enabled: true,
|
74
|
+
id: 'ERNIE-4.0-Turbo-8K',
|
75
|
+
pricing: {
|
76
|
+
currency: 'CNY',
|
77
|
+
input: 20,
|
78
|
+
output: 60,
|
79
|
+
},
|
80
|
+
tokens: 8192,
|
81
|
+
},
|
82
|
+
{
|
83
|
+
description:
|
84
|
+
'百度自研的旗舰级超大规模⼤语⾔模型,综合效果表现出色,广泛适用于各领域复杂任务场景;支持自动对接百度搜索插件,保障问答信息时效。相较于ERNIE 4.0在性能表现上更优秀',
|
85
|
+
displayName: 'ERNIE 4.0 Turbo 8K Preview',
|
86
|
+
id: 'ERNIE-4.0-Turbo-8K-Preview',
|
87
|
+
pricing: {
|
88
|
+
currency: 'CNY',
|
89
|
+
input: 20,
|
90
|
+
output: 60,
|
91
|
+
},
|
92
|
+
tokens: 8192,
|
93
|
+
},
|
94
|
+
{
|
95
|
+
description:
|
96
|
+
'百度自研的轻量级大语言模型,兼顾优异的模型效果与推理性能,效果比ERNIE Lite更优,适合低算力AI加速卡推理使用。',
|
97
|
+
displayName: 'ERNIE Lite Pro 128K',
|
98
|
+
enabled: true,
|
99
|
+
id: 'ERNIE-Lite-Pro-128K',
|
100
|
+
pricing: {
|
101
|
+
currency: 'CNY',
|
102
|
+
input: 0.2,
|
103
|
+
output: 0.4,
|
104
|
+
},
|
105
|
+
tokens: 128_000,
|
106
|
+
},
|
107
|
+
{
|
108
|
+
description:
|
109
|
+
'百度2024年最新发布的自研高性能大语言模型,通用能力优异,效果比ERNIE Speed更优,适合作为基座模型进行精调,更好地处理特定场景问题,同时具备极佳的推理性能。',
|
110
|
+
displayName: 'ERNIE Speed Pro 128K',
|
111
|
+
enabled: true,
|
112
|
+
id: 'ERNIE-Speed-Pro-128K',
|
113
|
+
pricing: {
|
114
|
+
currency: 'CNY',
|
115
|
+
input: 0.3,
|
116
|
+
output: 0.6,
|
117
|
+
},
|
118
|
+
tokens: 128_000,
|
119
|
+
},
|
120
|
+
{
|
121
|
+
description:
|
122
|
+
'百度2024年最新发布的自研高性能大语言模型,通用能力优异,适合作为基座模型进行精调,更好地处理特定场景问题,同时具备极佳的推理性能。',
|
123
|
+
displayName: 'ERNIE Speed 128K',
|
124
|
+
id: 'ERNIE-Speed-128K',
|
125
|
+
pricing: {
|
126
|
+
currency: 'CNY',
|
127
|
+
input: 0,
|
128
|
+
output: 0,
|
129
|
+
},
|
130
|
+
tokens: 128_000,
|
131
|
+
},
|
132
|
+
{
|
133
|
+
description:
|
134
|
+
'百度自研的垂直场景大语言模型,适合游戏NPC、客服对话、对话角色扮演等应用场景,人设风格更为鲜明、一致,指令遵循能力更强,推理性能更优。',
|
135
|
+
displayName: 'ERNIE Character 8K',
|
136
|
+
id: 'ERNIE-Character-8K',
|
137
|
+
pricing: {
|
138
|
+
currency: 'CNY',
|
139
|
+
input: 4,
|
140
|
+
output: 8,
|
141
|
+
},
|
142
|
+
tokens: 8192,
|
143
|
+
},
|
144
|
+
],
|
145
|
+
checkModel: 'ERNIE-Speed-128K',
|
146
|
+
description:
|
147
|
+
'企业级一站式大模型与AI原生应用开发及服务平台,提供最全面易用的生成式人工智能模型开发、应用开发全流程工具链',
|
148
|
+
disableBrowserRequest: true,
|
149
|
+
id: 'wenxin',
|
150
|
+
modelsUrl: 'https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu#%E5%AF%B9%E8%AF%9Dchat',
|
151
|
+
name: 'Wenxin',
|
152
|
+
smoothing: {
|
153
|
+
speed: 2,
|
154
|
+
text: true,
|
155
|
+
},
|
156
|
+
url: 'https://cloud.baidu.com/wenxin.html',
|
157
|
+
};
|
158
|
+
|
159
|
+
export default BaiduWenxin;
|
package/src/const/auth.ts
CHANGED
@@ -25,6 +25,7 @@ import {
|
|
25
25
|
TaichuProviderCard,
|
26
26
|
TogetherAIProviderCard,
|
27
27
|
UpstageProviderCard,
|
28
|
+
WenxinProviderCard,
|
28
29
|
ZeroOneProviderCard,
|
29
30
|
ZhiPuProviderCard,
|
30
31
|
filterEnabledModels,
|
@@ -141,6 +142,10 @@ export const DEFAULT_LLM_CONFIG: UserModelProviderConfig = {
|
|
141
142
|
enabled: false,
|
142
143
|
enabledModels: filterEnabledModels(UpstageProviderCard),
|
143
144
|
},
|
145
|
+
wenxin: {
|
146
|
+
enabled: false,
|
147
|
+
enabledModels: filterEnabledModels(WenxinProviderCard),
|
148
|
+
},
|
144
149
|
zeroone: {
|
145
150
|
enabled: false,
|
146
151
|
enabledModels: filterEnabledModels(ZeroOneProviderCard),
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import { Wenxin } from '@lobehub/icons';
|
2
|
+
import { Input } from 'antd';
|
3
|
+
import { memo } from 'react';
|
4
|
+
import { useTranslation } from 'react-i18next';
|
5
|
+
|
6
|
+
import { ModelProvider } from '@/libs/agent-runtime';
|
7
|
+
import { useUserStore } from '@/store/user';
|
8
|
+
import { keyVaultsConfigSelectors } from '@/store/user/selectors';
|
9
|
+
|
10
|
+
import { FormAction } from '../style';
|
11
|
+
|
12
|
+
const WenxinForm = memo(() => {
|
13
|
+
const { t } = useTranslation('modelProvider');
|
14
|
+
|
15
|
+
const [accessKey, secretKey, setConfig] = useUserStore((s) => [
|
16
|
+
keyVaultsConfigSelectors.wenxinConfig(s).accessKey,
|
17
|
+
keyVaultsConfigSelectors.wenxinConfig(s).secretKey,
|
18
|
+
s.updateKeyVaultConfig,
|
19
|
+
]);
|
20
|
+
|
21
|
+
return (
|
22
|
+
<FormAction
|
23
|
+
avatar={<Wenxin.Color size={56} />}
|
24
|
+
description={t('wenxin.unlock.description')}
|
25
|
+
title={t('wenxin.unlock.title')}
|
26
|
+
>
|
27
|
+
<Input.Password
|
28
|
+
autoComplete={'new-password'}
|
29
|
+
onChange={(e) => {
|
30
|
+
setConfig(ModelProvider.Wenxin, { accessKey: e.target.value });
|
31
|
+
}}
|
32
|
+
placeholder={'Access Key'}
|
33
|
+
type={'block'}
|
34
|
+
value={accessKey}
|
35
|
+
/>
|
36
|
+
<Input.Password
|
37
|
+
autoComplete={'new-password'}
|
38
|
+
onChange={(e) => {
|
39
|
+
setConfig(ModelProvider.Wenxin, { secretKey: e.target.value });
|
40
|
+
}}
|
41
|
+
placeholder={'Secret Key'}
|
42
|
+
type={'block'}
|
43
|
+
value={secretKey}
|
44
|
+
/>
|
45
|
+
</FormAction>
|
46
|
+
);
|
47
|
+
});
|
48
|
+
|
49
|
+
export default WenxinForm;
|
@@ -10,6 +10,7 @@ import { GlobalLLMProviderKey } from '@/types/user/settings';
|
|
10
10
|
|
11
11
|
import BedrockForm from './Bedrock';
|
12
12
|
import ProviderApiKeyForm from './ProviderApiKeyForm';
|
13
|
+
import WenxinForm from './Wenxin';
|
13
14
|
|
14
15
|
interface APIKeyFormProps {
|
15
16
|
id: string;
|
@@ -65,6 +66,8 @@ const APIKeyForm = memo<APIKeyFormProps>(({ id, provider }) => {
|
|
65
66
|
<Center gap={16} style={{ maxWidth: 300 }}>
|
66
67
|
{provider === ModelProvider.Bedrock ? (
|
67
68
|
<BedrockForm />
|
69
|
+
) : provider === ModelProvider.Wenxin ? (
|
70
|
+
<WenxinForm />
|
68
71
|
) : (
|
69
72
|
<ProviderApiKeyForm
|
70
73
|
apiKeyPlaceholder={apiKeyPlaceholder}
|
@@ -27,6 +27,7 @@ import {
|
|
27
27
|
ModelProvider,
|
28
28
|
} from '@/libs/agent-runtime';
|
29
29
|
import { LobeStepfunAI } from '@/libs/agent-runtime/stepfun';
|
30
|
+
import LobeWenxinAI from '@/libs/agent-runtime/wenxin';
|
30
31
|
|
31
32
|
import { AgentChatOptions } from './AgentRuntime';
|
32
33
|
import { LobeBedrockAIParams } from './bedrock';
|
@@ -3,6 +3,7 @@
|
|
3
3
|
export const AgentRuntimeErrorType = {
|
4
4
|
AgentRuntimeError: 'AgentRuntimeError', // Agent Runtime 模块运行时错误
|
5
5
|
LocationNotSupportError: 'LocationNotSupportError',
|
6
|
+
QuotaLimitReached: 'QuotaLimitReached',
|
6
7
|
|
7
8
|
InvalidProviderAPIKey: 'InvalidProviderAPIKey',
|
8
9
|
ProviderBizError: 'ProviderBizError',
|
@@ -0,0 +1,149 @@
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
2
|
+
|
3
|
+
import * as uuidModule from '@/utils/uuid';
|
4
|
+
|
5
|
+
import { ChatResp } from '../../wenxin/type';
|
6
|
+
import { WenxinResultToStream, WenxinStream } from './wenxin';
|
7
|
+
|
8
|
+
const dataStream = [
|
9
|
+
{
|
10
|
+
id: 'as-vb0m37ti8y',
|
11
|
+
object: 'chat.completion',
|
12
|
+
created: 1709089502,
|
13
|
+
sentence_id: 0,
|
14
|
+
is_end: false,
|
15
|
+
is_truncated: false,
|
16
|
+
result: '当然可以,',
|
17
|
+
need_clear_history: false,
|
18
|
+
finish_reason: 'normal',
|
19
|
+
usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },
|
20
|
+
},
|
21
|
+
{
|
22
|
+
id: 'as-vb0m37ti8y',
|
23
|
+
object: 'chat.completion',
|
24
|
+
created: 1709089504,
|
25
|
+
sentence_id: 1,
|
26
|
+
is_end: false,
|
27
|
+
is_truncated: false,
|
28
|
+
result:
|
29
|
+
'以下是一些建议的自驾游路线,它们涵盖了各种不同的风景和文化体验:\n\n1. **西安-敦煌历史文化之旅**:\n\n\n\t* 路线:西安',
|
30
|
+
need_clear_history: false,
|
31
|
+
finish_reason: 'normal',
|
32
|
+
usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },
|
33
|
+
},
|
34
|
+
{
|
35
|
+
id: 'as-vb0m37ti8y',
|
36
|
+
object: 'chat.completion',
|
37
|
+
created: 1709089506,
|
38
|
+
sentence_id: 2,
|
39
|
+
is_end: false,
|
40
|
+
is_truncated: false,
|
41
|
+
result: ' - 天水 - 兰州 - 嘉峪关 - 敦煌\n\t* 特点:此路线让您领略到中国西北的丰富历史文化。',
|
42
|
+
need_clear_history: false,
|
43
|
+
finish_reason: 'normal',
|
44
|
+
usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },
|
45
|
+
},
|
46
|
+
{
|
47
|
+
id: 'as-vb0m37ti8y',
|
48
|
+
object: 'chat.completion',
|
49
|
+
created: 1709089508,
|
50
|
+
sentence_id: 3,
|
51
|
+
is_end: false,
|
52
|
+
is_truncated: false,
|
53
|
+
result: '您可以参观西安的兵马俑、大雁塔,体验兰州的黄河风情,以及在敦煌欣赏壮丽的莫高窟。',
|
54
|
+
need_clear_history: false,
|
55
|
+
finish_reason: 'normal',
|
56
|
+
usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },
|
57
|
+
},
|
58
|
+
{
|
59
|
+
id: 'as-vb0m37ti8y',
|
60
|
+
object: 'chat.completion',
|
61
|
+
created: 1709089511,
|
62
|
+
sentence_id: 4,
|
63
|
+
is_end: false,
|
64
|
+
is_truncated: false,
|
65
|
+
result: '\n2. **海南环岛热带风情游**:\n\n\n\t* 路线:海口 - 三亚 - 陵水 - 万宁 - 文昌 - 海',
|
66
|
+
need_clear_history: false,
|
67
|
+
finish_reason: 'normal',
|
68
|
+
usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },
|
69
|
+
},
|
70
|
+
{
|
71
|
+
id: 'as-vb0m37ti8y',
|
72
|
+
object: 'chat.completion',
|
73
|
+
created: 1709089512,
|
74
|
+
sentence_id: 5,
|
75
|
+
is_end: false,
|
76
|
+
is_truncated: false,
|
77
|
+
result:
|
78
|
+
'口\n\t* 特点:海南岛是中国唯一的黎族聚居区,这里有独特的热带风情、美丽的海滩和丰富的水果。',
|
79
|
+
need_clear_history: false,
|
80
|
+
finish_reason: 'normal',
|
81
|
+
usage: { prompt_tokens: 5, completion_tokens: 153, total_tokens: 158 },
|
82
|
+
},
|
83
|
+
];
|
84
|
+
|
85
|
+
describe('WenxinStream', () => {
|
86
|
+
it('should transform Wenxin stream to protocol stream', async () => {
|
87
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
88
|
+
|
89
|
+
const mockWenxinStream: AsyncIterable<ChatResp> = {
|
90
|
+
// @ts-ignore
|
91
|
+
async *[Symbol.asyncIterator]() {
|
92
|
+
for (const item of dataStream) {
|
93
|
+
yield item;
|
94
|
+
}
|
95
|
+
},
|
96
|
+
};
|
97
|
+
|
98
|
+
const stream = WenxinResultToStream(mockWenxinStream);
|
99
|
+
|
100
|
+
const onStartMock = vi.fn();
|
101
|
+
const onTextMock = vi.fn();
|
102
|
+
const onTokenMock = vi.fn();
|
103
|
+
const onCompletionMock = vi.fn();
|
104
|
+
|
105
|
+
const protocolStream = WenxinStream(stream, {
|
106
|
+
onStart: onStartMock,
|
107
|
+
onText: onTextMock,
|
108
|
+
onToken: onTokenMock,
|
109
|
+
onCompletion: onCompletionMock,
|
110
|
+
});
|
111
|
+
|
112
|
+
const decoder = new TextDecoder();
|
113
|
+
const chunks = [];
|
114
|
+
|
115
|
+
// @ts-ignore
|
116
|
+
for await (const chunk of protocolStream) {
|
117
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
118
|
+
}
|
119
|
+
|
120
|
+
expect(chunks).toEqual(
|
121
|
+
[
|
122
|
+
'id: as-vb0m37ti8y',
|
123
|
+
'event: text',
|
124
|
+
`data: "当然可以,"\n`,
|
125
|
+
'id: as-vb0m37ti8y',
|
126
|
+
'event: text',
|
127
|
+
`data: "以下是一些建议的自驾游路线,它们涵盖了各种不同的风景和文化体验:\\n\\n1. **西安-敦煌历史文化之旅**:\\n\\n\\n\\t* 路线:西安"\n`,
|
128
|
+
'id: as-vb0m37ti8y',
|
129
|
+
'event: text',
|
130
|
+
`data: " - 天水 - 兰州 - 嘉峪关 - 敦煌\\n\\t* 特点:此路线让您领略到中国西北的丰富历史文化。"\n`,
|
131
|
+
'id: as-vb0m37ti8y',
|
132
|
+
'event: text',
|
133
|
+
`data: "您可以参观西安的兵马俑、大雁塔,体验兰州的黄河风情,以及在敦煌欣赏壮丽的莫高窟。"\n`,
|
134
|
+
'id: as-vb0m37ti8y',
|
135
|
+
'event: text',
|
136
|
+
`data: "\\n2. **海南环岛热带风情游**:\\n\\n\\n\\t* 路线:海口 - 三亚 - 陵水 - 万宁 - 文昌 - 海"\n`,
|
137
|
+
'id: as-vb0m37ti8y',
|
138
|
+
'event: text',
|
139
|
+
`data: "口\\n\\t* 特点:海南岛是中国唯一的黎族聚居区,这里有独特的热带风情、美丽的海滩和丰富的水果。"\n`,
|
140
|
+
].map((item) => `${item}\n`),
|
141
|
+
);
|
142
|
+
|
143
|
+
expect(onStartMock).toHaveBeenCalledTimes(1);
|
144
|
+
expect(onTextMock).toHaveBeenNthCalledWith(1, '"当然可以,"');
|
145
|
+
expect(onTextMock).toHaveBeenNthCalledWith(2, '"以下是一些建议的自驾游路线,它们涵盖了各种不同的风景和文化体验:\\n\\n1. **西安-敦煌历史文化之旅**:\\n\\n\\n\\t* 路线:西安"');
|
146
|
+
expect(onTokenMock).toHaveBeenCalledTimes(6);
|
147
|
+
expect(onCompletionMock).toHaveBeenCalledTimes(1);
|
148
|
+
});
|
149
|
+
});
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { readableFromAsyncIterable } from 'ai';
|
2
|
+
|
3
|
+
import { ChatStreamCallbacks } from '@/libs/agent-runtime';
|
4
|
+
import { nanoid } from '@/utils/uuid';
|
5
|
+
|
6
|
+
import { ChatResp } from '../../wenxin/type';
|
7
|
+
import {
|
8
|
+
StreamProtocolChunk,
|
9
|
+
StreamStack,
|
10
|
+
chatStreamable,
|
11
|
+
createCallbacksTransformer,
|
12
|
+
createSSEProtocolTransformer,
|
13
|
+
} from './protocol';
|
14
|
+
|
15
|
+
const transformERNIEBotStream = (chunk: ChatResp): StreamProtocolChunk => {
|
16
|
+
const finished = chunk.is_end;
|
17
|
+
if (finished) {
|
18
|
+
return { data: chunk.finish_reason || 'stop', id: chunk.id, type: 'stop' };
|
19
|
+
}
|
20
|
+
|
21
|
+
if (chunk.result) {
|
22
|
+
return { data: chunk.result, id: chunk.id, type: 'text' };
|
23
|
+
}
|
24
|
+
|
25
|
+
return {
|
26
|
+
data: chunk,
|
27
|
+
id: chunk.id,
|
28
|
+
type: 'data',
|
29
|
+
};
|
30
|
+
};
|
31
|
+
|
32
|
+
export const WenxinResultToStream = (stream: AsyncIterable<ChatResp>) => {
|
33
|
+
// make the response to the streamable format
|
34
|
+
return readableFromAsyncIterable(chatStreamable(stream));
|
35
|
+
};
|
36
|
+
|
37
|
+
export const WenxinStream = (
|
38
|
+
rawStream: ReadableStream<ChatResp>,
|
39
|
+
callbacks?: ChatStreamCallbacks,
|
40
|
+
) => {
|
41
|
+
const streamStack: StreamStack = { id: 'chat_' + nanoid() };
|
42
|
+
|
43
|
+
return rawStream
|
44
|
+
.pipeThrough(createSSEProtocolTransformer(transformERNIEBotStream, streamStack))
|
45
|
+
.pipeThrough(createCallbacksTransformer(callbacks));
|
46
|
+
};
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import { ChatCompletion } from '@baiducloud/qianfan';
|
2
|
+
|
3
|
+
// 如果引入了这个类型,那么在跑 type-check 的 tsc 检查中就会抛错,大无语
|
4
|
+
// import type QianFanClient from '@baiducloud/qianfan/src/ChatCompletion/index';
|
5
|
+
import { safeParseJSON } from '@/utils/safeParseJSON';
|
6
|
+
|
7
|
+
import { LobeRuntimeAI } from '../BaseAI';
|
8
|
+
import { AgentRuntimeErrorType } from '../error';
|
9
|
+
import { ChatCompetitionOptions, ChatStreamPayload } from '../types';
|
10
|
+
import { AgentRuntimeError } from '../utils/createError';
|
11
|
+
import { debugStream } from '../utils/debugStream';
|
12
|
+
import { StreamingResponse } from '../utils/response';
|
13
|
+
import { WenxinResultToStream, WenxinStream } from '../utils/streams/wenxin';
|
14
|
+
import { ChatResp } from './type';
|
15
|
+
|
16
|
+
interface ChatErrorCode {
|
17
|
+
error_code: number;
|
18
|
+
error_msg: string;
|
19
|
+
}
|
20
|
+
|
21
|
+
export interface LobeWenxinAIParams {
|
22
|
+
accessKey?: string;
|
23
|
+
baseURL?: string;
|
24
|
+
secretKey?: string;
|
25
|
+
}
|
26
|
+
|
27
|
+
export class LobeWenxinAI implements LobeRuntimeAI {
|
28
|
+
private client: any;
|
29
|
+
baseURL?: string;
|
30
|
+
|
31
|
+
constructor({ accessKey, baseURL, secretKey }: LobeWenxinAIParams = {}) {
|
32
|
+
if (!accessKey || !secretKey)
|
33
|
+
throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
|
34
|
+
|
35
|
+
this.client = new ChatCompletion({
|
36
|
+
QIANFAN_ACCESS_KEY: accessKey,
|
37
|
+
QIANFAN_SECRET_KEY: secretKey,
|
38
|
+
});
|
39
|
+
this.baseURL = baseURL;
|
40
|
+
}
|
41
|
+
|
42
|
+
async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
|
43
|
+
try {
|
44
|
+
const result = await this.client.chat(
|
45
|
+
{ messages: payload.messages as any, stream: true, user_id: options?.user },
|
46
|
+
payload.model,
|
47
|
+
);
|
48
|
+
|
49
|
+
const wenxinStream = WenxinResultToStream(result as AsyncIterable<ChatResp>);
|
50
|
+
|
51
|
+
const [prod, useForDebug] = wenxinStream.tee();
|
52
|
+
|
53
|
+
if (process.env.DEBUG_WENXIN_CHAT_COMPLETION === '1') {
|
54
|
+
debugStream(useForDebug).catch();
|
55
|
+
}
|
56
|
+
|
57
|
+
const stream = WenxinStream(prod, options?.callback);
|
58
|
+
|
59
|
+
// Respond with the stream
|
60
|
+
return StreamingResponse(stream, { headers: options?.headers });
|
61
|
+
} catch (e) {
|
62
|
+
const err = e as Error;
|
63
|
+
|
64
|
+
const error: ChatErrorCode | undefined = safeParseJSON(err.message);
|
65
|
+
|
66
|
+
if (!error) {
|
67
|
+
throw AgentRuntimeError.createError(AgentRuntimeErrorType.AgentRuntimeError, {
|
68
|
+
message: err.message,
|
69
|
+
name: err.name,
|
70
|
+
});
|
71
|
+
}
|
72
|
+
|
73
|
+
// 文心一言错误码
|
74
|
+
// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/tlmyncueh
|
75
|
+
switch (error.error_code) {
|
76
|
+
// Invalid API key or access key
|
77
|
+
case 100:
|
78
|
+
case 13:
|
79
|
+
case 14: {
|
80
|
+
throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey, error);
|
81
|
+
}
|
82
|
+
|
83
|
+
// quota limit
|
84
|
+
case 4:
|
85
|
+
case 17:
|
86
|
+
case 18:
|
87
|
+
case 19:
|
88
|
+
case 336_501:
|
89
|
+
case 336_502:
|
90
|
+
case 336_503:
|
91
|
+
case 336_504:
|
92
|
+
case 336_505:
|
93
|
+
case 336_507: {
|
94
|
+
throw AgentRuntimeError.createError(AgentRuntimeErrorType.QuotaLimitReached, {
|
95
|
+
errorCode: error.error_code,
|
96
|
+
message: `${error.error_msg} | you can visit https://cloud.baidu.com/doc/WENXINWORKSHOP/s/tlmyncueh for more information about the error code`,
|
97
|
+
});
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
throw AgentRuntimeError.createError(AgentRuntimeErrorType.ProviderBizError, error);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
export default LobeWenxinAI;
|
@@ -0,0 +1,84 @@
|
|
1
|
+
/**
|
2
|
+
* token 用量基类
|
3
|
+
*/
|
4
|
+
export interface TokenUsage {
|
5
|
+
/**
|
6
|
+
* 回答tokens数
|
7
|
+
*/
|
8
|
+
completion_tokens?: number;
|
9
|
+
/**
|
10
|
+
* 问题tokens数
|
11
|
+
*/
|
12
|
+
prompt_tokens: number;
|
13
|
+
/**
|
14
|
+
* tokens总数
|
15
|
+
*/
|
16
|
+
total_tokens: number;
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* 响应基类
|
21
|
+
*/
|
22
|
+
export interface RespBase {
|
23
|
+
/**
|
24
|
+
* 时间戳
|
25
|
+
*/
|
26
|
+
created: number;
|
27
|
+
/**
|
28
|
+
* 本轮对话的id
|
29
|
+
*/
|
30
|
+
id: string;
|
31
|
+
/**
|
32
|
+
* 表示当前子句是否是最后一句。只有在流式接口模式下会返回该字段
|
33
|
+
*/
|
34
|
+
is_end?: boolean;
|
35
|
+
/**
|
36
|
+
* 1:表示输入内容无安全风险
|
37
|
+
* 0:表示输入内容有安全风险
|
38
|
+
*/
|
39
|
+
is_safe?: number;
|
40
|
+
/**
|
41
|
+
* 回包类型。
|
42
|
+
*
|
43
|
+
* chat.completion:多轮对话返回
|
44
|
+
*/
|
45
|
+
object: string;
|
46
|
+
/**
|
47
|
+
* 对话返回结果
|
48
|
+
*/
|
49
|
+
result: string;
|
50
|
+
/**
|
51
|
+
* 表示当前子句的序号。只有在流式接口模式下会返回该字段
|
52
|
+
*/
|
53
|
+
sentence_id?: number;
|
54
|
+
/**
|
55
|
+
* token统计信息,token数 = 汉字数+单词数*1.3 (仅为估算逻辑)
|
56
|
+
*/
|
57
|
+
usage: TokenUsage;
|
58
|
+
}
|
59
|
+
|
60
|
+
export interface ChatResp extends RespBase {
|
61
|
+
/**
|
62
|
+
* 当 need_clear_history 为 true 时,此字段会告知第几轮对话有敏感信息,如果是当前问题,ban_round=-1
|
63
|
+
*/
|
64
|
+
ban_round: number;
|
65
|
+
/**
|
66
|
+
* 输出内容标识,说明:
|
67
|
+
* · normal:输出内容完全由大模型生成,未触发截断、替换
|
68
|
+
* · stop:输出结果命中入参stop中指定的字段后被截断
|
69
|
+
* · length:达到了最大的token数,根据EB返回结果is_truncated来截断
|
70
|
+
* · content_filter:输出内容被截断、兜底、替换为**等
|
71
|
+
*/
|
72
|
+
finish_reason: string;
|
73
|
+
/**
|
74
|
+
* 当前生成的结果是否被截断
|
75
|
+
*/
|
76
|
+
is_truncated?: boolean;
|
77
|
+
/**
|
78
|
+
* 表示用户输入是否存在安全,是否关闭当前会话,清理历史会话信息
|
79
|
+
*
|
80
|
+
* true:是,表示用户输入存在安全风险,建议关闭当前会话,清理历史会话信息
|
81
|
+
* false:否,表示用户输入无安全风险
|
82
|
+
*/
|
83
|
+
need_clear_history: boolean;
|
84
|
+
}
|
@@ -79,6 +79,8 @@ export default {
|
|
79
79
|
InvalidClerkUser: '很抱歉,你当前尚未登录,请先登录或注册账号后继续操作',
|
80
80
|
LocationNotSupportError:
|
81
81
|
'很抱歉,你的所在地区不支持此模型服务,可能是由于区域限制或服务未开通。请确认当前地区是否支持使用此服务,或尝试使用切换到其他地区后重试。',
|
82
|
+
QuotaLimitReached:
|
83
|
+
'很抱歉,当前 Token 用量或请求次数已达该秘钥的配额(quota)上限,请增加该秘钥的配额或稍后再试',
|
82
84
|
|
83
85
|
InvalidProviderAPIKey: '{{provider}} API Key 不正确或为空,请检查 {{provider}} API Key 后重试',
|
84
86
|
ProviderBizError: '请求 {{provider}} 服务出错,请根据以下信息排查或重试',
|