@lobehub/chat 1.128.8 → 1.128.9
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/.github/workflows/sync-database-schema.yml +7 -2
- package/CHANGELOG.md +26 -0
- package/changelog/v1.json +9 -0
- package/package.json +2 -1
- package/packages/file-loaders/src/loadFile.ts +1 -0
- package/packages/file-loaders/src/loaders/docx/index.ts +6 -1
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +72 -0
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +34 -2
- package/packages/model-runtime/src/core/streams/anthropic.ts +7 -2
- package/packages/model-runtime/src/core/streams/google-ai.ts +7 -2
- package/packages/model-runtime/src/core/streams/openai/openai.ts +15 -2
- package/packages/model-runtime/src/core/streams/openai/responsesStream.ts +14 -2
- package/packages/model-runtime/src/core/streams/protocol.ts +8 -2
- package/packages/model-runtime/src/core/streams/qwen.ts +10 -2
- package/packages/model-runtime/src/core/streams/spark.test.ts +2 -2
- package/packages/model-runtime/src/core/streams/spark.ts +10 -1
- package/packages/model-runtime/src/core/streams/vertex-ai.ts +8 -2
- package/packages/model-runtime/src/providers/azureOpenai/index.ts +6 -3
- package/packages/model-runtime/src/providers/azureai/index.ts +6 -3
- package/src/services/session/server.test.ts +4 -1
- package/src/services/session/server.ts +7 -1
|
@@ -13,8 +13,13 @@ jobs:
|
|
|
13
13
|
steps:
|
|
14
14
|
- uses: actions/checkout@v5
|
|
15
15
|
|
|
16
|
-
- name: Install
|
|
17
|
-
|
|
16
|
+
- name: Install bun
|
|
17
|
+
uses: oven-sh/setup-bun@v2
|
|
18
|
+
with:
|
|
19
|
+
bun-version: ${{ secrets.BUN_VERSION }}
|
|
20
|
+
|
|
21
|
+
- name: Install deps
|
|
22
|
+
run: bun i
|
|
18
23
|
|
|
19
24
|
- name: Check dbdocs
|
|
20
25
|
run: dbdocs
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 1.128.9](https://github.com/lobehub/lobe-chat/compare/v1.128.8...v1.128.9)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-09-15**</sup>
|
|
8
|
+
|
|
9
|
+
#### 💄 Styles
|
|
10
|
+
|
|
11
|
+
- **misc**: Improve error handle with agent config, support `.doc` file parse.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Styles
|
|
19
|
+
|
|
20
|
+
- **misc**: Improve error handle with agent config, closes [#9263](https://github.com/lobehub/lobe-chat/issues/9263) ([6656217](https://github.com/lobehub/lobe-chat/commit/6656217))
|
|
21
|
+
- **misc**: Support `.doc` file parse, closes [#8182](https://github.com/lobehub/lobe-chat/issues/8182) ([ed42753](https://github.com/lobehub/lobe-chat/commit/ed42753))
|
|
22
|
+
|
|
23
|
+
</details>
|
|
24
|
+
|
|
25
|
+
<div align="right">
|
|
26
|
+
|
|
27
|
+
[](#readme-top)
|
|
28
|
+
|
|
29
|
+
</div>
|
|
30
|
+
|
|
5
31
|
### [Version 1.128.8](https://github.com/lobehub/lobe-chat/compare/v1.128.7...v1.128.8)
|
|
6
32
|
|
|
7
33
|
<sup>Released on **2025-09-15**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "1.128.
|
|
3
|
+
"version": "1.128.9",
|
|
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",
|
|
@@ -279,6 +279,7 @@
|
|
|
279
279
|
"url-join": "^5.0.0",
|
|
280
280
|
"use-merge-value": "^1.2.0",
|
|
281
281
|
"uuid": "^11.1.0",
|
|
282
|
+
"word-extractor": "^1.0.4",
|
|
282
283
|
"ws": "^8.18.3",
|
|
283
284
|
"yaml": "^2.8.1",
|
|
284
285
|
"zod": "^3.25.76",
|
|
@@ -12,7 +12,12 @@ export class DocxLoader implements FileLoaderInterface {
|
|
|
12
12
|
async loadPages(filePath: string): Promise<DocumentPage[]> {
|
|
13
13
|
log('Loading DOCX file:', filePath);
|
|
14
14
|
try {
|
|
15
|
-
|
|
15
|
+
let loader: LangchainDocxLoader;
|
|
16
|
+
if (filePath.endsWith('.doc')) {
|
|
17
|
+
loader = new LangchainDocxLoader(filePath, { type: 'doc' });
|
|
18
|
+
} else {
|
|
19
|
+
loader = new LangchainDocxLoader(filePath, { type: 'docx' });
|
|
20
|
+
}
|
|
16
21
|
log('LangChain DocxLoader created');
|
|
17
22
|
const docs = await loader.load(); // Langchain DocxLoader typically loads the whole doc as one
|
|
18
23
|
log('DOCX document loaded, parts:', docs.length);
|
|
@@ -417,6 +417,78 @@ describe('LobeOpenAICompatibleFactory', () => {
|
|
|
417
417
|
'event: text\n',
|
|
418
418
|
'data: "Hello"\n\n',
|
|
419
419
|
'id: a\n',
|
|
420
|
+
'event: usage\n',
|
|
421
|
+
'data: {"inputTextTokens":5,"outputTextTokens":5,"totalInputTokens":5,"totalOutputTokens":5,"totalTokens":10}\n\n',
|
|
422
|
+
'id: output_speed\n',
|
|
423
|
+
'event: speed\n',
|
|
424
|
+
expect.stringMatching(/^data: \{.*"tps":.*,"ttft":.*}\n\n$/), // tps ttft 测试结果不一样
|
|
425
|
+
'id: a\n',
|
|
426
|
+
'event: stop\n',
|
|
427
|
+
'data: "stop"\n\n',
|
|
428
|
+
]);
|
|
429
|
+
|
|
430
|
+
expect((await reader.read()).done).toBe(true);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should transform non-streaming response to stream correctly with reasoning content', async () => {
|
|
434
|
+
const mockResponse = {
|
|
435
|
+
id: 'a',
|
|
436
|
+
object: 'chat.completion',
|
|
437
|
+
created: 123,
|
|
438
|
+
model: 'deepseek/deepseek-reasoner',
|
|
439
|
+
choices: [
|
|
440
|
+
{
|
|
441
|
+
index: 0,
|
|
442
|
+
message: {
|
|
443
|
+
role: 'assistant',
|
|
444
|
+
content: 'Hello',
|
|
445
|
+
reasoning_content: 'Thinking content',
|
|
446
|
+
},
|
|
447
|
+
finish_reason: 'stop',
|
|
448
|
+
logprobs: null,
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
usage: {
|
|
452
|
+
prompt_tokens: 5,
|
|
453
|
+
completion_tokens: 5,
|
|
454
|
+
total_tokens: 10,
|
|
455
|
+
},
|
|
456
|
+
} as unknown as OpenAI.ChatCompletion;
|
|
457
|
+
vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
|
|
458
|
+
mockResponse as any,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const result = await instance.chat({
|
|
462
|
+
messages: [{ content: 'Hello', role: 'user' }],
|
|
463
|
+
model: 'deepseek/deepseek-reasoner',
|
|
464
|
+
temperature: 0,
|
|
465
|
+
stream: false,
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const decoder = new TextDecoder();
|
|
469
|
+
const reader = result.body!.getReader();
|
|
470
|
+
const stream: string[] = [];
|
|
471
|
+
|
|
472
|
+
while (true) {
|
|
473
|
+
const { value, done } = await reader.read();
|
|
474
|
+
if (done) break;
|
|
475
|
+
stream.push(decoder.decode(value));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
expect(stream).toEqual([
|
|
479
|
+
'id: a\n',
|
|
480
|
+
'event: reasoning\n',
|
|
481
|
+
'data: "Thinking content"\n\n',
|
|
482
|
+
'id: a\n',
|
|
483
|
+
'event: text\n',
|
|
484
|
+
'data: "Hello"\n\n',
|
|
485
|
+
'id: a\n',
|
|
486
|
+
'event: usage\n',
|
|
487
|
+
'data: {"inputTextTokens":5,"outputTextTokens":5,"totalInputTokens":5,"totalOutputTokens":5,"totalTokens":10}\n\n',
|
|
488
|
+
'id: output_speed\n',
|
|
489
|
+
'event: speed\n',
|
|
490
|
+
expect.stringMatching(/^data: \{.*"tps":.*,"ttft":.*}\n\n$/), // tps ttft 测试结果不一样
|
|
491
|
+
'id: a\n',
|
|
420
492
|
'event: stop\n',
|
|
421
493
|
'data: "stop"\n\n',
|
|
422
494
|
]);
|
|
@@ -124,6 +124,29 @@ export function transformResponseToStream(data: OpenAI.ChatCompletion) {
|
|
|
124
124
|
return new ReadableStream({
|
|
125
125
|
start(controller) {
|
|
126
126
|
const choices = data.choices || [];
|
|
127
|
+
const first = choices[0];
|
|
128
|
+
// 兼容:非流式里 DeepSeek 等会把“深度思考”放在 message.reasoning_content
|
|
129
|
+
const message: any = first?.message ?? {};
|
|
130
|
+
const reasoningText =
|
|
131
|
+
typeof message.reasoning_content === 'string' && message.reasoning_content.length > 0
|
|
132
|
+
? message.reasoning_content
|
|
133
|
+
: null;
|
|
134
|
+
if (reasoningText) {
|
|
135
|
+
controller.enqueue({
|
|
136
|
+
choices: [
|
|
137
|
+
{
|
|
138
|
+
delta: { content: null, reasoning_content: reasoningText, role: 'assistant' },
|
|
139
|
+
finish_reason: null,
|
|
140
|
+
index: first?.index ?? 0,
|
|
141
|
+
logprobs: first?.logprobs ?? null,
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
created: data.created,
|
|
145
|
+
id: data.id,
|
|
146
|
+
model: data.model,
|
|
147
|
+
object: 'chat.completion.chunk',
|
|
148
|
+
} as unknown as OpenAI.ChatCompletionChunk);
|
|
149
|
+
}
|
|
127
150
|
const chunk: OpenAI.ChatCompletionChunk = {
|
|
128
151
|
choices: choices.map((choice: OpenAI.ChatCompletion.Choice) => ({
|
|
129
152
|
delta: {
|
|
@@ -149,7 +172,16 @@ export function transformResponseToStream(data: OpenAI.ChatCompletion) {
|
|
|
149
172
|
};
|
|
150
173
|
|
|
151
174
|
controller.enqueue(chunk);
|
|
152
|
-
|
|
175
|
+
if (data.usage) {
|
|
176
|
+
controller.enqueue({
|
|
177
|
+
choices: [],
|
|
178
|
+
created: data.created,
|
|
179
|
+
id: data.id,
|
|
180
|
+
model: data.model,
|
|
181
|
+
object: 'chat.completion.chunk',
|
|
182
|
+
usage: data.usage,
|
|
183
|
+
} as unknown as OpenAI.ChatCompletionChunk);
|
|
184
|
+
}
|
|
153
185
|
controller.enqueue({
|
|
154
186
|
choices: choices.map((choice: OpenAI.ChatCompletion.Choice) => ({
|
|
155
187
|
delta: {
|
|
@@ -311,7 +343,7 @@ export const createOpenAICompatibleRuntime = <T extends Record<string, any> = an
|
|
|
311
343
|
callbacks: streamOptions.callbacks,
|
|
312
344
|
inputStartAt,
|
|
313
345
|
})
|
|
314
|
-
: OpenAIStream(stream, { ...streamOptions, inputStartAt }),
|
|
346
|
+
: OpenAIStream(stream, { ...streamOptions, enableStreaming: false, inputStartAt }),
|
|
315
347
|
{
|
|
316
348
|
headers: options?.headers,
|
|
317
349
|
},
|
|
@@ -240,12 +240,13 @@ export const transformAnthropicStream = (
|
|
|
240
240
|
|
|
241
241
|
export interface AnthropicStreamOptions {
|
|
242
242
|
callbacks?: ChatStreamCallbacks;
|
|
243
|
+
enableStreaming?: boolean; // 选择 TPS 计算方式(非流式时传 false)
|
|
243
244
|
inputStartAt?: number;
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
export const AnthropicStream = (
|
|
247
248
|
stream: Stream<Anthropic.MessageStreamEvent> | ReadableStream,
|
|
248
|
-
{ callbacks, inputStartAt }: AnthropicStreamOptions = {},
|
|
249
|
+
{ callbacks, inputStartAt, enableStreaming = true }: AnthropicStreamOptions = {},
|
|
249
250
|
) => {
|
|
250
251
|
const streamStack: StreamContext = { id: '' };
|
|
251
252
|
|
|
@@ -254,7 +255,11 @@ export const AnthropicStream = (
|
|
|
254
255
|
|
|
255
256
|
return readableStream
|
|
256
257
|
.pipeThrough(
|
|
257
|
-
createTokenSpeedCalculator(transformAnthropicStream, {
|
|
258
|
+
createTokenSpeedCalculator(transformAnthropicStream, {
|
|
259
|
+
enableStreaming: enableStreaming,
|
|
260
|
+
inputStartAt,
|
|
261
|
+
streamStack,
|
|
262
|
+
}),
|
|
258
263
|
)
|
|
259
264
|
.pipeThrough(createSSEProtocolTransformer((c) => c, streamStack))
|
|
260
265
|
.pipeThrough(createCallbacksTransformer(callbacks));
|
|
@@ -213,18 +213,23 @@ const transformGoogleGenerativeAIStream = (
|
|
|
213
213
|
|
|
214
214
|
export interface GoogleAIStreamOptions {
|
|
215
215
|
callbacks?: ChatStreamCallbacks;
|
|
216
|
+
enableStreaming?: boolean; // 选择 TPS 计算方式(非流式时传 false)
|
|
216
217
|
inputStartAt?: number;
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
export const GoogleGenerativeAIStream = (
|
|
220
221
|
rawStream: ReadableStream<GenerateContentResponse>,
|
|
221
|
-
{ callbacks, inputStartAt }: GoogleAIStreamOptions = {},
|
|
222
|
+
{ callbacks, inputStartAt, enableStreaming = true }: GoogleAIStreamOptions = {},
|
|
222
223
|
) => {
|
|
223
224
|
const streamStack: StreamContext = { id: 'chat_' + nanoid() };
|
|
224
225
|
|
|
225
226
|
return rawStream
|
|
226
227
|
.pipeThrough(
|
|
227
|
-
createTokenSpeedCalculator(transformGoogleGenerativeAIStream, {
|
|
228
|
+
createTokenSpeedCalculator(transformGoogleGenerativeAIStream, {
|
|
229
|
+
enableStreaming: enableStreaming,
|
|
230
|
+
inputStartAt,
|
|
231
|
+
streamStack,
|
|
232
|
+
}),
|
|
228
233
|
)
|
|
229
234
|
.pipeThrough(
|
|
230
235
|
createSSEProtocolTransformer((c) => c, streamStack, { requireTerminalEvent: true }),
|
|
@@ -417,13 +417,20 @@ export interface OpenAIStreamOptions {
|
|
|
417
417
|
name: string;
|
|
418
418
|
}) => ILobeAgentRuntimeErrorType | undefined;
|
|
419
419
|
callbacks?: ChatStreamCallbacks;
|
|
420
|
+
enableStreaming?: boolean; // 选择 TPS 计算方式(非流式时传 false)
|
|
420
421
|
inputStartAt?: number;
|
|
421
422
|
provider?: string;
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
export const OpenAIStream = (
|
|
425
426
|
stream: Stream<OpenAI.ChatCompletionChunk> | ReadableStream,
|
|
426
|
-
{
|
|
427
|
+
{
|
|
428
|
+
callbacks,
|
|
429
|
+
provider,
|
|
430
|
+
bizErrorTypeTransformer,
|
|
431
|
+
inputStartAt,
|
|
432
|
+
enableStreaming = true,
|
|
433
|
+
}: OpenAIStreamOptions = {},
|
|
427
434
|
) => {
|
|
428
435
|
const streamStack: StreamContext = { id: '' };
|
|
429
436
|
|
|
@@ -439,7 +446,13 @@ export const OpenAIStream = (
|
|
|
439
446
|
// provider like huggingface or minimax will return error in the stream,
|
|
440
447
|
// so in the first Transformer, we need to handle the error
|
|
441
448
|
.pipeThrough(createFirstErrorHandleTransformer(bizErrorTypeTransformer, provider))
|
|
442
|
-
.pipeThrough(
|
|
449
|
+
.pipeThrough(
|
|
450
|
+
createTokenSpeedCalculator(transformWithProvider, {
|
|
451
|
+
enableStreaming: enableStreaming,
|
|
452
|
+
inputStartAt,
|
|
453
|
+
streamStack,
|
|
454
|
+
}),
|
|
455
|
+
)
|
|
443
456
|
.pipeThrough(createSSEProtocolTransformer((c) => c, streamStack))
|
|
444
457
|
.pipeThrough(createCallbacksTransformer(callbacks))
|
|
445
458
|
);
|
|
@@ -185,7 +185,13 @@ const transformOpenAIStream = (
|
|
|
185
185
|
|
|
186
186
|
export const OpenAIResponsesStream = (
|
|
187
187
|
stream: Stream<OpenAI.Responses.ResponseStreamEvent> | ReadableStream,
|
|
188
|
-
{
|
|
188
|
+
{
|
|
189
|
+
callbacks,
|
|
190
|
+
provider,
|
|
191
|
+
bizErrorTypeTransformer,
|
|
192
|
+
inputStartAt,
|
|
193
|
+
enableStreaming = true,
|
|
194
|
+
}: OpenAIStreamOptions = {},
|
|
189
195
|
) => {
|
|
190
196
|
const streamStack: StreamContext = { id: '' };
|
|
191
197
|
|
|
@@ -198,7 +204,13 @@ export const OpenAIResponsesStream = (
|
|
|
198
204
|
// provider like huggingface or minimax will return error in the stream,
|
|
199
205
|
// so in the first Transformer, we need to handle the error
|
|
200
206
|
.pipeThrough(createFirstErrorHandleTransformer(bizErrorTypeTransformer, provider))
|
|
201
|
-
.pipeThrough(
|
|
207
|
+
.pipeThrough(
|
|
208
|
+
createTokenSpeedCalculator(transformOpenAIStream, {
|
|
209
|
+
enableStreaming: enableStreaming,
|
|
210
|
+
inputStartAt,
|
|
211
|
+
streamStack,
|
|
212
|
+
}),
|
|
213
|
+
)
|
|
202
214
|
.pipeThrough(createSSEProtocolTransformer((c) => c, streamStack))
|
|
203
215
|
.pipeThrough(createCallbacksTransformer(callbacks))
|
|
204
216
|
);
|
|
@@ -360,7 +360,11 @@ export const TOKEN_SPEED_CHUNK_ID = 'output_speed';
|
|
|
360
360
|
*/
|
|
361
361
|
export const createTokenSpeedCalculator = (
|
|
362
362
|
transformer: (chunk: any, stack: StreamContext) => StreamProtocolChunk | StreamProtocolChunk[],
|
|
363
|
-
{
|
|
363
|
+
{
|
|
364
|
+
inputStartAt,
|
|
365
|
+
streamStack,
|
|
366
|
+
enableStreaming = true, // 选择 TPS 计算方式(非流式时传 false)
|
|
367
|
+
}: { enableStreaming?: boolean; inputStartAt?: number; streamStack?: StreamContext } = {},
|
|
364
368
|
) => {
|
|
365
369
|
let outputStartAt: number | undefined;
|
|
366
370
|
let outputThinking: boolean | undefined;
|
|
@@ -397,7 +401,9 @@ export const createTokenSpeedCalculator = (
|
|
|
397
401
|
: Math.max(0, totalOutputTokens - reasoningTokens);
|
|
398
402
|
result.push({
|
|
399
403
|
data: {
|
|
400
|
-
|
|
404
|
+
// 非流式计算 tps 从发出请求开始算
|
|
405
|
+
tps:
|
|
406
|
+
(outputTokens / (Date.now() - (enableStreaming ? outputStartAt : inputStartAt))) * 1000,
|
|
401
407
|
ttft: outputStartAt - inputStartAt,
|
|
402
408
|
} as ModelSpeed,
|
|
403
409
|
id: TOKEN_SPEED_CHUNK_ID,
|
|
@@ -116,7 +116,11 @@ export const QwenAIStream = (
|
|
|
116
116
|
stream: Stream<OpenAI.ChatCompletionChunk> | ReadableStream,
|
|
117
117
|
// TODO: preserve for RFC 097
|
|
118
118
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars
|
|
119
|
-
{
|
|
119
|
+
{
|
|
120
|
+
callbacks,
|
|
121
|
+
inputStartAt,
|
|
122
|
+
enableStreaming = true,
|
|
123
|
+
}: { callbacks?: ChatStreamCallbacks; enableStreaming?: boolean; inputStartAt?: number } = {},
|
|
120
124
|
) => {
|
|
121
125
|
const streamContext: StreamContext = { id: '' };
|
|
122
126
|
const readableStream =
|
|
@@ -124,7 +128,11 @@ export const QwenAIStream = (
|
|
|
124
128
|
|
|
125
129
|
return readableStream
|
|
126
130
|
.pipeThrough(
|
|
127
|
-
createTokenSpeedCalculator(transformQwenStream, {
|
|
131
|
+
createTokenSpeedCalculator(transformQwenStream, {
|
|
132
|
+
enableStreaming: enableStreaming,
|
|
133
|
+
inputStartAt,
|
|
134
|
+
streamStack: streamContext,
|
|
135
|
+
}),
|
|
128
136
|
)
|
|
129
137
|
.pipeThrough(createSSEProtocolTransformer((c) => c, streamContext))
|
|
130
138
|
.pipeThrough(createCallbacksTransformer(callbacks));
|
|
@@ -114,7 +114,7 @@ describe('SparkAIStream', () => {
|
|
|
114
114
|
chunks.push(chunk);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
expect(chunks).toHaveLength(
|
|
117
|
+
expect(chunks).toHaveLength(3);
|
|
118
118
|
expect(chunks[0].choices[0].delta.tool_calls).toEqual([
|
|
119
119
|
{
|
|
120
120
|
function: {
|
|
@@ -126,7 +126,7 @@ describe('SparkAIStream', () => {
|
|
|
126
126
|
type: 'function',
|
|
127
127
|
},
|
|
128
128
|
]);
|
|
129
|
-
expect(chunks[
|
|
129
|
+
expect(chunks[2].choices[0].finish_reason).toBeDefined();
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
it('should transform streaming response with tool calls', async () => {
|
|
@@ -50,7 +50,16 @@ export function transformSparkResponseToStream(data: OpenAI.ChatCompletion) {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
controller.enqueue(chunk);
|
|
53
|
-
|
|
53
|
+
if (data.usage) {
|
|
54
|
+
controller.enqueue({
|
|
55
|
+
choices: [],
|
|
56
|
+
created: data.created,
|
|
57
|
+
id: data.id,
|
|
58
|
+
model: data.model,
|
|
59
|
+
object: 'chat.completion.chunk',
|
|
60
|
+
usage: data.usage,
|
|
61
|
+
} as unknown as OpenAI.ChatCompletionChunk);
|
|
62
|
+
}
|
|
54
63
|
controller.enqueue({
|
|
55
64
|
choices: choices.map((choice: OpenAI.ChatCompletion.Choice) => ({
|
|
56
65
|
delta: {
|
|
@@ -143,12 +143,18 @@ const transformVertexAIStream = (
|
|
|
143
143
|
|
|
144
144
|
export const VertexAIStream = (
|
|
145
145
|
rawStream: ReadableStream<GenerateContentResponse>,
|
|
146
|
-
{ callbacks, inputStartAt }: GoogleAIStreamOptions = {},
|
|
146
|
+
{ callbacks, inputStartAt, enableStreaming = true }: GoogleAIStreamOptions = {},
|
|
147
147
|
) => {
|
|
148
148
|
const streamStack: StreamContext = { id: 'chat_' + nanoid() };
|
|
149
149
|
|
|
150
150
|
return rawStream
|
|
151
|
-
.pipeThrough(
|
|
151
|
+
.pipeThrough(
|
|
152
|
+
createTokenSpeedCalculator(transformVertexAIStream, {
|
|
153
|
+
enableStreaming: enableStreaming,
|
|
154
|
+
inputStartAt,
|
|
155
|
+
streamStack,
|
|
156
|
+
}),
|
|
157
|
+
)
|
|
152
158
|
.pipeThrough(createSSEProtocolTransformer((c) => c, streamStack))
|
|
153
159
|
.pipeThrough(createCallbacksTransformer(callbacks));
|
|
154
160
|
};
|
|
@@ -96,9 +96,12 @@ export class LobeAzureOpenAI implements LobeRuntimeAI {
|
|
|
96
96
|
});
|
|
97
97
|
} else {
|
|
98
98
|
const stream = transformResponseToStream(response as OpenAI.ChatCompletion);
|
|
99
|
-
return StreamingResponse(
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
return StreamingResponse(
|
|
100
|
+
OpenAIStream(stream, { callbacks: options?.callback, enableStreaming: false }),
|
|
101
|
+
{
|
|
102
|
+
headers: options?.headers,
|
|
103
|
+
},
|
|
104
|
+
);
|
|
102
105
|
}
|
|
103
106
|
} catch (e) {
|
|
104
107
|
return this.handleError(e, model);
|
|
@@ -83,9 +83,12 @@ export class LobeAzureAI implements LobeRuntimeAI {
|
|
|
83
83
|
|
|
84
84
|
// the azure AI inference response is openai compatible
|
|
85
85
|
const stream = transformResponseToStream(res.body as OpenAI.ChatCompletion);
|
|
86
|
-
return StreamingResponse(
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
return StreamingResponse(
|
|
87
|
+
OpenAIStream(stream, { callbacks: options?.callback, enableStreaming: false }),
|
|
88
|
+
{
|
|
89
|
+
headers: options?.headers,
|
|
90
|
+
},
|
|
91
|
+
);
|
|
89
92
|
}
|
|
90
93
|
} catch (e) {
|
|
91
94
|
let error = e as { [key: string]: any; code: string; message: string };
|
|
@@ -195,7 +195,10 @@ describe('ServerService', () => {
|
|
|
195
195
|
await service.updateSessionConfig('123', config, signal);
|
|
196
196
|
expect(lambdaClient.session.updateSessionConfig.mutate).toBeCalledWith(
|
|
197
197
|
{ id: '123', value: config },
|
|
198
|
-
{
|
|
198
|
+
{
|
|
199
|
+
signal,
|
|
200
|
+
context: { showNotification: false },
|
|
201
|
+
},
|
|
199
202
|
);
|
|
200
203
|
});
|
|
201
204
|
|
|
@@ -56,7 +56,13 @@ export class ServerService implements ISessionService {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
updateSessionConfig: ISessionService['updateSessionConfig'] = (id, config, signal) => {
|
|
59
|
-
return lambdaClient.session.updateSessionConfig.mutate(
|
|
59
|
+
return lambdaClient.session.updateSessionConfig.mutate(
|
|
60
|
+
{ id, value: config },
|
|
61
|
+
{
|
|
62
|
+
context: { showNotification: false },
|
|
63
|
+
signal,
|
|
64
|
+
},
|
|
65
|
+
);
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
updateSessionMeta: ISessionService['updateSessionMeta'] = (id, meta, signal) => {
|