@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
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { Type as SchemaType } from '@google/genai';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '../../types';
|
|
6
|
+
import * as imageToBase64Module from '../../utils/imageToBase64';
|
|
7
|
+
import { parseDataUri } from '../../utils/uriParser';
|
|
8
|
+
import {
|
|
9
|
+
buildGoogleMessage,
|
|
10
|
+
buildGoogleMessages,
|
|
11
|
+
buildGooglePart,
|
|
12
|
+
buildGoogleTool,
|
|
13
|
+
buildGoogleTools,
|
|
14
|
+
} from './google';
|
|
15
|
+
|
|
16
|
+
// Mock the utils
|
|
17
|
+
vi.mock('../../utils/uriParser', () => ({
|
|
18
|
+
parseDataUri: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock('../../utils/imageToBase64', () => ({
|
|
22
|
+
imageUrlToBase64: vi.fn(),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('google contextBuilders', () => {
|
|
26
|
+
describe('buildGooglePart', () => {
|
|
27
|
+
it('should handle text type messages', async () => {
|
|
28
|
+
const content: UserMessageContentPart = {
|
|
29
|
+
text: 'Hello',
|
|
30
|
+
type: 'text',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const result = await buildGooglePart(content);
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual({ text: 'Hello' });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should handle thinking type messages', async () => {
|
|
39
|
+
const content: UserMessageContentPart = {
|
|
40
|
+
signature: 'abc',
|
|
41
|
+
thinking: 'Hello',
|
|
42
|
+
type: 'thinking',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const result = await buildGooglePart(content);
|
|
46
|
+
|
|
47
|
+
expect(result).toEqual(undefined);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle base64 type images', async () => {
|
|
51
|
+
const base64Image =
|
|
52
|
+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
|
|
53
|
+
|
|
54
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
55
|
+
base64:
|
|
56
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
|
57
|
+
mimeType: 'image/png',
|
|
58
|
+
type: 'base64',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const content: UserMessageContentPart = {
|
|
62
|
+
image_url: { url: base64Image },
|
|
63
|
+
type: 'image_url',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const result = await buildGooglePart(content);
|
|
67
|
+
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
inlineData: {
|
|
70
|
+
data: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
|
71
|
+
mimeType: 'image/png',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should handle URL type images', async () => {
|
|
77
|
+
const imageUrl = 'http://example.com/image.png';
|
|
78
|
+
const mockBase64 = 'mockBase64Data';
|
|
79
|
+
|
|
80
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
81
|
+
base64: null,
|
|
82
|
+
mimeType: 'image/png',
|
|
83
|
+
type: 'url',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
vi.spyOn(imageToBase64Module, 'imageUrlToBase64').mockResolvedValueOnce({
|
|
87
|
+
base64: mockBase64,
|
|
88
|
+
mimeType: 'image/png',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const content: UserMessageContentPart = {
|
|
92
|
+
image_url: { url: imageUrl },
|
|
93
|
+
type: 'image_url',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const result = await buildGooglePart(content);
|
|
97
|
+
|
|
98
|
+
expect(result).toEqual({
|
|
99
|
+
inlineData: {
|
|
100
|
+
data: mockBase64,
|
|
101
|
+
mimeType: 'image/png',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(imageToBase64Module.imageUrlToBase64).toHaveBeenCalledWith(imageUrl);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should throw TypeError for unsupported image URL types', async () => {
|
|
109
|
+
const unsupportedImageUrl = 'unsupported://example.com/image.png';
|
|
110
|
+
|
|
111
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
112
|
+
base64: null,
|
|
113
|
+
mimeType: null,
|
|
114
|
+
type: 'unknown' as any,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const content: UserMessageContentPart = {
|
|
118
|
+
image_url: { url: unsupportedImageUrl },
|
|
119
|
+
type: 'image_url',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
await expect(buildGooglePart(content)).rejects.toThrow(TypeError);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should handle base64 video', async () => {
|
|
126
|
+
const base64Video = 'data:video/mp4;base64,mockVideoBase64Data';
|
|
127
|
+
|
|
128
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
129
|
+
base64: 'mockVideoBase64Data',
|
|
130
|
+
mimeType: 'video/mp4',
|
|
131
|
+
type: 'base64',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const content: UserMessageContentPart = {
|
|
135
|
+
type: 'video_url',
|
|
136
|
+
video_url: { url: base64Video },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = await buildGooglePart(content);
|
|
140
|
+
|
|
141
|
+
expect(result).toEqual({
|
|
142
|
+
inlineData: {
|
|
143
|
+
data: 'mockVideoBase64Data',
|
|
144
|
+
mimeType: 'video/mp4',
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('buildGoogleMessage', () => {
|
|
151
|
+
it('should correctly convert assistant message', async () => {
|
|
152
|
+
const message: OpenAIChatMessage = {
|
|
153
|
+
content: 'Hello',
|
|
154
|
+
role: 'assistant',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const converted = await buildGoogleMessage(message);
|
|
158
|
+
|
|
159
|
+
expect(converted).toEqual({
|
|
160
|
+
parts: [{ text: 'Hello' }],
|
|
161
|
+
role: 'model',
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should correctly convert user message', async () => {
|
|
166
|
+
const message: OpenAIChatMessage = {
|
|
167
|
+
content: 'Hi',
|
|
168
|
+
role: 'user',
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const converted = await buildGoogleMessage(message);
|
|
172
|
+
|
|
173
|
+
expect(converted).toEqual({
|
|
174
|
+
parts: [{ text: 'Hi' }],
|
|
175
|
+
role: 'user',
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should correctly convert message with inline base64 image parts', async () => {
|
|
180
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
181
|
+
base64: '...',
|
|
182
|
+
mimeType: 'image/png',
|
|
183
|
+
type: 'base64',
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const message: OpenAIChatMessage = {
|
|
187
|
+
content: [
|
|
188
|
+
{ text: 'Check this image:', type: 'text' },
|
|
189
|
+
{ image_url: { url: 'data:image/png;base64,...' }, type: 'image_url' },
|
|
190
|
+
],
|
|
191
|
+
role: 'user',
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const converted = await buildGoogleMessage(message);
|
|
195
|
+
|
|
196
|
+
expect(converted).toEqual({
|
|
197
|
+
parts: [
|
|
198
|
+
{ text: 'Check this image:' },
|
|
199
|
+
{ inlineData: { data: '...', mimeType: 'image/png' } },
|
|
200
|
+
],
|
|
201
|
+
role: 'user',
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should correctly convert function call message', async () => {
|
|
206
|
+
const message = {
|
|
207
|
+
role: 'assistant',
|
|
208
|
+
tool_calls: [
|
|
209
|
+
{
|
|
210
|
+
function: {
|
|
211
|
+
arguments: JSON.stringify({ location: 'London', unit: 'celsius' }),
|
|
212
|
+
name: 'get_current_weather',
|
|
213
|
+
},
|
|
214
|
+
id: 'call_1',
|
|
215
|
+
type: 'function',
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
} as OpenAIChatMessage;
|
|
219
|
+
|
|
220
|
+
const converted = await buildGoogleMessage(message);
|
|
221
|
+
|
|
222
|
+
expect(converted).toEqual({
|
|
223
|
+
parts: [
|
|
224
|
+
{
|
|
225
|
+
functionCall: {
|
|
226
|
+
args: { location: 'London', unit: 'celsius' },
|
|
227
|
+
name: 'get_current_weather',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
role: 'model',
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should correctly handle empty content', async () => {
|
|
236
|
+
const message: OpenAIChatMessage = {
|
|
237
|
+
content: '' as any, // explicitly set as empty string
|
|
238
|
+
role: 'user',
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const converted = await buildGoogleMessage(message);
|
|
242
|
+
|
|
243
|
+
expect(converted).toEqual({
|
|
244
|
+
parts: [{ text: '' }],
|
|
245
|
+
role: 'user',
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should correctly convert tool response message', async () => {
|
|
250
|
+
const toolCallNameMap = new Map<string, string>();
|
|
251
|
+
toolCallNameMap.set('call_1', 'get_current_weather');
|
|
252
|
+
|
|
253
|
+
const message: OpenAIChatMessage = {
|
|
254
|
+
content: '{"success":true,"data":{"temperature":"14°C"}}',
|
|
255
|
+
name: 'get_current_weather',
|
|
256
|
+
role: 'tool',
|
|
257
|
+
tool_call_id: 'call_1',
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const converted = await buildGoogleMessage(message, toolCallNameMap);
|
|
261
|
+
|
|
262
|
+
expect(converted).toEqual({
|
|
263
|
+
parts: [
|
|
264
|
+
{
|
|
265
|
+
functionResponse: {
|
|
266
|
+
name: 'get_current_weather',
|
|
267
|
+
response: { result: '{"success":true,"data":{"temperature":"14°C"}}' },
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
role: 'user',
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('buildGoogleMessages', () => {
|
|
277
|
+
it('get default result with gemini-pro', async () => {
|
|
278
|
+
const messages: OpenAIChatMessage[] = [{ content: 'Hello', role: 'user' }];
|
|
279
|
+
|
|
280
|
+
const contents = await buildGoogleMessages(messages);
|
|
281
|
+
|
|
282
|
+
expect(contents).toHaveLength(1);
|
|
283
|
+
expect(contents).toEqual([{ parts: [{ text: 'Hello' }], role: 'user' }]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should not modify the length if model is gemini-1.5-pro', async () => {
|
|
287
|
+
const messages: OpenAIChatMessage[] = [
|
|
288
|
+
{ content: 'Hello', role: 'user' },
|
|
289
|
+
{ content: 'Hi', role: 'assistant' },
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
const contents = await buildGoogleMessages(messages);
|
|
293
|
+
|
|
294
|
+
expect(contents).toHaveLength(2);
|
|
295
|
+
expect(contents).toEqual([
|
|
296
|
+
{ parts: [{ text: 'Hello' }], role: 'user' },
|
|
297
|
+
{ parts: [{ text: 'Hi' }], role: 'model' },
|
|
298
|
+
]);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should use specified model when images are included in messages', async () => {
|
|
302
|
+
vi.mocked(parseDataUri).mockReturnValueOnce({
|
|
303
|
+
base64: '...',
|
|
304
|
+
mimeType: 'image/png',
|
|
305
|
+
type: 'base64',
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const messages: OpenAIChatMessage[] = [
|
|
309
|
+
{
|
|
310
|
+
content: [
|
|
311
|
+
{ text: 'Hello', type: 'text' },
|
|
312
|
+
{ image_url: { url: 'data:image/png;base64,...' }, type: 'image_url' },
|
|
313
|
+
],
|
|
314
|
+
role: 'user',
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
const contents = await buildGoogleMessages(messages);
|
|
319
|
+
|
|
320
|
+
expect(contents).toHaveLength(1);
|
|
321
|
+
expect(contents).toEqual([
|
|
322
|
+
{
|
|
323
|
+
parts: [{ text: 'Hello' }, { inlineData: { data: '...', mimeType: 'image/png' } }],
|
|
324
|
+
role: 'user',
|
|
325
|
+
},
|
|
326
|
+
]);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should correctly convert function response message', async () => {
|
|
330
|
+
const messages: OpenAIChatMessage[] = [
|
|
331
|
+
{
|
|
332
|
+
content: '',
|
|
333
|
+
role: 'assistant',
|
|
334
|
+
tool_calls: [
|
|
335
|
+
{
|
|
336
|
+
function: {
|
|
337
|
+
arguments: JSON.stringify({ location: 'London', unit: 'celsius' }),
|
|
338
|
+
name: 'get_current_weather',
|
|
339
|
+
},
|
|
340
|
+
id: 'call_1',
|
|
341
|
+
type: 'function',
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
content: '{"success":true,"data":{"temperature":"14°C"}}',
|
|
347
|
+
name: 'get_current_weather',
|
|
348
|
+
role: 'tool',
|
|
349
|
+
tool_call_id: 'call_1',
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
const contents = await buildGoogleMessages(messages);
|
|
354
|
+
|
|
355
|
+
expect(contents).toHaveLength(2);
|
|
356
|
+
expect(contents).toEqual([
|
|
357
|
+
{
|
|
358
|
+
parts: [
|
|
359
|
+
{
|
|
360
|
+
functionCall: {
|
|
361
|
+
args: { location: 'London', unit: 'celsius' },
|
|
362
|
+
name: 'get_current_weather',
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
role: 'model',
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
parts: [
|
|
370
|
+
{
|
|
371
|
+
functionResponse: {
|
|
372
|
+
name: 'get_current_weather',
|
|
373
|
+
response: { result: '{"success":true,"data":{"temperature":"14°C"}}' },
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
role: 'user',
|
|
378
|
+
},
|
|
379
|
+
]);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should filter out function role messages', async () => {
|
|
383
|
+
const messages: OpenAIChatMessage[] = [
|
|
384
|
+
{ content: 'Hello', role: 'user' },
|
|
385
|
+
{ content: 'function result', name: 'test_func', role: 'function' },
|
|
386
|
+
{ content: 'Hi', role: 'assistant' },
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const contents = await buildGoogleMessages(messages);
|
|
390
|
+
|
|
391
|
+
expect(contents).toHaveLength(2);
|
|
392
|
+
expect(contents).toEqual([
|
|
393
|
+
{ parts: [{ text: 'Hello' }], role: 'user' },
|
|
394
|
+
{ parts: [{ text: 'Hi' }], role: 'model' },
|
|
395
|
+
]);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should filter out empty messages', async () => {
|
|
399
|
+
const messages: OpenAIChatMessage[] = [
|
|
400
|
+
{ content: 'Hello', role: 'user' },
|
|
401
|
+
{ content: [], role: 'user' },
|
|
402
|
+
{ content: 'Hi', role: 'assistant' },
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
const contents = await buildGoogleMessages(messages);
|
|
406
|
+
|
|
407
|
+
expect(contents).toHaveLength(2);
|
|
408
|
+
expect(contents).toEqual([
|
|
409
|
+
{ parts: [{ text: 'Hello' }], role: 'user' },
|
|
410
|
+
{ parts: [{ text: 'Hi' }], role: 'model' },
|
|
411
|
+
]);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('buildGoogleTool', () => {
|
|
416
|
+
it('should correctly convert ChatCompletionTool to FunctionDeclaration', () => {
|
|
417
|
+
const tool: ChatCompletionTool = {
|
|
418
|
+
function: {
|
|
419
|
+
description: 'A test tool',
|
|
420
|
+
name: 'testTool',
|
|
421
|
+
parameters: {
|
|
422
|
+
properties: {
|
|
423
|
+
param1: { type: 'string' },
|
|
424
|
+
param2: { type: 'number' },
|
|
425
|
+
},
|
|
426
|
+
required: ['param1'],
|
|
427
|
+
type: 'object',
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
type: 'function',
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const result = buildGoogleTool(tool);
|
|
434
|
+
|
|
435
|
+
expect(result).toEqual({
|
|
436
|
+
description: 'A test tool',
|
|
437
|
+
name: 'testTool',
|
|
438
|
+
parameters: {
|
|
439
|
+
description: undefined,
|
|
440
|
+
properties: {
|
|
441
|
+
param1: { type: 'string' },
|
|
442
|
+
param2: { type: 'number' },
|
|
443
|
+
},
|
|
444
|
+
required: ['param1'],
|
|
445
|
+
type: SchemaType.OBJECT,
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should handle tools with empty parameters', () => {
|
|
451
|
+
const tool: ChatCompletionTool = {
|
|
452
|
+
function: {
|
|
453
|
+
description: 'A simple function with no parameters',
|
|
454
|
+
name: 'simple_function',
|
|
455
|
+
parameters: {
|
|
456
|
+
properties: {},
|
|
457
|
+
type: 'object',
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
type: 'function',
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const result = buildGoogleTool(tool);
|
|
464
|
+
|
|
465
|
+
// Should use dummy property for empty parameters
|
|
466
|
+
expect(result).toEqual({
|
|
467
|
+
description: 'A simple function with no parameters',
|
|
468
|
+
name: 'simple_function',
|
|
469
|
+
parameters: {
|
|
470
|
+
description: undefined,
|
|
471
|
+
properties: { dummy: { type: 'string' } },
|
|
472
|
+
required: undefined,
|
|
473
|
+
type: SchemaType.OBJECT,
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should preserve parameter description', () => {
|
|
479
|
+
const tool: ChatCompletionTool = {
|
|
480
|
+
function: {
|
|
481
|
+
description: 'A test tool',
|
|
482
|
+
name: 'testTool',
|
|
483
|
+
parameters: {
|
|
484
|
+
description: 'Test parameters',
|
|
485
|
+
properties: {
|
|
486
|
+
param1: { type: 'string' },
|
|
487
|
+
},
|
|
488
|
+
type: 'object',
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
type: 'function',
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const result = buildGoogleTool(tool);
|
|
495
|
+
|
|
496
|
+
expect(result.parameters?.description).toBe('Test parameters');
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe('buildGoogleTools', () => {
|
|
501
|
+
it('should return undefined when tools is undefined or empty', () => {
|
|
502
|
+
expect(buildGoogleTools(undefined)).toBeUndefined();
|
|
503
|
+
expect(buildGoogleTools([])).toBeUndefined();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('should correctly convert ChatCompletionTool array to GoogleFunctionCallTool', () => {
|
|
507
|
+
const tools: ChatCompletionTool[] = [
|
|
508
|
+
{
|
|
509
|
+
function: {
|
|
510
|
+
description: 'A test tool',
|
|
511
|
+
name: 'testTool',
|
|
512
|
+
parameters: {
|
|
513
|
+
properties: {
|
|
514
|
+
param1: { type: 'string' },
|
|
515
|
+
param2: { type: 'number' },
|
|
516
|
+
},
|
|
517
|
+
required: ['param1'],
|
|
518
|
+
type: 'object',
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
type: 'function',
|
|
522
|
+
},
|
|
523
|
+
];
|
|
524
|
+
|
|
525
|
+
const googleTools = buildGoogleTools(tools);
|
|
526
|
+
|
|
527
|
+
expect(googleTools).toHaveLength(1);
|
|
528
|
+
expect(googleTools![0].functionDeclarations).toHaveLength(1);
|
|
529
|
+
expect(googleTools![0].functionDeclarations![0]).toEqual({
|
|
530
|
+
description: 'A test tool',
|
|
531
|
+
name: 'testTool',
|
|
532
|
+
parameters: {
|
|
533
|
+
description: undefined,
|
|
534
|
+
properties: {
|
|
535
|
+
param1: { type: 'string' },
|
|
536
|
+
param2: { type: 'number' },
|
|
537
|
+
},
|
|
538
|
+
required: ['param1'],
|
|
539
|
+
type: SchemaType.OBJECT,
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should handle multiple tools', () => {
|
|
545
|
+
const tools: ChatCompletionTool[] = [
|
|
546
|
+
{
|
|
547
|
+
function: {
|
|
548
|
+
description: 'Get weather information',
|
|
549
|
+
name: 'get_weather',
|
|
550
|
+
parameters: {
|
|
551
|
+
properties: {
|
|
552
|
+
city: { type: 'string' },
|
|
553
|
+
unit: { type: 'string' },
|
|
554
|
+
},
|
|
555
|
+
required: ['city'],
|
|
556
|
+
type: 'object',
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
type: 'function',
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
function: {
|
|
563
|
+
description: 'Get current time',
|
|
564
|
+
name: 'get_time',
|
|
565
|
+
parameters: {
|
|
566
|
+
properties: {
|
|
567
|
+
timezone: { type: 'string' },
|
|
568
|
+
},
|
|
569
|
+
required: ['timezone'],
|
|
570
|
+
type: 'object',
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
type: 'function',
|
|
574
|
+
},
|
|
575
|
+
];
|
|
576
|
+
|
|
577
|
+
const googleTools = buildGoogleTools(tools);
|
|
578
|
+
|
|
579
|
+
expect(googleTools).toHaveLength(1);
|
|
580
|
+
expect(googleTools![0].functionDeclarations).toHaveLength(2);
|
|
581
|
+
expect(googleTools![0].functionDeclarations![0].name).toBe('get_weather');
|
|
582
|
+
expect(googleTools![0].functionDeclarations![1].name).toBe('get_time');
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
});
|