@lobehub/chat 1.21.15 → 1.22.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 +50 -0
- package/Dockerfile +16 -8
- package/Dockerfile.database +20 -9
- package/README.zh-CN.md +8 -6
- package/docs/self-hosting/environment-variables.mdx +71 -0
- package/docs/usage/providers/wenxin.mdx +4 -3
- package/docs/usage/providers/wenxin.zh-CN.mdx +4 -3
- package/locales/ar/error.json +1 -0
- package/locales/ar/modelProvider.json +7 -0
- package/locales/ar/models.json +18 -6
- package/locales/ar/providers.json +3 -0
- package/locales/bg-BG/error.json +1 -0
- package/locales/bg-BG/modelProvider.json +7 -0
- package/locales/bg-BG/models.json +18 -6
- package/locales/bg-BG/providers.json +3 -0
- package/locales/de-DE/error.json +1 -0
- package/locales/de-DE/modelProvider.json +7 -0
- package/locales/de-DE/models.json +18 -6
- package/locales/de-DE/providers.json +3 -0
- package/locales/en-US/error.json +1 -0
- package/locales/en-US/modelProvider.json +7 -0
- package/locales/en-US/models.json +18 -6
- package/locales/en-US/providers.json +3 -0
- package/locales/es-ES/error.json +1 -0
- package/locales/es-ES/modelProvider.json +7 -0
- package/locales/es-ES/models.json +18 -6
- package/locales/es-ES/providers.json +3 -0
- package/locales/fr-FR/error.json +1 -0
- package/locales/fr-FR/modelProvider.json +7 -0
- package/locales/fr-FR/models.json +17 -5
- package/locales/fr-FR/providers.json +3 -0
- package/locales/it-IT/error.json +1 -0
- package/locales/it-IT/modelProvider.json +7 -0
- package/locales/it-IT/models.json +18 -6
- package/locales/it-IT/providers.json +3 -0
- package/locales/ja-JP/error.json +1 -0
- package/locales/ja-JP/modelProvider.json +7 -0
- package/locales/ja-JP/models.json +18 -6
- package/locales/ja-JP/providers.json +3 -0
- package/locales/ko-KR/error.json +1 -0
- package/locales/ko-KR/modelProvider.json +7 -0
- package/locales/ko-KR/models.json +17 -5
- package/locales/ko-KR/providers.json +3 -0
- package/locales/nl-NL/error.json +1 -0
- package/locales/nl-NL/modelProvider.json +7 -0
- package/locales/nl-NL/models.json +17 -5
- package/locales/nl-NL/providers.json +3 -0
- package/locales/pl-PL/error.json +1 -0
- package/locales/pl-PL/modelProvider.json +7 -0
- package/locales/pl-PL/models.json +18 -6
- package/locales/pl-PL/providers.json +3 -0
- package/locales/pt-BR/error.json +1 -0
- package/locales/pt-BR/modelProvider.json +7 -0
- package/locales/pt-BR/models.json +18 -6
- package/locales/pt-BR/providers.json +3 -0
- package/locales/ru-RU/error.json +1 -0
- package/locales/ru-RU/modelProvider.json +7 -0
- package/locales/ru-RU/models.json +18 -6
- package/locales/ru-RU/providers.json +3 -0
- package/locales/tr-TR/error.json +1 -0
- package/locales/tr-TR/modelProvider.json +7 -0
- package/locales/tr-TR/models.json +18 -6
- package/locales/tr-TR/providers.json +3 -0
- package/locales/vi-VN/error.json +1 -0
- package/locales/vi-VN/modelProvider.json +7 -0
- package/locales/vi-VN/models.json +18 -6
- package/locales/vi-VN/providers.json +3 -0
- package/locales/zh-CN/error.json +2 -1
- package/locales/zh-CN/modelProvider.json +8 -1
- package/locales/zh-CN/models.json +16 -4
- package/locales/zh-CN/providers.json +3 -0
- package/locales/zh-TW/error.json +1 -0
- package/locales/zh-TW/modelProvider.json +7 -0
- package/locales/zh-TW/models.json +16 -4
- package/locales/zh-TW/providers.json +3 -0
- package/package.json +5 -3
- package/src/app/(main)/settings/llm/ProviderList/HuggingFace/index.tsx +53 -0
- package/src/app/(main)/settings/llm/ProviderList/providers.tsx +12 -1
- package/src/config/llm.ts +10 -0
- package/src/config/modelProviders/huggingface.ts +50 -0
- package/src/config/modelProviders/index.ts +4 -0
- package/src/const/settings/llm.ts +5 -0
- package/src/features/Conversation/Error/index.tsx +1 -0
- package/src/libs/agent-runtime/AgentRuntime.ts +7 -0
- package/src/libs/agent-runtime/error.ts +1 -0
- package/src/libs/agent-runtime/groq/index.ts +1 -1
- package/src/libs/agent-runtime/huggingface/index.ts +48 -0
- package/src/libs/agent-runtime/types/type.ts +1 -0
- package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +58 -20
- package/src/libs/agent-runtime/utils/streams/openai.test.ts +78 -7
- package/src/libs/agent-runtime/utils/streams/openai.ts +38 -5
- package/src/libs/agent-runtime/utils/streams/protocol.ts +63 -4
- package/src/locales/default/error.ts +2 -2
- package/src/locales/default/modelProvider.ts +8 -1
- package/src/server/globalConfig/index.ts +12 -1
- package/src/server/modules/AgentRuntime/index.ts +10 -0
- package/src/services/_url.ts +4 -5
- package/src/types/user/settings/keyVaults.ts +1 -0
- /package/src/app/(backend)/{api → webapi}/chat/[provider]/route.test.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/[provider]/route.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/anthropic/route.test.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/anthropic/route.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/google/route.test.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/google/route.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/minimax/route.test.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/minimax/route.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/models/[provider]/route.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/openai/route.test.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/openai/route.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/wenxin/route.test.ts +0 -0
- /package/src/app/(backend)/{api → webapi}/chat/wenxin/route.ts +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
import OpenAI, { ClientOptions } from 'openai';
|
2
|
+
import { Stream } from 'openai/streaming';
|
2
3
|
|
3
4
|
import { LOBE_DEFAULT_MODEL_LIST } from '@/config/modelProviders';
|
4
5
|
import { ChatModelCard } from '@/types/llm';
|
@@ -22,7 +23,7 @@ import { desensitizeUrl } from '../desensitizeUrl';
|
|
22
23
|
import { handleOpenAIError } from '../handleOpenAIError';
|
23
24
|
import { convertOpenAIMessages } from '../openaiHelpers';
|
24
25
|
import { StreamingResponse } from '../response';
|
25
|
-
import { OpenAIStream } from '../streams';
|
26
|
+
import { OpenAIStream, OpenAIStreamOptions } from '../streams';
|
26
27
|
|
27
28
|
// the model contains the following keywords is not a chat model, so we should filter them out
|
28
29
|
const CHAT_MODELS_BLOCK_LIST = [
|
@@ -39,6 +40,15 @@ const CHAT_MODELS_BLOCK_LIST = [
|
|
39
40
|
|
40
41
|
type ConstructorOptions<T extends Record<string, any> = any> = ClientOptions & T;
|
41
42
|
|
43
|
+
export interface CustomClientOptions<T extends Record<string, any> = any> {
|
44
|
+
createChatCompletionStream?: (
|
45
|
+
client: any,
|
46
|
+
payload: ChatStreamPayload,
|
47
|
+
instance: any,
|
48
|
+
) => ReadableStream<any>;
|
49
|
+
createClient?: (options: ConstructorOptions<T>) => any;
|
50
|
+
}
|
51
|
+
|
42
52
|
interface OpenAICompatibleFactoryOptions<T extends Record<string, any> = any> {
|
43
53
|
baseURL?: string;
|
44
54
|
chatCompletion?: {
|
@@ -50,9 +60,14 @@ interface OpenAICompatibleFactoryOptions<T extends Record<string, any> = any> {
|
|
50
60
|
payload: ChatStreamPayload,
|
51
61
|
options: ConstructorOptions<T>,
|
52
62
|
) => OpenAI.ChatCompletionCreateParamsStreaming;
|
63
|
+
handleStreamBizErrorType?: (error: {
|
64
|
+
message: string;
|
65
|
+
name: string;
|
66
|
+
}) => ILobeAgentRuntimeErrorType | undefined;
|
53
67
|
noUserId?: boolean;
|
54
68
|
};
|
55
69
|
constructorOptions?: ConstructorOptions<T>;
|
70
|
+
customClient?: CustomClientOptions<T>;
|
56
71
|
debug?: {
|
57
72
|
chatCompletion: () => boolean;
|
58
73
|
};
|
@@ -129,6 +144,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
|
|
129
144
|
constructorOptions,
|
130
145
|
chatCompletion,
|
131
146
|
models,
|
147
|
+
customClient,
|
132
148
|
}: OpenAICompatibleFactoryOptions<T>) => {
|
133
149
|
const ErrorType = {
|
134
150
|
bizError: errorType?.bizError || AgentRuntimeErrorType.ProviderBizError,
|
@@ -136,9 +152,9 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
|
|
136
152
|
};
|
137
153
|
|
138
154
|
return class LobeOpenAICompatibleAI implements LobeRuntimeAI {
|
139
|
-
client
|
155
|
+
client!: OpenAI;
|
140
156
|
|
141
|
-
baseURL
|
157
|
+
baseURL!: string;
|
142
158
|
private _options: ConstructorOptions<T>;
|
143
159
|
|
144
160
|
constructor(options: ClientOptions & Record<string, any> = {}) {
|
@@ -148,8 +164,16 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
|
|
148
164
|
|
149
165
|
if (!apiKey) throw AgentRuntimeError.createError(ErrorType?.invalidAPIKey);
|
150
166
|
|
151
|
-
|
152
|
-
|
167
|
+
const initOptions = { apiKey, baseURL, ...constructorOptions, ...res };
|
168
|
+
|
169
|
+
// if the custom client is provided, use it as client
|
170
|
+
if (customClient?.createClient) {
|
171
|
+
this.client = customClient.createClient(initOptions as any);
|
172
|
+
} else {
|
173
|
+
this.client = new OpenAI(initOptions);
|
174
|
+
}
|
175
|
+
|
176
|
+
this.baseURL = baseURL || this.client.baseURL;
|
153
177
|
}
|
154
178
|
|
155
179
|
async chat({ responseMode, ...payload }: ChatStreamPayload, options?: ChatCompetitionOptions) {
|
@@ -163,27 +187,41 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
|
|
163
187
|
|
164
188
|
const messages = await convertOpenAIMessages(postPayload.messages);
|
165
189
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
190
|
+
let response: Stream<OpenAI.Chat.Completions.ChatCompletionChunk>;
|
191
|
+
|
192
|
+
const streamOptions: OpenAIStreamOptions = {
|
193
|
+
bizErrorTypeTransformer: chatCompletion?.handleStreamBizErrorType,
|
194
|
+
callbacks: options?.callback,
|
195
|
+
provider,
|
196
|
+
};
|
197
|
+
if (customClient?.createChatCompletionStream) {
|
198
|
+
response = customClient.createChatCompletionStream(this.client, payload, this) as any;
|
199
|
+
} else {
|
200
|
+
response = await this.client.chat.completions.create(
|
201
|
+
{
|
202
|
+
...postPayload,
|
203
|
+
messages,
|
204
|
+
...(chatCompletion?.noUserId ? {} : { user: options?.user }),
|
205
|
+
},
|
206
|
+
{
|
207
|
+
// https://github.com/lobehub/lobe-chat/pull/318
|
208
|
+
headers: { Accept: '*/*' },
|
209
|
+
signal: options?.signal,
|
210
|
+
},
|
211
|
+
);
|
212
|
+
}
|
178
213
|
|
179
214
|
if (postPayload.stream) {
|
180
215
|
const [prod, useForDebug] = response.tee();
|
181
216
|
|
182
217
|
if (debug?.chatCompletion?.()) {
|
183
|
-
|
218
|
+
const useForDebugStream =
|
219
|
+
useForDebug instanceof ReadableStream ? useForDebug : useForDebug.toReadableStream();
|
220
|
+
|
221
|
+
debugStream(useForDebugStream).catch(console.error);
|
184
222
|
}
|
185
223
|
|
186
|
-
return StreamingResponse(OpenAIStream(prod,
|
224
|
+
return StreamingResponse(OpenAIStream(prod, streamOptions), {
|
187
225
|
headers: options?.headers,
|
188
226
|
});
|
189
227
|
}
|
@@ -196,7 +234,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
|
|
196
234
|
|
197
235
|
const stream = transformResponseToStream(response as unknown as OpenAI.ChatCompletion);
|
198
236
|
|
199
|
-
return StreamingResponse(OpenAIStream(stream,
|
237
|
+
return StreamingResponse(OpenAIStream(stream, streamOptions), {
|
200
238
|
headers: options?.headers,
|
201
239
|
});
|
202
240
|
} catch (error) {
|
@@ -1,6 +1,9 @@
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
2
2
|
|
3
|
+
import { AgentRuntimeErrorType } from '@/libs/agent-runtime';
|
4
|
+
|
3
5
|
import { OpenAIStream } from './openai';
|
6
|
+
import { FIRST_CHUNK_ERROR_KEY } from './protocol';
|
4
7
|
|
5
8
|
describe('OpenAIStream', () => {
|
6
9
|
it('should transform OpenAI stream to protocol stream', async () => {
|
@@ -45,10 +48,12 @@ describe('OpenAIStream', () => {
|
|
45
48
|
const onCompletionMock = vi.fn();
|
46
49
|
|
47
50
|
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
51
|
+
callbacks: {
|
52
|
+
onStart: onStartMock,
|
53
|
+
onText: onTextMock,
|
54
|
+
onToken: onTokenMock,
|
55
|
+
onCompletion: onCompletionMock,
|
56
|
+
},
|
52
57
|
});
|
53
58
|
|
54
59
|
const decoder = new TextDecoder();
|
@@ -189,7 +194,9 @@ describe('OpenAIStream', () => {
|
|
189
194
|
const onToolCallMock = vi.fn();
|
190
195
|
|
191
196
|
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
192
|
-
|
197
|
+
callbacks: {
|
198
|
+
onToolCall: onToolCallMock,
|
199
|
+
},
|
193
200
|
});
|
194
201
|
|
195
202
|
const decoder = new TextDecoder();
|
@@ -281,6 +288,66 @@ describe('OpenAIStream', () => {
|
|
281
288
|
);
|
282
289
|
});
|
283
290
|
|
291
|
+
it('should handle FIRST_CHUNK_ERROR_KEY', async () => {
|
292
|
+
const mockOpenAIStream = new ReadableStream({
|
293
|
+
start(controller) {
|
294
|
+
controller.enqueue({
|
295
|
+
[FIRST_CHUNK_ERROR_KEY]: true,
|
296
|
+
errorType: AgentRuntimeErrorType.ProviderBizError,
|
297
|
+
message: 'Test error',
|
298
|
+
});
|
299
|
+
controller.close();
|
300
|
+
},
|
301
|
+
});
|
302
|
+
|
303
|
+
const protocolStream = OpenAIStream(mockOpenAIStream);
|
304
|
+
|
305
|
+
const decoder = new TextDecoder();
|
306
|
+
const chunks = [];
|
307
|
+
|
308
|
+
// @ts-ignore
|
309
|
+
for await (const chunk of protocolStream) {
|
310
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
311
|
+
}
|
312
|
+
|
313
|
+
expect(chunks).toEqual([
|
314
|
+
'id: first_chunk_error\n',
|
315
|
+
'event: error\n',
|
316
|
+
`data: {"body":{"errorType":"ProviderBizError","message":"Test error"},"type":"ProviderBizError"}\n\n`,
|
317
|
+
]);
|
318
|
+
});
|
319
|
+
|
320
|
+
it('should use bizErrorTypeTransformer', async () => {
|
321
|
+
const mockOpenAIStream = new ReadableStream({
|
322
|
+
start(controller) {
|
323
|
+
controller.enqueue(
|
324
|
+
'%FIRST_CHUNK_ERROR%: ' +
|
325
|
+
JSON.stringify({ message: 'Custom error', name: 'CustomError' }),
|
326
|
+
);
|
327
|
+
controller.close();
|
328
|
+
},
|
329
|
+
});
|
330
|
+
|
331
|
+
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
332
|
+
bizErrorTypeTransformer: () => AgentRuntimeErrorType.PermissionDenied,
|
333
|
+
provider: 'grok',
|
334
|
+
});
|
335
|
+
|
336
|
+
const decoder = new TextDecoder();
|
337
|
+
const chunks = [];
|
338
|
+
|
339
|
+
// @ts-ignore
|
340
|
+
for await (const chunk of protocolStream) {
|
341
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
342
|
+
}
|
343
|
+
|
344
|
+
expect(chunks).toEqual([
|
345
|
+
'id: first_chunk_error\n',
|
346
|
+
'event: error\n',
|
347
|
+
`data: {"body":{"message":"Custom error","errorType":"PermissionDenied","provider":"grok"},"type":"PermissionDenied"}\n\n`,
|
348
|
+
]);
|
349
|
+
});
|
350
|
+
|
284
351
|
describe('Tools Calling', () => {
|
285
352
|
it('should handle OpenAI official tool calls', async () => {
|
286
353
|
const mockOpenAIStream = new ReadableStream({
|
@@ -316,7 +383,9 @@ describe('OpenAIStream', () => {
|
|
316
383
|
const onToolCallMock = vi.fn();
|
317
384
|
|
318
385
|
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
319
|
-
|
386
|
+
callbacks: {
|
387
|
+
onToolCall: onToolCallMock,
|
388
|
+
},
|
320
389
|
});
|
321
390
|
|
322
391
|
const decoder = new TextDecoder();
|
@@ -447,7 +516,9 @@ describe('OpenAIStream', () => {
|
|
447
516
|
const onToolCallMock = vi.fn();
|
448
517
|
|
449
518
|
const protocolStream = OpenAIStream(mockOpenAIStream, {
|
450
|
-
|
519
|
+
callbacks: {
|
520
|
+
onToolCall: onToolCallMock,
|
521
|
+
},
|
451
522
|
});
|
452
523
|
|
453
524
|
const decoder = new TextDecoder();
|
@@ -3,14 +3,17 @@ import type { Stream } from 'openai/streaming';
|
|
3
3
|
|
4
4
|
import { ChatMessageError } from '@/types/message';
|
5
5
|
|
6
|
+
import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../../error';
|
6
7
|
import { ChatStreamCallbacks } from '../../types';
|
7
8
|
import {
|
9
|
+
FIRST_CHUNK_ERROR_KEY,
|
8
10
|
StreamProtocolChunk,
|
9
11
|
StreamProtocolToolCallChunk,
|
10
12
|
StreamStack,
|
11
13
|
StreamToolCallChunkData,
|
12
14
|
convertIterableToStream,
|
13
15
|
createCallbacksTransformer,
|
16
|
+
createFirstErrorHandleTransformer,
|
14
17
|
createSSEProtocolTransformer,
|
15
18
|
generateToolCallId,
|
16
19
|
} from './protocol';
|
@@ -19,6 +22,21 @@ export const transformOpenAIStream = (
|
|
19
22
|
chunk: OpenAI.ChatCompletionChunk,
|
20
23
|
stack?: StreamStack,
|
21
24
|
): StreamProtocolChunk => {
|
25
|
+
// handle the first chunk error
|
26
|
+
if (FIRST_CHUNK_ERROR_KEY in chunk) {
|
27
|
+
delete chunk[FIRST_CHUNK_ERROR_KEY];
|
28
|
+
// @ts-ignore
|
29
|
+
delete chunk['name'];
|
30
|
+
// @ts-ignore
|
31
|
+
delete chunk['stack'];
|
32
|
+
|
33
|
+
const errorData = {
|
34
|
+
body: chunk,
|
35
|
+
type: 'errorType' in chunk ? chunk.errorType : AgentRuntimeErrorType.ProviderBizError,
|
36
|
+
} as ChatMessageError;
|
37
|
+
return { data: errorData, id: 'first_chunk_error', type: 'error' };
|
38
|
+
}
|
39
|
+
|
22
40
|
// maybe need another structure to add support for multiple choices
|
23
41
|
|
24
42
|
try {
|
@@ -97,7 +115,7 @@ export const transformOpenAIStream = (
|
|
97
115
|
'chat response streaming chunk parse error, please contact your API Provider to fix it.',
|
98
116
|
context: { error: { message: err.message, name: err.name }, chunk },
|
99
117
|
},
|
100
|
-
type:
|
118
|
+
type: errorName,
|
101
119
|
} as ChatMessageError;
|
102
120
|
/* eslint-enable */
|
103
121
|
|
@@ -105,16 +123,31 @@ export const transformOpenAIStream = (
|
|
105
123
|
}
|
106
124
|
};
|
107
125
|
|
126
|
+
export interface OpenAIStreamOptions {
|
127
|
+
bizErrorTypeTransformer?: (error: {
|
128
|
+
message: string;
|
129
|
+
name: string;
|
130
|
+
}) => ILobeAgentRuntimeErrorType | undefined;
|
131
|
+
callbacks?: ChatStreamCallbacks;
|
132
|
+
provider?: string;
|
133
|
+
}
|
134
|
+
|
108
135
|
export const OpenAIStream = (
|
109
136
|
stream: Stream<OpenAI.ChatCompletionChunk> | ReadableStream,
|
110
|
-
callbacks
|
137
|
+
{ callbacks, provider, bizErrorTypeTransformer }: OpenAIStreamOptions = {},
|
111
138
|
) => {
|
112
139
|
const streamStack: StreamStack = { id: '' };
|
113
140
|
|
114
141
|
const readableStream =
|
115
142
|
stream instanceof ReadableStream ? stream : convertIterableToStream(stream);
|
116
143
|
|
117
|
-
return
|
118
|
-
|
119
|
-
|
144
|
+
return (
|
145
|
+
readableStream
|
146
|
+
// 1. handle the first error if exist
|
147
|
+
// provider like huggingface or minimax will return error in the stream,
|
148
|
+
// so in the first Transformer, we need to handle the error
|
149
|
+
.pipeThrough(createFirstErrorHandleTransformer(bizErrorTypeTransformer, provider))
|
150
|
+
.pipeThrough(createSSEProtocolTransformer(transformOpenAIStream, streamStack))
|
151
|
+
.pipeThrough(createCallbacksTransformer(callbacks))
|
152
|
+
);
|
120
153
|
};
|
@@ -1,7 +1,7 @@
|
|
1
|
-
import { readableFromAsyncIterable } from 'ai';
|
2
|
-
|
3
1
|
import { ChatStreamCallbacks } from '@/libs/agent-runtime';
|
4
2
|
|
3
|
+
import { AgentRuntimeErrorType } from '../../error';
|
4
|
+
|
5
5
|
export interface StreamStack {
|
6
6
|
id: string;
|
7
7
|
tool?: {
|
@@ -38,17 +38,52 @@ export interface StreamProtocolToolCallChunk {
|
|
38
38
|
export const generateToolCallId = (index: number, functionName?: string) =>
|
39
39
|
`${functionName || 'unknown_tool_call'}_${index}`;
|
40
40
|
|
41
|
-
|
41
|
+
const chatStreamable = async function* <T>(stream: AsyncIterable<T>) {
|
42
42
|
for await (const response of stream) {
|
43
43
|
yield response;
|
44
44
|
}
|
45
45
|
};
|
46
46
|
|
47
|
+
const ERROR_CHUNK_PREFIX = '%FIRST_CHUNK_ERROR%: ';
|
47
48
|
// make the response to the streamable format
|
48
49
|
export const convertIterableToStream = <T>(stream: AsyncIterable<T>) => {
|
49
|
-
|
50
|
+
const iterable = chatStreamable(stream);
|
51
|
+
|
52
|
+
// copy from https://github.com/vercel/ai/blob/d3aa5486529e3d1a38b30e3972b4f4c63ea4ae9a/packages/ai/streams/ai-stream.ts#L284
|
53
|
+
// and add an error handle
|
54
|
+
let it = iterable[Symbol.asyncIterator]();
|
55
|
+
|
56
|
+
return new ReadableStream<T>({
|
57
|
+
async cancel(reason) {
|
58
|
+
await it.return?.(reason);
|
59
|
+
},
|
60
|
+
async pull(controller) {
|
61
|
+
const { done, value } = await it.next();
|
62
|
+
if (done) controller.close();
|
63
|
+
else controller.enqueue(value);
|
64
|
+
},
|
65
|
+
|
66
|
+
async start(controller) {
|
67
|
+
try {
|
68
|
+
const { done, value } = await it.next();
|
69
|
+
if (done) controller.close();
|
70
|
+
else controller.enqueue(value);
|
71
|
+
} catch (e) {
|
72
|
+
const error = e as Error;
|
73
|
+
|
74
|
+
controller.enqueue(
|
75
|
+
(ERROR_CHUNK_PREFIX +
|
76
|
+
JSON.stringify({ message: error.message, name: error.name, stack: error.stack })) as T,
|
77
|
+
);
|
78
|
+
controller.close();
|
79
|
+
}
|
80
|
+
},
|
81
|
+
});
|
50
82
|
};
|
51
83
|
|
84
|
+
/**
|
85
|
+
* Create a transformer to convert the response into an SSE format
|
86
|
+
*/
|
52
87
|
export const createSSEProtocolTransformer = (
|
53
88
|
transformer: (chunk: any, stack: StreamStack) => StreamProtocolChunk,
|
54
89
|
streamStack?: StreamStack,
|
@@ -111,3 +146,27 @@ export function createCallbacksTransformer(cb: ChatStreamCallbacks | undefined)
|
|
111
146
|
},
|
112
147
|
});
|
113
148
|
}
|
149
|
+
|
150
|
+
export const FIRST_CHUNK_ERROR_KEY = '_isFirstChunkError';
|
151
|
+
|
152
|
+
export const createFirstErrorHandleTransformer = (
|
153
|
+
errorHandler?: (errorJson: any) => any,
|
154
|
+
provider?: string,
|
155
|
+
) => {
|
156
|
+
return new TransformStream({
|
157
|
+
transform(chunk, controller) {
|
158
|
+
if (chunk.toString().startsWith(ERROR_CHUNK_PREFIX)) {
|
159
|
+
const errorData = JSON.parse(chunk.toString().replace(ERROR_CHUNK_PREFIX, ''));
|
160
|
+
|
161
|
+
controller.enqueue({
|
162
|
+
...errorData,
|
163
|
+
[FIRST_CHUNK_ERROR_KEY]: true,
|
164
|
+
errorType: errorHandler?.(errorData) || AgentRuntimeErrorType.ProviderBizError,
|
165
|
+
provider,
|
166
|
+
});
|
167
|
+
} else {
|
168
|
+
controller.enqueue(chunk);
|
169
|
+
}
|
170
|
+
},
|
171
|
+
});
|
172
|
+
};
|
@@ -80,8 +80,8 @@ export default {
|
|
80
80
|
LocationNotSupportError:
|
81
81
|
'很抱歉,你的所在地区不支持此模型服务,可能是由于区域限制或服务未开通。请确认当前地区是否支持使用此服务,或尝试使用切换到其他地区后重试。',
|
82
82
|
QuotaLimitReached:
|
83
|
-
'很抱歉,当前 Token
|
84
|
-
|
83
|
+
'很抱歉,当前 Token 用量或请求次数已达该密钥的配额(quota)上限,请增加该密钥的配额或稍后再试',
|
84
|
+
PermissionDenied: '很抱歉,你没有权限访问该服务,请检查你的密钥是否有访问权限',
|
85
85
|
InvalidProviderAPIKey: '{{provider}} API Key 不正确或为空,请检查 {{provider}} API Key 后重试',
|
86
86
|
ProviderBizError: '请求 {{provider}} 服务出错,请根据以下信息排查或重试',
|
87
87
|
/**
|
@@ -54,11 +54,18 @@ export default {
|
|
54
54
|
},
|
55
55
|
github: {
|
56
56
|
personalAccessToken: {
|
57
|
-
desc: '填入你的 Github PAT,点击[这里](https://github.com/settings/tokens) 创建',
|
57
|
+
desc: '填入你的 Github PAT,点击 [这里](https://github.com/settings/tokens) 创建',
|
58
58
|
placeholder: 'ghp_xxxxxx',
|
59
59
|
title: 'Github PAT',
|
60
60
|
},
|
61
61
|
},
|
62
|
+
huggingface: {
|
63
|
+
accessToken: {
|
64
|
+
desc: '填入你的 HuggingFace Token,点击 [这里](https://huggingface.co/settings/tokens) 创建',
|
65
|
+
placeholder: 'hf_xxxxxxxxx',
|
66
|
+
title: 'HuggingFace Token',
|
67
|
+
},
|
68
|
+
},
|
62
69
|
ollama: {
|
63
70
|
checker: {
|
64
71
|
desc: '测试代理地址是否正确填写',
|
@@ -9,6 +9,7 @@ import {
|
|
9
9
|
GithubProviderCard,
|
10
10
|
GoogleProviderCard,
|
11
11
|
GroqProviderCard,
|
12
|
+
HuggingFaceProviderCard,
|
12
13
|
HunyuanProviderCard,
|
13
14
|
NovitaProviderCard,
|
14
15
|
OllamaProviderCard,
|
@@ -98,6 +99,9 @@ export const getServerGlobalConfig = () => {
|
|
98
99
|
FIREWORKSAI_MODEL_LIST,
|
99
100
|
|
100
101
|
ENABLED_WENXIN,
|
102
|
+
|
103
|
+
ENABLED_HUGGINGFACE,
|
104
|
+
HUGGINGFACE_MODEL_LIST,
|
101
105
|
} = getLLMConfig();
|
102
106
|
|
103
107
|
const config: GlobalServerConfig = {
|
@@ -166,6 +170,14 @@ export const getServerGlobalConfig = () => {
|
|
166
170
|
modelString: GROQ_MODEL_LIST,
|
167
171
|
}),
|
168
172
|
},
|
173
|
+
huggingface: {
|
174
|
+
enabled: ENABLED_HUGGINGFACE,
|
175
|
+
enabledModels: extractEnabledModels(HUGGINGFACE_MODEL_LIST),
|
176
|
+
serverModelCards: transformToChatModelCards({
|
177
|
+
defaultChatModels: HuggingFaceProviderCard.chatModels,
|
178
|
+
modelString: HUGGINGFACE_MODEL_LIST,
|
179
|
+
}),
|
180
|
+
},
|
169
181
|
hunyuan: {
|
170
182
|
enabled: ENABLED_HUNYUAN,
|
171
183
|
enabledModels: extractEnabledModels(HUNYUAN_MODEL_LIST),
|
@@ -202,7 +214,6 @@ export const getServerGlobalConfig = () => {
|
|
202
214
|
modelString: OPENAI_MODEL_LIST,
|
203
215
|
}),
|
204
216
|
},
|
205
|
-
|
206
217
|
openrouter: {
|
207
218
|
enabled: ENABLED_OPENROUTER,
|
208
219
|
enabledModels: extractEnabledModels(OPENROUTER_MODEL_LIST),
|
@@ -225,6 +225,16 @@ const getLlmOptionsFromPayload = (provider: string, payload: JWTPayload) => {
|
|
225
225
|
|
226
226
|
return { apiKey, baseURL };
|
227
227
|
}
|
228
|
+
|
229
|
+
case ModelProvider.HuggingFace: {
|
230
|
+
const { HUGGINGFACE_PROXY_URL, HUGGINGFACE_API_KEY } = getLLMConfig();
|
231
|
+
|
232
|
+
const apiKey = apiKeyManager.pick(payload?.apiKey || HUGGINGFACE_API_KEY);
|
233
|
+
const baseURL = payload?.endpoint || HUGGINGFACE_PROXY_URL;
|
234
|
+
|
235
|
+
return { apiKey, baseURL };
|
236
|
+
}
|
237
|
+
|
228
238
|
case ModelProvider.Upstage: {
|
229
239
|
const { UPSTAGE_API_KEY } = getLLMConfig();
|
230
240
|
|
package/src/services/_url.ts
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
// TODO: 未来所有核心路由需要迁移到 trpc,部分不需要迁移的则走 webapi
|
2
|
-
|
3
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
4
2
|
import { transform } from 'lodash-es';
|
5
3
|
|
@@ -17,9 +15,6 @@ const mapWithBasePath = <T extends object>(apis: T): T => {
|
|
17
15
|
};
|
18
16
|
|
19
17
|
export const API_ENDPOINTS = mapWithBasePath({
|
20
|
-
// chat
|
21
|
-
chat: (provider: string) => withBasePath(`/api/chat/${provider}`),
|
22
|
-
chatModels: (provider: string) => withBasePath(`/api/chat/models/${provider}`),
|
23
18
|
oauth: '/api/auth',
|
24
19
|
|
25
20
|
proxy: '/webapi/proxy',
|
@@ -35,6 +30,10 @@ export const API_ENDPOINTS = mapWithBasePath({
|
|
35
30
|
// trace
|
36
31
|
trace: '/webapi/trace',
|
37
32
|
|
33
|
+
// chat
|
34
|
+
chat: (provider: string) => withBasePath(`/webapi/chat/${provider}`),
|
35
|
+
chatModels: (provider: string) => withBasePath(`/webapi/chat/models/${provider}`),
|
36
|
+
|
38
37
|
// image
|
39
38
|
images: (provider: string) => `/webapi/text-to-image/${provider}`,
|
40
39
|
|
@@ -33,6 +33,7 @@ export interface UserKeyVaults {
|
|
33
33
|
github?: OpenAICompatibleKeyVault;
|
34
34
|
google?: OpenAICompatibleKeyVault;
|
35
35
|
groq?: OpenAICompatibleKeyVault;
|
36
|
+
huggingface?: OpenAICompatibleKeyVault;
|
36
37
|
hunyuan?: OpenAICompatibleKeyVault;
|
37
38
|
lobehub?: any;
|
38
39
|
minimax?: OpenAICompatibleKeyVault;
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|