@lobehub/chat 1.15.27 → 1.15.29
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.
Potentially problematic release.
This version of @lobehub/chat might be problematic. Click here for more details.
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.15.29](https://github.com/lobehub/lobe-chat/compare/v1.15.28...v1.15.29)
|
6
|
+
|
7
|
+
<sup>Released on **2024-09-09**</sup>
|
8
|
+
|
9
|
+
#### 🐛 Bug Fixes
|
10
|
+
|
11
|
+
- **misc**: Gemini cannot input images when server database is enabled.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's fixed
|
19
|
+
|
20
|
+
- **misc**: Gemini cannot input images when server database is enabled, closes [#3370](https://github.com/lobehub/lobe-chat/issues/3370) ([eb552d2](https://github.com/lobehub/lobe-chat/commit/eb552d2))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.15.28](https://github.com/lobehub/lobe-chat/compare/v1.15.27...v1.15.28)
|
31
|
+
|
32
|
+
<sup>Released on **2024-09-09**</sup>
|
33
|
+
|
34
|
+
#### 🐛 Bug Fixes
|
35
|
+
|
36
|
+
- **misc**: Update baichuan param.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### What's fixed
|
44
|
+
|
45
|
+
- **misc**: Update baichuan param, closes [#3356](https://github.com/lobehub/lobe-chat/issues/3356) ([29bced1](https://github.com/lobehub/lobe-chat/commit/29bced1))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
### [Version 1.15.27](https://github.com/lobehub/lobe-chat/compare/v1.15.26...v1.15.27)
|
6
56
|
|
7
57
|
<sup>Released on **2024-09-09**</sup>
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.15.
|
3
|
+
"version": "1.15.29",
|
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",
|
@@ -7,22 +7,15 @@ export const LobeBaichuanAI = LobeOpenAICompatibleFactory({
|
|
7
7
|
baseURL: 'https://api.baichuan-ai.com/v1',
|
8
8
|
chatCompletion: {
|
9
9
|
handlePayload: (payload: ChatStreamPayload) => {
|
10
|
-
const {
|
10
|
+
const { temperature, ...rest } = payload;
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
// If greater than 2, cap it at 2
|
20
|
-
adjustedFrequencyPenalty = 2;
|
21
|
-
}
|
22
|
-
// If between 1 and 2, keep the original value
|
23
|
-
}
|
24
|
-
|
25
|
-
return { ...rest, frequency_penalty: adjustedFrequencyPenalty } as OpenAI.ChatCompletionCreateParamsStreaming;
|
12
|
+
return {
|
13
|
+
...rest,
|
14
|
+
temperature:
|
15
|
+
temperature !== undefined
|
16
|
+
? temperature / 2
|
17
|
+
: undefined,
|
18
|
+
} as OpenAI.ChatCompletionCreateParamsStreaming;
|
26
19
|
},
|
27
20
|
},
|
28
21
|
debug: {
|
@@ -5,6 +5,7 @@ import OpenAI from 'openai';
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
6
6
|
|
7
7
|
import { OpenAIChatMessage } from '@/libs/agent-runtime';
|
8
|
+
import * as imageToBase64Module from '@/utils/imageToBase64';
|
8
9
|
|
9
10
|
import * as debugStreamModule from '../utils/debugStream';
|
10
11
|
import { LobeGoogleAI } from './index';
|
@@ -303,36 +304,57 @@ describe('LobeGoogleAI', () => {
|
|
303
304
|
|
304
305
|
describe('private method', () => {
|
305
306
|
describe('convertContentToGooglePart', () => {
|
306
|
-
it('should
|
307
|
-
|
308
|
-
const
|
307
|
+
it('should handle URL type images', async () => {
|
308
|
+
const imageUrl = 'http://example.com/image.png';
|
309
|
+
const mockBase64 = 'mockBase64Data';
|
309
310
|
|
310
|
-
|
311
|
+
// Mock the imageUrlToBase64 function
|
312
|
+
vi.spyOn(imageToBase64Module, 'imageUrlToBase64').mockResolvedValueOnce(mockBase64);
|
313
|
+
|
314
|
+
const result = await instance['convertContentToGooglePart']({
|
315
|
+
type: 'image_url',
|
316
|
+
image_url: { url: imageUrl },
|
317
|
+
});
|
318
|
+
|
319
|
+
expect(result).toEqual({
|
320
|
+
inlineData: {
|
321
|
+
data: mockBase64,
|
322
|
+
mimeType: 'image/png',
|
323
|
+
},
|
324
|
+
});
|
325
|
+
|
326
|
+
expect(imageToBase64Module.imageUrlToBase64).toHaveBeenCalledWith(imageUrl);
|
327
|
+
});
|
328
|
+
|
329
|
+
it('should throw TypeError for unsupported image URL types', async () => {
|
330
|
+
const unsupportedImageUrl = 'unsupported://example.com/image.png';
|
331
|
+
|
332
|
+
await expect(
|
311
333
|
instance['convertContentToGooglePart']({
|
312
334
|
type: 'image_url',
|
313
|
-
image_url: { url:
|
335
|
+
image_url: { url: unsupportedImageUrl },
|
314
336
|
}),
|
315
|
-
).toThrow(TypeError);
|
337
|
+
).rejects.toThrow(TypeError);
|
316
338
|
});
|
317
339
|
});
|
318
340
|
|
319
341
|
describe('buildGoogleMessages', () => {
|
320
|
-
it('get default result with gemini-pro', () => {
|
342
|
+
it('get default result with gemini-pro', async () => {
|
321
343
|
const messages: OpenAIChatMessage[] = [{ content: 'Hello', role: 'user' }];
|
322
344
|
|
323
|
-
const contents = instance['buildGoogleMessages'](messages, 'gemini-pro');
|
345
|
+
const contents = await instance['buildGoogleMessages'](messages, 'gemini-pro');
|
324
346
|
|
325
347
|
expect(contents).toHaveLength(1);
|
326
348
|
expect(contents).toEqual([{ parts: [{ text: 'Hello' }], role: 'user' }]);
|
327
349
|
});
|
328
350
|
|
329
|
-
it('messages should end with user if using gemini-pro', () => {
|
351
|
+
it('messages should end with user if using gemini-pro', async () => {
|
330
352
|
const messages: OpenAIChatMessage[] = [
|
331
353
|
{ content: 'Hello', role: 'user' },
|
332
354
|
{ content: 'Hi', role: 'assistant' },
|
333
355
|
];
|
334
356
|
|
335
|
-
const contents = instance['buildGoogleMessages'](messages, 'gemini-pro');
|
357
|
+
const contents = await instance['buildGoogleMessages'](messages, 'gemini-pro');
|
336
358
|
|
337
359
|
expect(contents).toHaveLength(3);
|
338
360
|
expect(contents).toEqual([
|
@@ -342,13 +364,13 @@ describe('LobeGoogleAI', () => {
|
|
342
364
|
]);
|
343
365
|
});
|
344
366
|
|
345
|
-
it('should include system role if there is a system role prompt', () => {
|
367
|
+
it('should include system role if there is a system role prompt', async () => {
|
346
368
|
const messages: OpenAIChatMessage[] = [
|
347
369
|
{ content: 'you are ChatGPT', role: 'system' },
|
348
370
|
{ content: 'Who are you', role: 'user' },
|
349
371
|
];
|
350
372
|
|
351
|
-
const contents = instance['buildGoogleMessages'](messages, 'gemini-pro');
|
373
|
+
const contents = await instance['buildGoogleMessages'](messages, 'gemini-pro');
|
352
374
|
|
353
375
|
expect(contents).toHaveLength(3);
|
354
376
|
expect(contents).toEqual([
|
@@ -358,13 +380,13 @@ describe('LobeGoogleAI', () => {
|
|
358
380
|
]);
|
359
381
|
});
|
360
382
|
|
361
|
-
it('should not modify the length if model is gemini-1.5-pro', () => {
|
383
|
+
it('should not modify the length if model is gemini-1.5-pro', async () => {
|
362
384
|
const messages: OpenAIChatMessage[] = [
|
363
385
|
{ content: 'Hello', role: 'user' },
|
364
386
|
{ content: 'Hi', role: 'assistant' },
|
365
387
|
];
|
366
388
|
|
367
|
-
const contents = instance['buildGoogleMessages'](messages, 'gemini-1.5-pro-latest');
|
389
|
+
const contents = await instance['buildGoogleMessages'](messages, 'gemini-1.5-pro-latest');
|
368
390
|
|
369
391
|
expect(contents).toHaveLength(2);
|
370
392
|
expect(contents).toEqual([
|
@@ -373,7 +395,7 @@ describe('LobeGoogleAI', () => {
|
|
373
395
|
]);
|
374
396
|
});
|
375
397
|
|
376
|
-
it('should use specified model when images are included in messages', () => {
|
398
|
+
it('should use specified model when images are included in messages', async () => {
|
377
399
|
const messages: OpenAIChatMessage[] = [
|
378
400
|
{
|
379
401
|
content: [
|
@@ -386,7 +408,7 @@ describe('LobeGoogleAI', () => {
|
|
386
408
|
const model = 'gemini-1.5-flash-latest';
|
387
409
|
|
388
410
|
// 调用 buildGoogleMessages 方法
|
389
|
-
const contents = instance['buildGoogleMessages'](messages, model);
|
411
|
+
const contents = await instance['buildGoogleMessages'](messages, model);
|
390
412
|
|
391
413
|
expect(contents).toHaveLength(1);
|
392
414
|
expect(contents).toEqual([
|
@@ -501,13 +523,13 @@ describe('LobeGoogleAI', () => {
|
|
501
523
|
});
|
502
524
|
|
503
525
|
describe('convertOAIMessagesToGoogleMessage', () => {
|
504
|
-
it('should correctly convert assistant message', () => {
|
526
|
+
it('should correctly convert assistant message', async () => {
|
505
527
|
const message: OpenAIChatMessage = {
|
506
528
|
role: 'assistant',
|
507
529
|
content: 'Hello',
|
508
530
|
};
|
509
531
|
|
510
|
-
const converted = instance['convertOAIMessagesToGoogleMessage'](message);
|
532
|
+
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
511
533
|
|
512
534
|
expect(converted).toEqual({
|
513
535
|
role: 'model',
|
@@ -515,13 +537,13 @@ describe('LobeGoogleAI', () => {
|
|
515
537
|
});
|
516
538
|
});
|
517
539
|
|
518
|
-
it('should correctly convert user message', () => {
|
540
|
+
it('should correctly convert user message', async () => {
|
519
541
|
const message: OpenAIChatMessage = {
|
520
542
|
role: 'user',
|
521
543
|
content: 'Hi',
|
522
544
|
};
|
523
545
|
|
524
|
-
const converted = instance['convertOAIMessagesToGoogleMessage'](message);
|
546
|
+
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
525
547
|
|
526
548
|
expect(converted).toEqual({
|
527
549
|
role: 'user',
|
@@ -529,7 +551,7 @@ describe('LobeGoogleAI', () => {
|
|
529
551
|
});
|
530
552
|
});
|
531
553
|
|
532
|
-
it('should correctly convert message with inline base64 image parts', () => {
|
554
|
+
it('should correctly convert message with inline base64 image parts', async () => {
|
533
555
|
const message: OpenAIChatMessage = {
|
534
556
|
role: 'user',
|
535
557
|
content: [
|
@@ -538,7 +560,7 @@ describe('LobeGoogleAI', () => {
|
|
538
560
|
],
|
539
561
|
};
|
540
562
|
|
541
|
-
const converted = instance['convertOAIMessagesToGoogleMessage'](message);
|
563
|
+
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
542
564
|
|
543
565
|
expect(converted).toEqual({
|
544
566
|
role: 'user',
|
@@ -548,7 +570,7 @@ describe('LobeGoogleAI', () => {
|
|
548
570
|
],
|
549
571
|
});
|
550
572
|
});
|
551
|
-
it.skip('should correctly convert message with image url parts', () => {
|
573
|
+
it.skip('should correctly convert message with image url parts', async () => {
|
552
574
|
const message: OpenAIChatMessage = {
|
553
575
|
role: 'user',
|
554
576
|
content: [
|
@@ -557,7 +579,7 @@ describe('LobeGoogleAI', () => {
|
|
557
579
|
],
|
558
580
|
};
|
559
581
|
|
560
|
-
const converted = instance['convertOAIMessagesToGoogleMessage'](message);
|
582
|
+
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
561
583
|
|
562
584
|
expect(converted).toEqual({
|
563
585
|
role: 'user',
|
@@ -10,6 +10,8 @@ import {
|
|
10
10
|
import { JSONSchema7 } from 'json-schema';
|
11
11
|
import { transform } from 'lodash-es';
|
12
12
|
|
13
|
+
import { imageUrlToBase64 } from '@/utils/imageToBase64';
|
14
|
+
|
13
15
|
import { LobeRuntimeAI } from '../BaseAI';
|
14
16
|
import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../error';
|
15
17
|
import {
|
@@ -52,7 +54,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
|
|
52
54
|
try {
|
53
55
|
const model = payload.model;
|
54
56
|
|
55
|
-
const contents = this.buildGoogleMessages(payload.messages, model);
|
57
|
+
const contents = await this.buildGoogleMessages(payload.messages, model);
|
56
58
|
|
57
59
|
const geminiStreamResult = await this.client
|
58
60
|
.getGenerativeModel(
|
@@ -109,7 +111,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
|
|
109
111
|
}
|
110
112
|
}
|
111
113
|
|
112
|
-
private convertContentToGooglePart = (content: UserMessageContentPart): Part => {
|
114
|
+
private convertContentToGooglePart = async (content: UserMessageContentPart): Promise<Part> => {
|
113
115
|
switch (content.type) {
|
114
116
|
case 'text': {
|
115
117
|
return { text: content.text };
|
@@ -130,51 +132,60 @@ export class LobeGoogleAI implements LobeRuntimeAI {
|
|
130
132
|
};
|
131
133
|
}
|
132
134
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
135
|
+
if (type === 'url') {
|
136
|
+
const base64Image = await imageUrlToBase64(content.image_url.url);
|
137
|
+
|
138
|
+
return {
|
139
|
+
inlineData: {
|
140
|
+
data: base64Image,
|
141
|
+
mimeType: mimeType || 'image/png',
|
142
|
+
},
|
143
|
+
};
|
144
|
+
}
|
141
145
|
|
142
146
|
throw new TypeError(`currently we don't support image url: ${content.image_url.url}`);
|
143
147
|
}
|
144
148
|
}
|
145
149
|
};
|
146
150
|
|
147
|
-
private convertOAIMessagesToGoogleMessage = (
|
151
|
+
private convertOAIMessagesToGoogleMessage = async (
|
152
|
+
message: OpenAIChatMessage,
|
153
|
+
): Promise<Content> => {
|
148
154
|
const content = message.content as string | UserMessageContentPart[];
|
149
155
|
|
150
156
|
return {
|
151
157
|
parts:
|
152
158
|
typeof content === 'string'
|
153
159
|
? [{ text: content }]
|
154
|
-
: content.map((c) => this.convertContentToGooglePart(c)),
|
160
|
+
: await Promise.all(content.map(async (c) => await this.convertContentToGooglePart(c))),
|
155
161
|
role: message.role === 'assistant' ? 'model' : 'user',
|
156
162
|
};
|
157
163
|
};
|
158
164
|
|
159
165
|
// convert messages from the Vercel AI SDK Format to the format
|
160
166
|
// that is expected by the Google GenAI SDK
|
161
|
-
private buildGoogleMessages = (
|
167
|
+
private buildGoogleMessages = async (
|
168
|
+
messages: OpenAIChatMessage[],
|
169
|
+
model: string,
|
170
|
+
): Promise<Content[]> => {
|
162
171
|
// if the model is gemini-1.5-pro-latest, we don't need any special handling
|
163
172
|
if (model === 'gemini-1.5-pro-latest') {
|
164
|
-
|
173
|
+
const pools = messages
|
165
174
|
.filter((message) => message.role !== 'function')
|
166
|
-
.map((msg) => this.convertOAIMessagesToGoogleMessage(msg));
|
175
|
+
.map(async (msg) => await this.convertOAIMessagesToGoogleMessage(msg));
|
176
|
+
|
177
|
+
return Promise.all(pools);
|
167
178
|
}
|
168
179
|
|
169
180
|
const contents: Content[] = [];
|
170
181
|
let lastRole = 'model';
|
171
182
|
|
172
|
-
|
183
|
+
for (const message of messages) {
|
173
184
|
// current to filter function message
|
174
185
|
if (message.role === 'function') {
|
175
|
-
|
186
|
+
continue;
|
176
187
|
}
|
177
|
-
const googleMessage = this.convertOAIMessagesToGoogleMessage(message);
|
188
|
+
const googleMessage = await this.convertOAIMessagesToGoogleMessage(message);
|
178
189
|
|
179
190
|
// if the last message is a model message and the current message is a model message
|
180
191
|
// then we need to add a user message to separate them
|
@@ -187,7 +198,7 @@ export class LobeGoogleAI implements LobeRuntimeAI {
|
|
187
198
|
|
188
199
|
// update the last role
|
189
200
|
lastRole = googleMessage.role;
|
190
|
-
}
|
201
|
+
}
|
191
202
|
|
192
203
|
// if the last message is a user message, then we need to add a model message to separate them
|
193
204
|
if (lastRole === 'model') {
|
@@ -35,3 +35,19 @@ export const imageToBase64 = ({
|
|
35
35
|
|
36
36
|
return canvas.toDataURL(type);
|
37
37
|
};
|
38
|
+
|
39
|
+
export const imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
|
40
|
+
try {
|
41
|
+
const res = await fetch(imageUrl);
|
42
|
+
const arrayBuffer = await res.arrayBuffer();
|
43
|
+
|
44
|
+
return typeof btoa === 'function'
|
45
|
+
? btoa(
|
46
|
+
new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), ''),
|
47
|
+
)
|
48
|
+
: Buffer.from(arrayBuffer).toString('base64');
|
49
|
+
} catch (error) {
|
50
|
+
console.error('Error converting image to base64:', error);
|
51
|
+
throw error;
|
52
|
+
}
|
53
|
+
};
|