@lobehub/chat 1.138.3 → 1.138.5
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/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/packages/database/src/repositories/aiInfra/index.test.ts +656 -0
- package/packages/model-runtime/src/core/contextBuilders/google.test.ts +585 -0
- package/packages/model-runtime/src/core/contextBuilders/google.ts +201 -0
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +191 -179
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +305 -47
- package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +93 -84
- package/packages/model-runtime/src/providers/anthropic/generateObject.ts +3 -3
- package/packages/model-runtime/src/providers/google/generateObject.test.ts +588 -83
- package/packages/model-runtime/src/providers/google/generateObject.ts +104 -6
- package/packages/model-runtime/src/providers/google/index.test.ts +0 -395
- package/packages/model-runtime/src/providers/google/index.ts +28 -194
- package/packages/model-runtime/src/providers/openai/index.test.ts +18 -17
- package/packages/model-runtime/src/types/structureOutput.ts +3 -4
- package/packages/types/src/aiChat.ts +0 -1
- package/src/app/(backend)/trpc/edge/[trpc]/route.ts +0 -2
- package/src/server/routers/edge/index.ts +2 -1
- package/src/server/routers/lambda/aiChat.ts +1 -2
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/upload.ts +16 -0
- package/src/services/__tests__/upload.test.ts +266 -18
- package/src/services/upload.ts +2 -2
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
FunctionCallingConfigMode,
|
|
3
|
+
GenerateContentConfig,
|
|
4
|
+
GoogleGenAI,
|
|
5
|
+
Type as SchemaType,
|
|
6
|
+
} from '@google/genai';
|
|
2
7
|
import Debug from 'debug';
|
|
3
8
|
|
|
4
|
-
import {
|
|
9
|
+
import { buildGoogleTool } from '../../core/contextBuilders/google';
|
|
10
|
+
import { ChatCompletionTool, GenerateObjectOptions, GenerateObjectSchema } from '../../types';
|
|
5
11
|
|
|
6
|
-
const debug = Debug('mode-runtime:google:generateObject');
|
|
12
|
+
const debug = Debug('lobe-mode-runtime:google:generateObject');
|
|
7
13
|
|
|
8
14
|
enum HarmCategory {
|
|
9
15
|
HARM_CATEGORY_DANGEROUS_CONTENT = 'HARM_CATEGORY_DANGEROUS_CONTENT',
|
|
@@ -54,7 +60,7 @@ const convertType = (type: string): SchemaType => {
|
|
|
54
60
|
/**
|
|
55
61
|
* Convert OpenAI JSON schema to Google Gemini schema format
|
|
56
62
|
*/
|
|
57
|
-
export const convertOpenAISchemaToGoogleSchema = (openAISchema:
|
|
63
|
+
export const convertOpenAISchemaToGoogleSchema = (openAISchema: GenerateObjectSchema): any => {
|
|
58
64
|
const convertSchema = (schema: any): any => {
|
|
59
65
|
if (!schema) return schema;
|
|
60
66
|
|
|
@@ -92,7 +98,7 @@ export const convertOpenAISchemaToGoogleSchema = (openAISchema: any): any => {
|
|
|
92
98
|
return converted;
|
|
93
99
|
};
|
|
94
100
|
|
|
95
|
-
return convertSchema(openAISchema);
|
|
101
|
+
return convertSchema(openAISchema.schema);
|
|
96
102
|
};
|
|
97
103
|
|
|
98
104
|
/**
|
|
@@ -104,7 +110,7 @@ export const createGoogleGenerateObject = async (
|
|
|
104
110
|
payload: {
|
|
105
111
|
contents: any[];
|
|
106
112
|
model: string;
|
|
107
|
-
schema:
|
|
113
|
+
schema: GenerateObjectSchema;
|
|
108
114
|
},
|
|
109
115
|
options?: GenerateObjectOptions,
|
|
110
116
|
) => {
|
|
@@ -175,3 +181,95 @@ export const createGoogleGenerateObject = async (
|
|
|
175
181
|
return undefined;
|
|
176
182
|
}
|
|
177
183
|
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Generate structured output using Google Gemini API with tools calling
|
|
187
|
+
* @see https://ai.google.dev/gemini-api/docs/function-calling
|
|
188
|
+
*/
|
|
189
|
+
export const createGoogleGenerateObjectWithTools = async (
|
|
190
|
+
client: GoogleGenAI,
|
|
191
|
+
payload: {
|
|
192
|
+
contents: any[];
|
|
193
|
+
model: string;
|
|
194
|
+
tools: ChatCompletionTool[];
|
|
195
|
+
},
|
|
196
|
+
options?: GenerateObjectOptions,
|
|
197
|
+
) => {
|
|
198
|
+
const { tools, contents, model } = payload;
|
|
199
|
+
|
|
200
|
+
debug('createGoogleGenerateObjectWithTools started', {
|
|
201
|
+
contentsLength: contents.length,
|
|
202
|
+
model,
|
|
203
|
+
toolsCount: tools.length,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Convert tools to Google FunctionDeclaration format
|
|
207
|
+
const functionDeclarations = tools.map(buildGoogleTool);
|
|
208
|
+
debug('Tools conversion completed', { functionDeclarations });
|
|
209
|
+
|
|
210
|
+
const config: GenerateContentConfig = {
|
|
211
|
+
abortSignal: options?.signal,
|
|
212
|
+
// avoid wide sensitive words
|
|
213
|
+
safetySettings: [
|
|
214
|
+
{
|
|
215
|
+
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
|
|
216
|
+
threshold: getThreshold(model),
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
|
|
220
|
+
threshold: getThreshold(model),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
|
|
224
|
+
threshold: getThreshold(model),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
|
228
|
+
threshold: getThreshold(model),
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
// Force tool calling with 'any' mode
|
|
232
|
+
toolConfig: {
|
|
233
|
+
functionCallingConfig: {
|
|
234
|
+
mode: FunctionCallingConfigMode.ANY,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
tools: [{ functionDeclarations }],
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
debug('Config prepared', {
|
|
241
|
+
hasAbortSignal: !!config.abortSignal,
|
|
242
|
+
hasSafetySettings: !!config.safetySettings,
|
|
243
|
+
hasTools: !!config.tools,
|
|
244
|
+
model,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const response = await client.models.generateContent({
|
|
248
|
+
config,
|
|
249
|
+
contents,
|
|
250
|
+
model,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
debug('API response received', {
|
|
254
|
+
candidatesCount: response.candidates?.length,
|
|
255
|
+
hasContent: !!response.candidates?.[0]?.content,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Extract function calls from response
|
|
259
|
+
const candidate = response.candidates?.[0];
|
|
260
|
+
if (!candidate?.content?.parts) {
|
|
261
|
+
debug('no content parts in response');
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const functionCalls = candidate.content.parts
|
|
266
|
+
.filter((part) => part.functionCall)
|
|
267
|
+
.map((part) => ({
|
|
268
|
+
arguments: part.functionCall!.args,
|
|
269
|
+
name: part.functionCall!.name,
|
|
270
|
+
}));
|
|
271
|
+
|
|
272
|
+
debug('extracted function calls', { count: functionCalls.length, functionCalls });
|
|
273
|
+
|
|
274
|
+
return functionCalls.length > 0 ? functionCalls : undefined;
|
|
275
|
+
};
|
|
@@ -432,401 +432,6 @@ describe('LobeGoogleAI', () => {
|
|
|
432
432
|
});
|
|
433
433
|
|
|
434
434
|
describe('private method', () => {
|
|
435
|
-
describe('convertContentToGooglePart', () => {
|
|
436
|
-
it('should handle text type messages', async () => {
|
|
437
|
-
const result = await instance['convertContentToGooglePart']({
|
|
438
|
-
type: 'text',
|
|
439
|
-
text: 'Hello',
|
|
440
|
-
});
|
|
441
|
-
expect(result).toEqual({ text: 'Hello' });
|
|
442
|
-
});
|
|
443
|
-
it('should handle thinking type messages', async () => {
|
|
444
|
-
const result = await instance['convertContentToGooglePart']({
|
|
445
|
-
type: 'thinking',
|
|
446
|
-
thinking: 'Hello',
|
|
447
|
-
signature: 'abc',
|
|
448
|
-
});
|
|
449
|
-
expect(result).toEqual(undefined);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
it('should handle base64 type images', async () => {
|
|
453
|
-
const base64Image =
|
|
454
|
-
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
|
455
|
-
const result = await instance['convertContentToGooglePart']({
|
|
456
|
-
type: 'image_url',
|
|
457
|
-
image_url: { url: base64Image },
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
expect(result).toEqual({
|
|
461
|
-
inlineData: {
|
|
462
|
-
data: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
|
463
|
-
mimeType: 'image/png',
|
|
464
|
-
},
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it('should handle URL type images', async () => {
|
|
469
|
-
const imageUrl = 'http://example.com/image.png';
|
|
470
|
-
const mockBase64 = 'mockBase64Data';
|
|
471
|
-
|
|
472
|
-
// Mock the imageUrlToBase64 function
|
|
473
|
-
vi.spyOn(imageToBase64Module, 'imageUrlToBase64').mockResolvedValueOnce({
|
|
474
|
-
base64: mockBase64,
|
|
475
|
-
mimeType: 'image/png',
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
const result = await instance['convertContentToGooglePart']({
|
|
479
|
-
type: 'image_url',
|
|
480
|
-
image_url: { url: imageUrl },
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
expect(result).toEqual({
|
|
484
|
-
inlineData: {
|
|
485
|
-
data: mockBase64,
|
|
486
|
-
mimeType: 'image/png',
|
|
487
|
-
},
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
expect(imageToBase64Module.imageUrlToBase64).toHaveBeenCalledWith(imageUrl);
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
it('should throw TypeError for unsupported image URL types', async () => {
|
|
494
|
-
const unsupportedImageUrl = 'unsupported://example.com/image.png';
|
|
495
|
-
|
|
496
|
-
await expect(
|
|
497
|
-
instance['convertContentToGooglePart']({
|
|
498
|
-
type: 'image_url',
|
|
499
|
-
image_url: { url: unsupportedImageUrl },
|
|
500
|
-
}),
|
|
501
|
-
).rejects.toThrow(TypeError);
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
describe('buildGoogleMessages', () => {
|
|
506
|
-
it('get default result with gemini-pro', async () => {
|
|
507
|
-
const messages: OpenAIChatMessage[] = [{ content: 'Hello', role: 'user' }];
|
|
508
|
-
|
|
509
|
-
const contents = await instance['buildGoogleMessages'](messages);
|
|
510
|
-
|
|
511
|
-
expect(contents).toHaveLength(1);
|
|
512
|
-
expect(contents).toEqual([{ parts: [{ text: 'Hello' }], role: 'user' }]);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
it('should not modify the length if model is gemini-1.5-pro', async () => {
|
|
516
|
-
const messages: OpenAIChatMessage[] = [
|
|
517
|
-
{ content: 'Hello', role: 'user' },
|
|
518
|
-
{ content: 'Hi', role: 'assistant' },
|
|
519
|
-
];
|
|
520
|
-
|
|
521
|
-
const contents = await instance['buildGoogleMessages'](messages);
|
|
522
|
-
|
|
523
|
-
expect(contents).toHaveLength(2);
|
|
524
|
-
expect(contents).toEqual([
|
|
525
|
-
{ parts: [{ text: 'Hello' }], role: 'user' },
|
|
526
|
-
{ parts: [{ text: 'Hi' }], role: 'model' },
|
|
527
|
-
]);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it('should use specified model when images are included in messages', async () => {
|
|
531
|
-
const messages: OpenAIChatMessage[] = [
|
|
532
|
-
{
|
|
533
|
-
content: [
|
|
534
|
-
{ type: 'text', text: 'Hello' },
|
|
535
|
-
{ type: 'image_url', image_url: { url: 'data:image/png;base64,...' } },
|
|
536
|
-
],
|
|
537
|
-
role: 'user',
|
|
538
|
-
},
|
|
539
|
-
];
|
|
540
|
-
|
|
541
|
-
// Call the buildGoogleMessages method
|
|
542
|
-
const contents = await instance['buildGoogleMessages'](messages);
|
|
543
|
-
|
|
544
|
-
expect(contents).toHaveLength(1);
|
|
545
|
-
expect(contents).toEqual([
|
|
546
|
-
{
|
|
547
|
-
parts: [{ text: 'Hello' }, { inlineData: { data: '...', mimeType: 'image/png' } }],
|
|
548
|
-
role: 'user',
|
|
549
|
-
},
|
|
550
|
-
]);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
it('should correctly convert function response message', async () => {
|
|
554
|
-
const messages: OpenAIChatMessage[] = [
|
|
555
|
-
{
|
|
556
|
-
content: '',
|
|
557
|
-
role: 'assistant',
|
|
558
|
-
tool_calls: [
|
|
559
|
-
{
|
|
560
|
-
id: 'call_1',
|
|
561
|
-
function: {
|
|
562
|
-
name: 'get_current_weather',
|
|
563
|
-
arguments: JSON.stringify({ location: 'London', unit: 'celsius' }),
|
|
564
|
-
},
|
|
565
|
-
type: 'function',
|
|
566
|
-
},
|
|
567
|
-
],
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
content: '{"success":true,"data":{"temperature":"14°C"}}',
|
|
571
|
-
name: 'get_current_weather',
|
|
572
|
-
role: 'tool',
|
|
573
|
-
tool_call_id: 'call_1',
|
|
574
|
-
},
|
|
575
|
-
];
|
|
576
|
-
|
|
577
|
-
const contents = await instance['buildGoogleMessages'](messages);
|
|
578
|
-
expect(contents).toHaveLength(2);
|
|
579
|
-
expect(contents).toEqual([
|
|
580
|
-
{
|
|
581
|
-
parts: [
|
|
582
|
-
{
|
|
583
|
-
functionCall: {
|
|
584
|
-
args: { location: 'London', unit: 'celsius' },
|
|
585
|
-
name: 'get_current_weather',
|
|
586
|
-
},
|
|
587
|
-
},
|
|
588
|
-
],
|
|
589
|
-
role: 'model',
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
parts: [
|
|
593
|
-
{
|
|
594
|
-
functionResponse: {
|
|
595
|
-
name: 'get_current_weather',
|
|
596
|
-
response: { result: '{"success":true,"data":{"temperature":"14°C"}}' },
|
|
597
|
-
},
|
|
598
|
-
},
|
|
599
|
-
],
|
|
600
|
-
role: 'user',
|
|
601
|
-
},
|
|
602
|
-
]);
|
|
603
|
-
});
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
describe('buildGoogleTools', () => {
|
|
607
|
-
it('should return undefined when tools is undefined or empty', () => {
|
|
608
|
-
expect(instance['buildGoogleTools'](undefined)).toBeUndefined();
|
|
609
|
-
expect(instance['buildGoogleTools']([])).toBeUndefined();
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
it('should correctly convert ChatCompletionTool to GoogleFunctionCallTool', () => {
|
|
613
|
-
const tools: OpenAI.ChatCompletionTool[] = [
|
|
614
|
-
{
|
|
615
|
-
function: {
|
|
616
|
-
name: 'testTool',
|
|
617
|
-
description: 'A test tool',
|
|
618
|
-
parameters: {
|
|
619
|
-
type: 'object',
|
|
620
|
-
properties: {
|
|
621
|
-
param1: { type: 'string' },
|
|
622
|
-
param2: { type: 'number' },
|
|
623
|
-
},
|
|
624
|
-
required: ['param1'],
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
type: 'function',
|
|
628
|
-
},
|
|
629
|
-
];
|
|
630
|
-
|
|
631
|
-
const googleTools = instance['buildGoogleTools'](tools);
|
|
632
|
-
|
|
633
|
-
expect(googleTools).toHaveLength(1);
|
|
634
|
-
expect((googleTools![0] as Tool).functionDeclarations![0]).toEqual({
|
|
635
|
-
name: 'testTool',
|
|
636
|
-
description: 'A test tool',
|
|
637
|
-
parameters: {
|
|
638
|
-
type: 'OBJECT',
|
|
639
|
-
properties: {
|
|
640
|
-
param1: { type: 'string' },
|
|
641
|
-
param2: { type: 'number' },
|
|
642
|
-
},
|
|
643
|
-
required: ['param1'],
|
|
644
|
-
},
|
|
645
|
-
});
|
|
646
|
-
});
|
|
647
|
-
|
|
648
|
-
it('should also add tools when tool_calls exists', () => {
|
|
649
|
-
const tools: OpenAI.ChatCompletionTool[] = [
|
|
650
|
-
{
|
|
651
|
-
function: {
|
|
652
|
-
name: 'testTool',
|
|
653
|
-
description: 'A test tool',
|
|
654
|
-
parameters: {
|
|
655
|
-
type: 'object',
|
|
656
|
-
properties: {
|
|
657
|
-
param1: { type: 'string' },
|
|
658
|
-
param2: { type: 'number' },
|
|
659
|
-
},
|
|
660
|
-
required: ['param1'],
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
type: 'function',
|
|
664
|
-
},
|
|
665
|
-
];
|
|
666
|
-
|
|
667
|
-
const payload: ChatStreamPayload = {
|
|
668
|
-
messages: [
|
|
669
|
-
{
|
|
670
|
-
role: 'user',
|
|
671
|
-
content: '',
|
|
672
|
-
tool_calls: [
|
|
673
|
-
{ function: { name: 'some_func', arguments: '' }, id: 'func_1', type: 'function' },
|
|
674
|
-
],
|
|
675
|
-
},
|
|
676
|
-
],
|
|
677
|
-
model: 'gemini-2.5-flash-preview-04-17',
|
|
678
|
-
temperature: 1,
|
|
679
|
-
};
|
|
680
|
-
|
|
681
|
-
const googleTools = instance['buildGoogleTools'](tools, payload);
|
|
682
|
-
|
|
683
|
-
expect(googleTools).toHaveLength(1);
|
|
684
|
-
expect((googleTools![0] as Tool).functionDeclarations![0]).toEqual({
|
|
685
|
-
name: 'testTool',
|
|
686
|
-
description: 'A test tool',
|
|
687
|
-
parameters: {
|
|
688
|
-
type: 'OBJECT',
|
|
689
|
-
properties: {
|
|
690
|
-
param1: { type: 'string' },
|
|
691
|
-
param2: { type: 'number' },
|
|
692
|
-
},
|
|
693
|
-
required: ['param1'],
|
|
694
|
-
},
|
|
695
|
-
});
|
|
696
|
-
});
|
|
697
|
-
|
|
698
|
-
it('should handle googleSearch', () => {
|
|
699
|
-
const payload: ChatStreamPayload = {
|
|
700
|
-
messages: [
|
|
701
|
-
{
|
|
702
|
-
role: 'user',
|
|
703
|
-
content: '',
|
|
704
|
-
},
|
|
705
|
-
],
|
|
706
|
-
model: 'gemini-2.5-flash-preview-04-17',
|
|
707
|
-
temperature: 1,
|
|
708
|
-
enabledSearch: true,
|
|
709
|
-
};
|
|
710
|
-
|
|
711
|
-
const googleTools = instance['buildGoogleTools'](undefined, payload);
|
|
712
|
-
|
|
713
|
-
expect(googleTools).toHaveLength(1);
|
|
714
|
-
expect(googleTools![0] as Tool).toEqual({ googleSearch: {} });
|
|
715
|
-
});
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
describe('convertOAIMessagesToGoogleMessage', () => {
|
|
719
|
-
it('should correctly convert assistant message', async () => {
|
|
720
|
-
const message: OpenAIChatMessage = {
|
|
721
|
-
role: 'assistant',
|
|
722
|
-
content: 'Hello',
|
|
723
|
-
};
|
|
724
|
-
|
|
725
|
-
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
|
726
|
-
|
|
727
|
-
expect(converted).toEqual({
|
|
728
|
-
role: 'model',
|
|
729
|
-
parts: [{ text: 'Hello' }],
|
|
730
|
-
});
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
it('should correctly convert user message', async () => {
|
|
734
|
-
const message: OpenAIChatMessage = {
|
|
735
|
-
role: 'user',
|
|
736
|
-
content: 'Hi',
|
|
737
|
-
};
|
|
738
|
-
|
|
739
|
-
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
|
740
|
-
|
|
741
|
-
expect(converted).toEqual({
|
|
742
|
-
role: 'user',
|
|
743
|
-
parts: [{ text: 'Hi' }],
|
|
744
|
-
});
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
it('should correctly convert message with inline base64 image parts', async () => {
|
|
748
|
-
const message: OpenAIChatMessage = {
|
|
749
|
-
role: 'user',
|
|
750
|
-
content: [
|
|
751
|
-
{ type: 'text', text: 'Check this image:' },
|
|
752
|
-
{ type: 'image_url', image_url: { url: 'data:image/png;base64,...' } },
|
|
753
|
-
],
|
|
754
|
-
};
|
|
755
|
-
|
|
756
|
-
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
|
757
|
-
|
|
758
|
-
expect(converted).toEqual({
|
|
759
|
-
role: 'user',
|
|
760
|
-
parts: [
|
|
761
|
-
{ text: 'Check this image:' },
|
|
762
|
-
{ inlineData: { data: '...', mimeType: 'image/png' } },
|
|
763
|
-
],
|
|
764
|
-
});
|
|
765
|
-
});
|
|
766
|
-
it.skip('should correctly convert message with image url parts', async () => {
|
|
767
|
-
const message: OpenAIChatMessage = {
|
|
768
|
-
role: 'user',
|
|
769
|
-
content: [
|
|
770
|
-
{ type: 'text', text: 'Check this image:' },
|
|
771
|
-
{ type: 'image_url', image_url: { url: 'https://image-file.com' } },
|
|
772
|
-
],
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
|
776
|
-
|
|
777
|
-
expect(converted).toEqual({
|
|
778
|
-
role: 'user',
|
|
779
|
-
parts: [
|
|
780
|
-
{ text: 'Check this image:' },
|
|
781
|
-
{ inlineData: { data: '...', mimeType: 'image/png' } },
|
|
782
|
-
],
|
|
783
|
-
});
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
it('should correctly convert function call message', async () => {
|
|
787
|
-
const message = {
|
|
788
|
-
role: 'assistant',
|
|
789
|
-
tool_calls: [
|
|
790
|
-
{
|
|
791
|
-
id: 'call_1',
|
|
792
|
-
function: {
|
|
793
|
-
name: 'get_current_weather',
|
|
794
|
-
arguments: JSON.stringify({ location: 'London', unit: 'celsius' }),
|
|
795
|
-
},
|
|
796
|
-
type: 'function',
|
|
797
|
-
},
|
|
798
|
-
],
|
|
799
|
-
} as OpenAIChatMessage;
|
|
800
|
-
|
|
801
|
-
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
|
802
|
-
expect(converted).toEqual({
|
|
803
|
-
role: 'model',
|
|
804
|
-
parts: [
|
|
805
|
-
{
|
|
806
|
-
functionCall: {
|
|
807
|
-
name: 'get_current_weather',
|
|
808
|
-
args: { location: 'London', unit: 'celsius' },
|
|
809
|
-
},
|
|
810
|
-
},
|
|
811
|
-
],
|
|
812
|
-
});
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
it('should correctly handle empty content', async () => {
|
|
816
|
-
const message: OpenAIChatMessage = {
|
|
817
|
-
role: 'user',
|
|
818
|
-
content: '' as any, // explicitly set as empty string
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
const converted = await instance['convertOAIMessagesToGoogleMessage'](message);
|
|
822
|
-
|
|
823
|
-
expect(converted).toEqual({
|
|
824
|
-
role: 'user',
|
|
825
|
-
parts: [{ text: '' }],
|
|
826
|
-
});
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
|
|
830
435
|
describe('createEnhancedStream', () => {
|
|
831
436
|
it('should handle stream cancellation with data gracefully', async () => {
|
|
832
437
|
const mockStream = (async function* () {
|