@lobehub/lobehub 2.0.0-next.88 → 2.0.0-next.89
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 +25 -0
- package/changelog/v1.json +9 -0
- package/next.config.ts +0 -1
- package/package.json +2 -2
- package/packages/context-engine/src/processors/ToolCall.ts +1 -0
- package/packages/context-engine/src/processors/__tests__/ToolCall.test.ts +59 -0
- package/packages/context-engine/src/tools/ToolNameResolver.ts +1 -0
- package/packages/context-engine/src/tools/__tests__/ToolNameResolver.test.ts +57 -0
- package/packages/context-engine/src/types.ts +1 -0
- package/packages/fetch-sse/src/fetchSSE.ts +12 -2
- package/packages/model-runtime/src/core/contextBuilders/google.test.ts +479 -0
- package/packages/model-runtime/src/core/contextBuilders/google.ts +44 -1
- package/packages/model-runtime/src/core/streams/google/google-ai.test.ts +1115 -814
- package/packages/model-runtime/src/core/streams/google/index.ts +19 -5
- package/packages/model-runtime/src/core/streams/protocol.ts +1 -0
- package/packages/model-runtime/src/providers/google/index.test.ts +1 -1
- package/packages/model-runtime/src/providers/google/index.ts +11 -9
- package/packages/model-runtime/src/types/toolsCalling.ts +3 -1
- package/packages/types/src/message/common/tools.ts +3 -0
- package/src/features/Conversation/Messages/Group/Error/index.tsx +3 -2
- package/src/features/Conversation/Messages/Group/GroupItem.tsx +2 -2
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -5
- package/src/store/chat/slices/message/actions/optimisticUpdate.ts +6 -11
- package/src/store/chat/slices/plugin/actions/internals.ts +2 -2
|
@@ -6,6 +6,7 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
6
6
|
import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '../../types';
|
|
7
7
|
import { parseDataUri } from '../../utils/uriParser';
|
|
8
8
|
import {
|
|
9
|
+
GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
9
10
|
buildGoogleMessage,
|
|
10
11
|
buildGoogleMessages,
|
|
11
12
|
buildGooglePart,
|
|
@@ -232,6 +233,415 @@ describe('google contextBuilders', () => {
|
|
|
232
233
|
});
|
|
233
234
|
});
|
|
234
235
|
|
|
236
|
+
it('should correctly convert function call message with thoughtSignature', async () => {
|
|
237
|
+
const message = {
|
|
238
|
+
role: 'assistant',
|
|
239
|
+
tool_calls: [
|
|
240
|
+
{
|
|
241
|
+
function: {
|
|
242
|
+
arguments: JSON.stringify({
|
|
243
|
+
language: ['JSON'],
|
|
244
|
+
path: 'package.json',
|
|
245
|
+
query: '"version":',
|
|
246
|
+
repo: 'lobehub/lobe-chat',
|
|
247
|
+
}),
|
|
248
|
+
name: 'grep____searchGitHub____mcp',
|
|
249
|
+
},
|
|
250
|
+
id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
|
|
251
|
+
thoughtSignature:
|
|
252
|
+
'EsUHCsIHAdHtim9/MrjP+pnhM8DVkvulyfWQVf+isXQxEAbF32gbflE1hl6Te80qtp77Ywn8opB2uhQOIH/l6SStsj3+XRy1U1DTeKtqZxDBoLP2rNK6pi3/nk0ZOQIc8f6rxB70G/zOhk7d/1XQFqhmw5H+yDVRQjGD1cNPY5ctWGxQLAIk/HMWNovUJzz2c81jGWoXu7k2vtpuur2hcAL+J79BEVUTfvU3mSiXqJFTClmFPB6Fe79i0y3TwM2XdIBxzPgVgf8B+Pnv1S6YDxHNSm46jTlXKcSw30r3ixs5xEOzerbOUW5WG9BGukw/YQVvHiuoGLIALRa2Ig7dlOMH8+o+f0mKJtyYj8yF6wyBMol+G4mhSHvQSKJLj/Z5kFHvDZKeVUEOZed6vZivYLrVezjQPXgLHJMOmbp6QrZGxqW45QxDKY5X5F8giIOM8VgsUYhDQUBown+3vvwkIBA24icDsOwdhJ/roe9GabbGfxpkSzARIFh7rSI01cRKbh6cEaVFXf2WQftPeD7dBseQLiCdUYoy4ytECrjTpknrWnVUG6Ly4SKW6uN/IJXpm9JT9GgnGLIddFtEQzm9sIKWNpGEz6++lZpiCFS6LsYSnTP3vPj/7oSABRmwWywxA8EmLh+sv+jiK5aMjFi1sTuJ0Ujsvza3/SHZKewNi9WKQUDOa9Mqtjs2YGDnJxto4l5GMUzI5vhf6/+/A5eHALfVabaFP97v8FEPrXQU94dognwx4EnNqy/KWmGIlYZYqIfjaSAy7Z74viwl+oTtL9gyyBDc/FrQvXfyrYIq8N0pkLKAEh33fa/+YVocLL1LKI9rb2bg/RRr+Ee4NyIQKhIdEJaEh74d1COd/4r06J92ThkfVo5PEVTSsr8tBKiJ5wSmX9vyhbLWzxmXoq1xfGrs8kg7NMW53XEWGlQrIVOQmUtjjjBQKj6b4rBTAO6EKk63cGFbkSPohifiUBPHbxUUPy/hf0tQpeOo3jA01AuCFLOIZ5IYJ+Rm5+aZTU3Panv+Q7Yl1w5t5swhbNZfg7MlU/sxwLijLuWDDNfw+2Zw/aa3VDPgVw6Nv2vKkHi4tUU0XlgfiQgQYUMPxpGRV837uUxvZFNep2QUlAMog5h4sMYJWIAX1kK1pzsyR/KxuCn6nUq4ovWNBQHLC4aW2ZcGgW/6CbF81F1cewUz+vWNMMkJrL0d9celGEbFuY0Q709UipaDbCg49twlnLV9XUwqC5wYTFBiJbynBDqiZAvXn2YOxNIs8CCzuu2GSCQDo09ksJy5g/o=',
|
|
253
|
+
type: 'function',
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
} as OpenAIChatMessage;
|
|
257
|
+
|
|
258
|
+
const converted = await buildGoogleMessage(message);
|
|
259
|
+
|
|
260
|
+
expect(converted).toEqual({
|
|
261
|
+
parts: [
|
|
262
|
+
{
|
|
263
|
+
functionCall: {
|
|
264
|
+
args: {
|
|
265
|
+
language: ['JSON'],
|
|
266
|
+
path: 'package.json',
|
|
267
|
+
query: '"version":',
|
|
268
|
+
repo: 'lobehub/lobe-chat',
|
|
269
|
+
},
|
|
270
|
+
name: 'grep____searchGitHub____mcp',
|
|
271
|
+
},
|
|
272
|
+
thoughtSignature:
|
|
273
|
+
'EsUHCsIHAdHtim9/MrjP+pnhM8DVkvulyfWQVf+isXQxEAbF32gbflE1hl6Te80qtp77Ywn8opB2uhQOIH/l6SStsj3+XRy1U1DTeKtqZxDBoLP2rNK6pi3/nk0ZOQIc8f6rxB70G/zOhk7d/1XQFqhmw5H+yDVRQjGD1cNPY5ctWGxQLAIk/HMWNovUJzz2c81jGWoXu7k2vtpuur2hcAL+J79BEVUTfvU3mSiXqJFTClmFPB6Fe79i0y3TwM2XdIBxzPgVgf8B+Pnv1S6YDxHNSm46jTlXKcSw30r3ixs5xEOzerbOUW5WG9BGukw/YQVvHiuoGLIALRa2Ig7dlOMH8+o+f0mKJtyYj8yF6wyBMol+G4mhSHvQSKJLj/Z5kFHvDZKeVUEOZed6vZivYLrVezjQPXgLHJMOmbp6QrZGxqW45QxDKY5X5F8giIOM8VgsUYhDQUBown+3vvwkIBA24icDsOwdhJ/roe9GabbGfxpkSzARIFh7rSI01cRKbh6cEaVFXf2WQftPeD7dBseQLiCdUYoy4ytECrjTpknrWnVUG6Ly4SKW6uN/IJXpm9JT9GgnGLIddFtEQzm9sIKWNpGEz6++lZpiCFS6LsYSnTP3vPj/7oSABRmwWywxA8EmLh+sv+jiK5aMjFi1sTuJ0Ujsvza3/SHZKewNi9WKQUDOa9Mqtjs2YGDnJxto4l5GMUzI5vhf6/+/A5eHALfVabaFP97v8FEPrXQU94dognwx4EnNqy/KWmGIlYZYqIfjaSAy7Z74viwl+oTtL9gyyBDc/FrQvXfyrYIq8N0pkLKAEh33fa/+YVocLL1LKI9rb2bg/RRr+Ee4NyIQKhIdEJaEh74d1COd/4r06J92ThkfVo5PEVTSsr8tBKiJ5wSmX9vyhbLWzxmXoq1xfGrs8kg7NMW53XEWGlQrIVOQmUtjjjBQKj6b4rBTAO6EKk63cGFbkSPohifiUBPHbxUUPy/hf0tQpeOo3jA01AuCFLOIZ5IYJ+Rm5+aZTU3Panv+Q7Yl1w5t5swhbNZfg7MlU/sxwLijLuWDDNfw+2Zw/aa3VDPgVw6Nv2vKkHi4tUU0XlgfiQgQYUMPxpGRV837uUxvZFNep2QUlAMog5h4sMYJWIAX1kK1pzsyR/KxuCn6nUq4ovWNBQHLC4aW2ZcGgW/6CbF81F1cewUz+vWNMMkJrL0d9celGEbFuY0Q709UipaDbCg49twlnLV9XUwqC5wYTFBiJbynBDqiZAvXn2YOxNIs8CCzuu2GSCQDo09ksJy5g/o=',
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
role: 'model',
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('should correctly convert function call message without thoughtSignature', () => {
|
|
281
|
+
it('should add magic signature when last message is tool message', async () => {
|
|
282
|
+
const messages: OpenAIChatMessage[] = [
|
|
283
|
+
{
|
|
284
|
+
content: '<plugins>Web Browsing plugin available</plugins>',
|
|
285
|
+
role: 'system',
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
content: '杭州天气如何',
|
|
289
|
+
role: 'user',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
content: '',
|
|
293
|
+
role: 'assistant',
|
|
294
|
+
tool_calls: [
|
|
295
|
+
{
|
|
296
|
+
function: {
|
|
297
|
+
arguments: '{"query":"杭州天气","searchEngines":["google"]}',
|
|
298
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
299
|
+
},
|
|
300
|
+
id: 'call_001',
|
|
301
|
+
type: 'function',
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
content: 'Tool execution was aborted by user.',
|
|
307
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
308
|
+
role: 'tool',
|
|
309
|
+
tool_call_id: 'call_001',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
content: '',
|
|
313
|
+
role: 'assistant',
|
|
314
|
+
tool_calls: [
|
|
315
|
+
{
|
|
316
|
+
function: {
|
|
317
|
+
arguments: '{"query":"杭州 天气","searchEngines":["bing"]}',
|
|
318
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
319
|
+
},
|
|
320
|
+
id: 'call_002',
|
|
321
|
+
type: 'function',
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
content: 'no result',
|
|
327
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
328
|
+
role: 'tool',
|
|
329
|
+
tool_call_id: 'call_002',
|
|
330
|
+
},
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const contents = await buildGoogleMessages(messages);
|
|
334
|
+
|
|
335
|
+
expect(contents).toEqual([
|
|
336
|
+
{
|
|
337
|
+
parts: [{ text: '<plugins>Web Browsing plugin available</plugins>' }],
|
|
338
|
+
role: 'user',
|
|
339
|
+
},
|
|
340
|
+
{ parts: [{ text: '杭州天气如何' }], role: 'user' },
|
|
341
|
+
{
|
|
342
|
+
parts: [
|
|
343
|
+
{
|
|
344
|
+
functionCall: {
|
|
345
|
+
args: { query: '杭州天气', searchEngines: ['google'] },
|
|
346
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
347
|
+
},
|
|
348
|
+
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
role: 'model',
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
parts: [
|
|
355
|
+
{
|
|
356
|
+
functionResponse: {
|
|
357
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
358
|
+
response: { result: 'Tool execution was aborted by user.' },
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
role: 'user',
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
parts: [
|
|
366
|
+
{
|
|
367
|
+
functionCall: {
|
|
368
|
+
args: { query: '杭州 天气', searchEngines: ['bing'] },
|
|
369
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
370
|
+
},
|
|
371
|
+
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
role: 'model',
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
parts: [
|
|
378
|
+
{
|
|
379
|
+
functionResponse: {
|
|
380
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
381
|
+
response: { result: 'no result' },
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
role: 'user',
|
|
386
|
+
},
|
|
387
|
+
]);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should NOT add magic signature when thoughtSignature already exists', async () => {
|
|
391
|
+
const existingSignature = 'existing_signature_from_model';
|
|
392
|
+
const messages: OpenAIChatMessage[] = [
|
|
393
|
+
{
|
|
394
|
+
content: '杭州天气如何',
|
|
395
|
+
role: 'user',
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
content: '',
|
|
399
|
+
role: 'assistant',
|
|
400
|
+
tool_calls: [
|
|
401
|
+
{
|
|
402
|
+
function: {
|
|
403
|
+
arguments: '{"query":"杭州天气","searchEngines":["google"]}',
|
|
404
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
405
|
+
},
|
|
406
|
+
id: 'call_001',
|
|
407
|
+
thoughtSignature: existingSignature,
|
|
408
|
+
type: 'function',
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
content: 'Tool result',
|
|
414
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
415
|
+
role: 'tool',
|
|
416
|
+
tool_call_id: 'call_001',
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
|
|
420
|
+
const contents = await buildGoogleMessages(messages);
|
|
421
|
+
|
|
422
|
+
expect(contents).toEqual([
|
|
423
|
+
{
|
|
424
|
+
parts: [{ text: '杭州天气如何' }],
|
|
425
|
+
role: 'user',
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
parts: [
|
|
429
|
+
{
|
|
430
|
+
functionCall: {
|
|
431
|
+
args: { query: '杭州天气', searchEngines: ['google'] },
|
|
432
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
433
|
+
},
|
|
434
|
+
// Should keep existing thoughtSignature, not add magic signature
|
|
435
|
+
thoughtSignature: existingSignature,
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
role: 'model',
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
parts: [
|
|
442
|
+
{
|
|
443
|
+
functionResponse: {
|
|
444
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
445
|
+
response: { result: 'Tool result' },
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
],
|
|
449
|
+
role: 'user',
|
|
450
|
+
},
|
|
451
|
+
]);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('should add magic signature only after last user message in multi-turn scenario', async () => {
|
|
455
|
+
const messages: OpenAIChatMessage[] = [
|
|
456
|
+
{
|
|
457
|
+
content: 'First question',
|
|
458
|
+
role: 'user',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
content: '',
|
|
462
|
+
role: 'assistant',
|
|
463
|
+
tool_calls: [
|
|
464
|
+
{
|
|
465
|
+
function: {
|
|
466
|
+
arguments: '{"query":"first"}',
|
|
467
|
+
name: 'search',
|
|
468
|
+
},
|
|
469
|
+
id: 'call_001',
|
|
470
|
+
type: 'function',
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
content: 'First result',
|
|
476
|
+
name: 'search',
|
|
477
|
+
role: 'tool',
|
|
478
|
+
tool_call_id: 'call_001',
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
content: 'Second question',
|
|
482
|
+
role: 'user',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
content: '',
|
|
486
|
+
role: 'assistant',
|
|
487
|
+
tool_calls: [
|
|
488
|
+
{
|
|
489
|
+
function: {
|
|
490
|
+
arguments: '{"query":"second"}',
|
|
491
|
+
name: 'search',
|
|
492
|
+
},
|
|
493
|
+
id: 'call_002',
|
|
494
|
+
type: 'function',
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
content: 'Second result',
|
|
500
|
+
name: 'search',
|
|
501
|
+
role: 'tool',
|
|
502
|
+
tool_call_id: 'call_002',
|
|
503
|
+
},
|
|
504
|
+
];
|
|
505
|
+
|
|
506
|
+
const contents = await buildGoogleMessages(messages);
|
|
507
|
+
|
|
508
|
+
expect(contents).toEqual([
|
|
509
|
+
{
|
|
510
|
+
parts: [{ text: 'First question' }],
|
|
511
|
+
role: 'user',
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
parts: [
|
|
515
|
+
{
|
|
516
|
+
functionCall: {
|
|
517
|
+
args: { query: 'first' },
|
|
518
|
+
name: 'search',
|
|
519
|
+
},
|
|
520
|
+
// No magic signature for this one (before last user message)
|
|
521
|
+
},
|
|
522
|
+
],
|
|
523
|
+
role: 'model',
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
parts: [
|
|
527
|
+
{
|
|
528
|
+
functionResponse: {
|
|
529
|
+
name: 'search',
|
|
530
|
+
response: { result: 'First result' },
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
role: 'user',
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
parts: [{ text: 'Second question' }],
|
|
538
|
+
role: 'user',
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
parts: [
|
|
542
|
+
{
|
|
543
|
+
functionCall: {
|
|
544
|
+
args: { query: 'second' },
|
|
545
|
+
name: 'search',
|
|
546
|
+
},
|
|
547
|
+
// Magic signature added (after last user message)
|
|
548
|
+
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
role: 'model',
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
parts: [
|
|
555
|
+
{
|
|
556
|
+
functionResponse: {
|
|
557
|
+
name: 'search',
|
|
558
|
+
response: { result: 'Second result' },
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
role: 'user',
|
|
563
|
+
},
|
|
564
|
+
]);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('should NOT add magic signature when last message is user text message', async () => {
|
|
568
|
+
const messages: OpenAIChatMessage[] = [
|
|
569
|
+
{
|
|
570
|
+
content: '<plugins>Web Browsing plugin available</plugins>',
|
|
571
|
+
role: 'system',
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
content: '杭州天气如何',
|
|
575
|
+
role: 'user',
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
content: '',
|
|
579
|
+
role: 'assistant',
|
|
580
|
+
tool_calls: [
|
|
581
|
+
{
|
|
582
|
+
function: {
|
|
583
|
+
arguments: '{"query":"杭州天气","searchEngines":["google"]}',
|
|
584
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
585
|
+
},
|
|
586
|
+
id: 'call_001',
|
|
587
|
+
type: 'function',
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
content: 'Tool execution was aborted by user.',
|
|
593
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
594
|
+
role: 'tool',
|
|
595
|
+
tool_call_id: 'call_001',
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
content: 'Please try again',
|
|
599
|
+
role: 'user',
|
|
600
|
+
},
|
|
601
|
+
];
|
|
602
|
+
|
|
603
|
+
const contents = await buildGoogleMessages(messages);
|
|
604
|
+
|
|
605
|
+
expect(contents).toEqual([
|
|
606
|
+
{
|
|
607
|
+
parts: [{ text: '<plugins>Web Browsing plugin available</plugins>' }],
|
|
608
|
+
role: 'user',
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
parts: [{ text: '杭州天气如何' }],
|
|
612
|
+
role: 'user',
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
parts: [
|
|
616
|
+
{
|
|
617
|
+
functionCall: {
|
|
618
|
+
args: { query: '杭州天气', searchEngines: ['google'] },
|
|
619
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
620
|
+
},
|
|
621
|
+
// No thoughtSignature should be added when last message is user text
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
role: 'model',
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
parts: [
|
|
628
|
+
{
|
|
629
|
+
functionResponse: {
|
|
630
|
+
name: 'lobe-web-browsing____search____builtin',
|
|
631
|
+
response: { result: 'Tool execution was aborted by user.' },
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
role: 'user',
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
parts: [{ text: 'Please try again' }],
|
|
639
|
+
role: 'user',
|
|
640
|
+
},
|
|
641
|
+
]);
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
235
645
|
it('should correctly handle empty content', async () => {
|
|
236
646
|
const message: OpenAIChatMessage = {
|
|
237
647
|
content: '' as any, // explicitly set as empty string
|
|
@@ -361,6 +771,7 @@ describe('google contextBuilders', () => {
|
|
|
361
771
|
args: { location: 'London', unit: 'celsius' },
|
|
362
772
|
name: 'get_current_weather',
|
|
363
773
|
},
|
|
774
|
+
thoughtSignature: GEMINI_MAGIC_THOUGHT_SIGNATURE,
|
|
364
775
|
},
|
|
365
776
|
],
|
|
366
777
|
role: 'model',
|
|
@@ -410,6 +821,74 @@ describe('google contextBuilders', () => {
|
|
|
410
821
|
{ parts: [{ text: 'Hi' }], role: 'model' },
|
|
411
822
|
]);
|
|
412
823
|
});
|
|
824
|
+
|
|
825
|
+
it('should correctly convert full conversation with thoughtSignature', async () => {
|
|
826
|
+
const messages: OpenAIChatMessage[] = [
|
|
827
|
+
{ content: 'system prompt', role: 'system' },
|
|
828
|
+
{ content: 'LobeChat 最新版本', role: 'user' },
|
|
829
|
+
{
|
|
830
|
+
content: '',
|
|
831
|
+
role: 'assistant',
|
|
832
|
+
tool_calls: [
|
|
833
|
+
{
|
|
834
|
+
function: {
|
|
835
|
+
arguments: JSON.stringify({
|
|
836
|
+
language: ['JSON'],
|
|
837
|
+
path: 'package.json',
|
|
838
|
+
query: '"version":',
|
|
839
|
+
repo: 'lobehub/lobe-chat',
|
|
840
|
+
}),
|
|
841
|
+
name: 'grep____searchGitHub____mcp',
|
|
842
|
+
},
|
|
843
|
+
id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
|
|
844
|
+
thoughtSignature: 'test-signature',
|
|
845
|
+
type: 'function',
|
|
846
|
+
},
|
|
847
|
+
],
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
content: '',
|
|
851
|
+
name: 'grep____searchGitHub____mcp',
|
|
852
|
+
role: 'tool',
|
|
853
|
+
tool_call_id: 'grep____searchGitHub____mcp_0_6RnOMTF0',
|
|
854
|
+
},
|
|
855
|
+
];
|
|
856
|
+
|
|
857
|
+
const contents = await buildGoogleMessages(messages);
|
|
858
|
+
|
|
859
|
+
expect(contents).toEqual([
|
|
860
|
+
{ parts: [{ text: 'system prompt' }], role: 'user' },
|
|
861
|
+
{ parts: [{ text: 'LobeChat 最新版本' }], role: 'user' },
|
|
862
|
+
{
|
|
863
|
+
parts: [
|
|
864
|
+
{
|
|
865
|
+
functionCall: {
|
|
866
|
+
args: {
|
|
867
|
+
language: ['JSON'],
|
|
868
|
+
path: 'package.json',
|
|
869
|
+
query: '"version":',
|
|
870
|
+
repo: 'lobehub/lobe-chat',
|
|
871
|
+
},
|
|
872
|
+
name: 'grep____searchGitHub____mcp',
|
|
873
|
+
},
|
|
874
|
+
thoughtSignature: 'test-signature',
|
|
875
|
+
},
|
|
876
|
+
],
|
|
877
|
+
role: 'model',
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
parts: [
|
|
881
|
+
{
|
|
882
|
+
functionResponse: {
|
|
883
|
+
name: 'grep____searchGitHub____mcp',
|
|
884
|
+
response: { result: '' },
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
],
|
|
888
|
+
role: 'user',
|
|
889
|
+
},
|
|
890
|
+
]);
|
|
891
|
+
});
|
|
413
892
|
});
|
|
414
893
|
|
|
415
894
|
describe('buildGoogleTool', () => {
|
|
@@ -11,6 +11,12 @@ import { ChatCompletionTool, OpenAIChatMessage, UserMessageContentPart } from '.
|
|
|
11
11
|
import { safeParseJSON } from '../../utils/safeParseJSON';
|
|
12
12
|
import { parseDataUri } from '../../utils/uriParser';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Magic thoughtSignature
|
|
16
|
+
* @see https://ai.google.dev/gemini-api/docs/thought-signatures#model-behavior:~:text=context_engineering_is_the_way_to_go
|
|
17
|
+
*/
|
|
18
|
+
export const GEMINI_MAGIC_THOUGHT_SIGNATURE = 'context_engineering_is_the_way_to_go';
|
|
19
|
+
|
|
14
20
|
/**
|
|
15
21
|
* Convert OpenAI content part to Google Part format
|
|
16
22
|
*/
|
|
@@ -95,6 +101,7 @@ export const buildGoogleMessage = async (
|
|
|
95
101
|
args: safeParseJSON(tool.function.arguments)!,
|
|
96
102
|
name: tool.function.name,
|
|
97
103
|
},
|
|
104
|
+
thoughtSignature: tool.thoughtSignature,
|
|
98
105
|
})),
|
|
99
106
|
role: 'model',
|
|
100
107
|
};
|
|
@@ -155,7 +162,43 @@ export const buildGoogleMessages = async (messages: OpenAIChatMessage[]): Promis
|
|
|
155
162
|
const contents = await Promise.all(pools);
|
|
156
163
|
|
|
157
164
|
// Filter out empty messages: contents.parts must not be empty.
|
|
158
|
-
|
|
165
|
+
const filteredContents = contents.filter(
|
|
166
|
+
(content: Content) => content.parts && content.parts.length > 0,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Check if the last message is a tool message
|
|
170
|
+
const lastMessage = messages.at(-1);
|
|
171
|
+
const shouldAddMagicSignature = lastMessage?.role === 'tool';
|
|
172
|
+
|
|
173
|
+
if (shouldAddMagicSignature) {
|
|
174
|
+
// Find the last user message index in filtered contents
|
|
175
|
+
let lastUserIndex = -1;
|
|
176
|
+
for (let i = filteredContents.length - 1; i >= 0; i--) {
|
|
177
|
+
if (filteredContents[i].role === 'user') {
|
|
178
|
+
// Skip if it's a functionResponse (tool result)
|
|
179
|
+
const hasFunctionResponse = filteredContents[i].parts?.some((p) => p.functionResponse);
|
|
180
|
+
if (!hasFunctionResponse) {
|
|
181
|
+
lastUserIndex = i;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Add magic signature to all function calls after last user message that don't have thoughtSignature
|
|
188
|
+
for (let i = lastUserIndex + 1; i < filteredContents.length; i++) {
|
|
189
|
+
const content = filteredContents[i];
|
|
190
|
+
if (content.role === 'model' && content.parts) {
|
|
191
|
+
for (const part of content.parts) {
|
|
192
|
+
if (part.functionCall && !part.thoughtSignature) {
|
|
193
|
+
// Only add magic signature if thoughtSignature doesn't exist
|
|
194
|
+
part.thoughtSignature = GEMINI_MAGIC_THOUGHT_SIGNATURE;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return filteredContents;
|
|
159
202
|
};
|
|
160
203
|
|
|
161
204
|
/**
|