@lobehub/chat 1.96.12 → 1.96.14
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/docs/self-hosting/advanced/online-search.mdx +21 -21
- package/docs/self-hosting/advanced/online-search.zh-CN.mdx +29 -29
- package/locales/ar/models.json +6 -0
- package/locales/bg-BG/models.json +6 -0
- package/locales/de-DE/models.json +6 -0
- package/locales/en-US/models.json +6 -0
- package/locales/es-ES/models.json +6 -0
- package/locales/fa-IR/models.json +6 -0
- package/locales/fr-FR/models.json +6 -0
- package/locales/it-IT/models.json +6 -0
- package/locales/ja-JP/models.json +6 -0
- package/locales/ko-KR/models.json +6 -0
- package/locales/nl-NL/models.json +6 -0
- package/locales/pl-PL/models.json +6 -0
- package/locales/pt-BR/models.json +6 -0
- package/locales/ru-RU/models.json +6 -0
- package/locales/tr-TR/models.json +6 -0
- package/locales/vi-VN/models.json +6 -0
- package/locales/zh-CN/models.json +6 -0
- package/locales/zh-TW/models.json +6 -0
- package/package.json +2 -3
- package/src/libs/model-runtime/google/index.test.ts +93 -36
- package/src/libs/model-runtime/google/index.ts +50 -64
- package/src/libs/model-runtime/utils/streams/google-ai.test.ts +416 -17
- package/src/libs/model-runtime/utils/streams/google-ai.ts +17 -17
- package/src/libs/model-runtime/utils/streams/vertex-ai.test.ts +129 -0
- package/src/libs/model-runtime/utils/streams/vertex-ai.ts +16 -16
- package/src/libs/model-runtime/vertexai/index.ts +9 -3
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { GenerateContentResponse } from '@google/genai';
|
2
2
|
import { describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
4
|
import * as uuidModule from '@/utils/uuid';
|
@@ -11,10 +11,9 @@ describe('GoogleGenerativeAIStream', () => {
|
|
11
11
|
|
12
12
|
const mockGenerateContentResponse = (text: string, functionCalls?: any[]) =>
|
13
13
|
({
|
14
|
-
text:
|
15
|
-
|
16
|
-
|
17
|
-
}) as EnhancedGenerateContentResponse;
|
14
|
+
text: text,
|
15
|
+
functionCalls: functionCalls,
|
16
|
+
}) as unknown as GenerateContentResponse;
|
18
17
|
|
19
18
|
const mockGoogleStream = new ReadableStream({
|
20
19
|
start(controller) {
|
@@ -114,12 +113,6 @@ describe('GoogleGenerativeAIStream', () => {
|
|
114
113
|
},
|
115
114
|
modelVersion: 'gemini-2.0-flash-exp',
|
116
115
|
};
|
117
|
-
const mockGenerateContentResponse = (text: string, functionCalls?: any[]) =>
|
118
|
-
({
|
119
|
-
text: () => text,
|
120
|
-
functionCall: () => functionCalls?.[0],
|
121
|
-
functionCalls: () => functionCalls,
|
122
|
-
}) as EnhancedGenerateContentResponse;
|
123
116
|
|
124
117
|
const mockGoogleStream = new ReadableStream({
|
125
118
|
start(controller) {
|
@@ -209,7 +202,7 @@ describe('GoogleGenerativeAIStream', () => {
|
|
209
202
|
],
|
210
203
|
},
|
211
204
|
],
|
212
|
-
text:
|
205
|
+
text: '234',
|
213
206
|
usageMetadata: {
|
214
207
|
promptTokenCount: 20,
|
215
208
|
totalTokenCount: 20,
|
@@ -218,7 +211,7 @@ describe('GoogleGenerativeAIStream', () => {
|
|
218
211
|
modelVersion: 'gemini-2.0-flash-exp-image-generation',
|
219
212
|
},
|
220
213
|
{
|
221
|
-
text:
|
214
|
+
text: '567890\n',
|
222
215
|
candidates: [
|
223
216
|
{
|
224
217
|
content: { parts: [{ text: '567890\n' }], role: 'model' },
|
@@ -299,7 +292,7 @@ describe('GoogleGenerativeAIStream', () => {
|
|
299
292
|
],
|
300
293
|
},
|
301
294
|
],
|
302
|
-
text:
|
295
|
+
text: '234',
|
303
296
|
usageMetadata: {
|
304
297
|
promptTokenCount: 19,
|
305
298
|
candidatesTokenCount: 3,
|
@@ -307,10 +300,10 @@ describe('GoogleGenerativeAIStream', () => {
|
|
307
300
|
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 19 }],
|
308
301
|
thoughtsTokenCount: 100,
|
309
302
|
},
|
310
|
-
modelVersion: 'gemini-2.
|
303
|
+
modelVersion: 'gemini-2.5-flash-preview-04-17',
|
311
304
|
},
|
312
305
|
{
|
313
|
-
text:
|
306
|
+
text: '567890\n',
|
314
307
|
candidates: [
|
315
308
|
{
|
316
309
|
content: { parts: [{ text: '567890\n' }], role: 'model' },
|
@@ -331,7 +324,7 @@ describe('GoogleGenerativeAIStream', () => {
|
|
331
324
|
candidatesTokensDetails: [{ modality: 'TEXT', tokenCount: 11 }],
|
332
325
|
thoughtsTokenCount: 100,
|
333
326
|
},
|
334
|
-
modelVersion: 'gemini-2.
|
327
|
+
modelVersion: 'gemini-2.5-flash-preview-04-17',
|
335
328
|
},
|
336
329
|
];
|
337
330
|
|
@@ -375,4 +368,410 @@ describe('GoogleGenerativeAIStream', () => {
|
|
375
368
|
].map((i) => i + '\n'),
|
376
369
|
);
|
377
370
|
});
|
371
|
+
|
372
|
+
it('should handle thought candidate part', async () => {
|
373
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
374
|
+
|
375
|
+
const data = [
|
376
|
+
{
|
377
|
+
candidates: [
|
378
|
+
{
|
379
|
+
content: {
|
380
|
+
parts: [{ text: '**Understanding the Conditional Logic**\n\n', thought: true }],
|
381
|
+
role: 'model',
|
382
|
+
},
|
383
|
+
index: 0,
|
384
|
+
},
|
385
|
+
],
|
386
|
+
text: '**Understanding the Conditional Logic**\n\n',
|
387
|
+
usageMetadata: {
|
388
|
+
promptTokenCount: 38,
|
389
|
+
candidatesTokenCount: 7,
|
390
|
+
totalTokenCount: 301,
|
391
|
+
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 38 }],
|
392
|
+
thoughtsTokenCount: 256,
|
393
|
+
},
|
394
|
+
modelVersion: 'models/gemini-2.5-flash-preview-04-17',
|
395
|
+
},
|
396
|
+
{
|
397
|
+
candidates: [
|
398
|
+
{
|
399
|
+
content: {
|
400
|
+
parts: [{ text: '**Finalizing Interpretation**\n\n', thought: true }],
|
401
|
+
role: 'model',
|
402
|
+
},
|
403
|
+
index: 0,
|
404
|
+
},
|
405
|
+
],
|
406
|
+
text: '**Finalizing Interpretation**\n\n',
|
407
|
+
usageMetadata: {
|
408
|
+
promptTokenCount: 38,
|
409
|
+
candidatesTokenCount: 13,
|
410
|
+
totalTokenCount: 355,
|
411
|
+
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 38 }],
|
412
|
+
thoughtsTokenCount: 304,
|
413
|
+
},
|
414
|
+
modelVersion: 'models/gemini-2.5-flash-preview-04-17',
|
415
|
+
},
|
416
|
+
{
|
417
|
+
candidates: [
|
418
|
+
{
|
419
|
+
content: {
|
420
|
+
parts: [{ text: '简单来说,' }],
|
421
|
+
role: 'model',
|
422
|
+
},
|
423
|
+
index: 0,
|
424
|
+
},
|
425
|
+
],
|
426
|
+
text: '简单来说,',
|
427
|
+
usageMetadata: {
|
428
|
+
promptTokenCount: 38,
|
429
|
+
candidatesTokenCount: 16,
|
430
|
+
totalTokenCount: 358,
|
431
|
+
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 38 }],
|
432
|
+
thoughtsTokenCount: 304,
|
433
|
+
},
|
434
|
+
modelVersion: 'models/gemini-2.5-flash-preview-04-17',
|
435
|
+
},
|
436
|
+
{
|
437
|
+
candidates: [
|
438
|
+
{
|
439
|
+
content: { parts: [{ text: '文本内容。' }], role: 'model' },
|
440
|
+
finishReason: 'STOP',
|
441
|
+
index: 0,
|
442
|
+
},
|
443
|
+
],
|
444
|
+
text: '文本内容。',
|
445
|
+
usageMetadata: {
|
446
|
+
promptTokenCount: 38,
|
447
|
+
candidatesTokenCount: 19,
|
448
|
+
totalTokenCount: 361,
|
449
|
+
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 38 }],
|
450
|
+
thoughtsTokenCount: 304,
|
451
|
+
},
|
452
|
+
modelVersion: 'models/gemini-2.5-flash-preview-04-17',
|
453
|
+
},
|
454
|
+
];
|
455
|
+
|
456
|
+
const mockGoogleStream = new ReadableStream({
|
457
|
+
start(controller) {
|
458
|
+
data.forEach((item) => {
|
459
|
+
controller.enqueue(item);
|
460
|
+
});
|
461
|
+
|
462
|
+
controller.close();
|
463
|
+
},
|
464
|
+
});
|
465
|
+
|
466
|
+
const protocolStream = GoogleGenerativeAIStream(mockGoogleStream);
|
467
|
+
|
468
|
+
const decoder = new TextDecoder();
|
469
|
+
const chunks = [];
|
470
|
+
|
471
|
+
// @ts-ignore
|
472
|
+
for await (const chunk of protocolStream) {
|
473
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
474
|
+
}
|
475
|
+
|
476
|
+
expect(chunks).toEqual(
|
477
|
+
[
|
478
|
+
'id: chat_1',
|
479
|
+
'event: reasoning',
|
480
|
+
'data: "**Understanding the Conditional Logic**\\n\\n"\n',
|
481
|
+
|
482
|
+
'id: chat_1',
|
483
|
+
'event: reasoning',
|
484
|
+
`data: "**Finalizing Interpretation**\\n\\n"\n`,
|
485
|
+
|
486
|
+
'id: chat_1',
|
487
|
+
'event: text',
|
488
|
+
`data: "简单来说,"\n`,
|
489
|
+
|
490
|
+
'id: chat_1',
|
491
|
+
'event: text',
|
492
|
+
`data: "文本内容。"\n`,
|
493
|
+
// stop
|
494
|
+
'id: chat_1',
|
495
|
+
'event: stop',
|
496
|
+
`data: "STOP"\n`,
|
497
|
+
// usage
|
498
|
+
'id: chat_1',
|
499
|
+
'event: usage',
|
500
|
+
`data: {"inputTextTokens":38,"outputReasoningTokens":304,"outputTextTokens":19,"totalInputTokens":38,"totalOutputTokens":323,"totalTokens":361}\n`,
|
501
|
+
].map((i) => i + '\n'),
|
502
|
+
);
|
503
|
+
});
|
504
|
+
|
505
|
+
it('should return undefined data without text', async () => {
|
506
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
507
|
+
|
508
|
+
const data = [
|
509
|
+
{
|
510
|
+
candidates: [
|
511
|
+
{
|
512
|
+
content: { parts: [{ text: '234' }], role: 'model' },
|
513
|
+
safetyRatings: [
|
514
|
+
{ category: 'HARM_CATEGORY_HATE_SPEECH', probability: 'NEGLIGIBLE' },
|
515
|
+
{ category: 'HARM_CATEGORY_DANGEROUS_CONTENT', probability: 'NEGLIGIBLE' },
|
516
|
+
{ category: 'HARM_CATEGORY_HARASSMENT', probability: 'NEGLIGIBLE' },
|
517
|
+
{ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', probability: 'NEGLIGIBLE' },
|
518
|
+
],
|
519
|
+
},
|
520
|
+
],
|
521
|
+
text: '234',
|
522
|
+
usageMetadata: {
|
523
|
+
promptTokenCount: 19,
|
524
|
+
candidatesTokenCount: 3,
|
525
|
+
totalTokenCount: 122,
|
526
|
+
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 19 }],
|
527
|
+
thoughtsTokenCount: 100,
|
528
|
+
},
|
529
|
+
modelVersion: 'gemini-2.5-flash-preview-04-17',
|
530
|
+
},
|
531
|
+
{
|
532
|
+
text: '',
|
533
|
+
candidates: [
|
534
|
+
{
|
535
|
+
content: { parts: [{ text: '' }], role: 'model' },
|
536
|
+
safetyRatings: [
|
537
|
+
{ category: 'HARM_CATEGORY_HATE_SPEECH', probability: 'NEGLIGIBLE' },
|
538
|
+
{ category: 'HARM_CATEGORY_DANGEROUS_CONTENT', probability: 'NEGLIGIBLE' },
|
539
|
+
{ category: 'HARM_CATEGORY_HARASSMENT', probability: 'NEGLIGIBLE' },
|
540
|
+
{ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', probability: 'NEGLIGIBLE' },
|
541
|
+
],
|
542
|
+
},
|
543
|
+
],
|
544
|
+
usageMetadata: {
|
545
|
+
promptTokenCount: 19,
|
546
|
+
candidatesTokenCount: 3,
|
547
|
+
totalTokenCount: 122,
|
548
|
+
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 19 }],
|
549
|
+
candidatesTokensDetails: [{ modality: 'TEXT', tokenCount: 3 }],
|
550
|
+
thoughtsTokenCount: 100,
|
551
|
+
},
|
552
|
+
modelVersion: 'gemini-2.5-flash-preview-04-17',
|
553
|
+
},
|
554
|
+
{
|
555
|
+
text: '567890\n',
|
556
|
+
candidates: [
|
557
|
+
{
|
558
|
+
content: { parts: [{ text: '567890\n' }], role: 'model' },
|
559
|
+
finishReason: 'STOP',
|
560
|
+
safetyRatings: [
|
561
|
+
{ category: 'HARM_CATEGORY_HATE_SPEECH', probability: 'NEGLIGIBLE' },
|
562
|
+
{ category: 'HARM_CATEGORY_DANGEROUS_CONTENT', probability: 'NEGLIGIBLE' },
|
563
|
+
{ category: 'HARM_CATEGORY_HARASSMENT', probability: 'NEGLIGIBLE' },
|
564
|
+
{ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', probability: 'NEGLIGIBLE' },
|
565
|
+
],
|
566
|
+
},
|
567
|
+
],
|
568
|
+
usageMetadata: {
|
569
|
+
promptTokenCount: 19,
|
570
|
+
candidatesTokenCount: 11,
|
571
|
+
totalTokenCount: 131,
|
572
|
+
promptTokensDetails: [{ modality: 'TEXT', tokenCount: 19 }],
|
573
|
+
candidatesTokensDetails: [{ modality: 'TEXT', tokenCount: 11 }],
|
574
|
+
thoughtsTokenCount: 100,
|
575
|
+
},
|
576
|
+
modelVersion: 'gemini-2.5-flash-preview-04-17',
|
577
|
+
},
|
578
|
+
];
|
579
|
+
|
580
|
+
const mockGoogleStream = new ReadableStream({
|
581
|
+
start(controller) {
|
582
|
+
data.forEach((item) => {
|
583
|
+
controller.enqueue(item);
|
584
|
+
});
|
585
|
+
|
586
|
+
controller.close();
|
587
|
+
},
|
588
|
+
});
|
589
|
+
|
590
|
+
const protocolStream = GoogleGenerativeAIStream(mockGoogleStream);
|
591
|
+
|
592
|
+
const decoder = new TextDecoder();
|
593
|
+
const chunks = [];
|
594
|
+
|
595
|
+
// @ts-ignore
|
596
|
+
for await (const chunk of protocolStream) {
|
597
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
598
|
+
}
|
599
|
+
|
600
|
+
expect(chunks).toEqual(
|
601
|
+
[
|
602
|
+
'id: chat_1',
|
603
|
+
'event: text',
|
604
|
+
'data: "234"\n',
|
605
|
+
|
606
|
+
'id: chat_1',
|
607
|
+
'event: text',
|
608
|
+
'data: ""\n',
|
609
|
+
|
610
|
+
'id: chat_1',
|
611
|
+
'event: text',
|
612
|
+
`data: "567890\\n"\n`,
|
613
|
+
// stop
|
614
|
+
'id: chat_1',
|
615
|
+
'event: stop',
|
616
|
+
`data: "STOP"\n`,
|
617
|
+
// usage
|
618
|
+
'id: chat_1',
|
619
|
+
'event: usage',
|
620
|
+
`data: {"inputTextTokens":19,"outputReasoningTokens":100,"outputTextTokens":11,"totalInputTokens":19,"totalOutputTokens":111,"totalTokens":131}\n`,
|
621
|
+
].map((i) => i + '\n'),
|
622
|
+
);
|
623
|
+
});
|
624
|
+
|
625
|
+
it('should handle groundingMetadata', async () => {
|
626
|
+
vi.spyOn(uuidModule, 'nanoid').mockReturnValueOnce('1');
|
627
|
+
|
628
|
+
const data = [
|
629
|
+
{
|
630
|
+
text: '123',
|
631
|
+
candidates: [
|
632
|
+
{
|
633
|
+
content: {
|
634
|
+
parts: [
|
635
|
+
{
|
636
|
+
text: '123',
|
637
|
+
},
|
638
|
+
],
|
639
|
+
role: 'model',
|
640
|
+
},
|
641
|
+
index: 0,
|
642
|
+
groundingMetadata: {},
|
643
|
+
},
|
644
|
+
],
|
645
|
+
usageMetadata: {
|
646
|
+
promptTokenCount: 9,
|
647
|
+
candidatesTokenCount: 18,
|
648
|
+
totalTokenCount: 27,
|
649
|
+
promptTokensDetails: [
|
650
|
+
{
|
651
|
+
modality: 'TEXT',
|
652
|
+
tokenCount: 9,
|
653
|
+
},
|
654
|
+
],
|
655
|
+
},
|
656
|
+
modelVersion: 'models/gemini-2.5-flash-preview-04-17',
|
657
|
+
},
|
658
|
+
{
|
659
|
+
text: '45678',
|
660
|
+
candidates: [
|
661
|
+
{
|
662
|
+
content: {
|
663
|
+
parts: [
|
664
|
+
{
|
665
|
+
text: '45678',
|
666
|
+
},
|
667
|
+
],
|
668
|
+
role: 'model',
|
669
|
+
},
|
670
|
+
finishReason: 'STOP',
|
671
|
+
index: 0,
|
672
|
+
groundingMetadata: {
|
673
|
+
searchEntryPoint: {
|
674
|
+
renderedContent: 'content\n',
|
675
|
+
},
|
676
|
+
groundingChunks: [
|
677
|
+
{
|
678
|
+
web: {
|
679
|
+
uri: 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1234545',
|
680
|
+
title: 'npmjs.com',
|
681
|
+
},
|
682
|
+
},
|
683
|
+
{
|
684
|
+
web: {
|
685
|
+
uri: 'https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXE9288334',
|
686
|
+
title: 'google.dev',
|
687
|
+
},
|
688
|
+
},
|
689
|
+
],
|
690
|
+
groundingSupports: [
|
691
|
+
{
|
692
|
+
segment: {
|
693
|
+
startIndex: 63,
|
694
|
+
endIndex: 67,
|
695
|
+
text: '1。',
|
696
|
+
},
|
697
|
+
groundingChunkIndices: [0],
|
698
|
+
confidenceScores: [1],
|
699
|
+
},
|
700
|
+
{
|
701
|
+
segment: {
|
702
|
+
startIndex: 69,
|
703
|
+
endIndex: 187,
|
704
|
+
text: 'SDK。',
|
705
|
+
},
|
706
|
+
groundingChunkIndices: [1],
|
707
|
+
confidenceScores: [1],
|
708
|
+
},
|
709
|
+
],
|
710
|
+
webSearchQueries: ['sdk latest version'],
|
711
|
+
},
|
712
|
+
},
|
713
|
+
],
|
714
|
+
usageMetadata: {
|
715
|
+
promptTokenCount: 9,
|
716
|
+
candidatesTokenCount: 122,
|
717
|
+
totalTokenCount: 131,
|
718
|
+
promptTokensDetails: [
|
719
|
+
{
|
720
|
+
modality: 'TEXT',
|
721
|
+
tokenCount: 9,
|
722
|
+
},
|
723
|
+
],
|
724
|
+
},
|
725
|
+
modelVersion: 'models/gemini-2.5-flash-preview-04-17',
|
726
|
+
},
|
727
|
+
];
|
728
|
+
|
729
|
+
const mockGoogleStream = new ReadableStream({
|
730
|
+
start(controller) {
|
731
|
+
data.forEach((item) => {
|
732
|
+
controller.enqueue(item);
|
733
|
+
});
|
734
|
+
|
735
|
+
controller.close();
|
736
|
+
},
|
737
|
+
});
|
738
|
+
|
739
|
+
const protocolStream = GoogleGenerativeAIStream(mockGoogleStream);
|
740
|
+
|
741
|
+
const decoder = new TextDecoder();
|
742
|
+
const chunks = [];
|
743
|
+
|
744
|
+
// @ts-ignore
|
745
|
+
for await (const chunk of protocolStream) {
|
746
|
+
chunks.push(decoder.decode(chunk, { stream: true }));
|
747
|
+
}
|
748
|
+
|
749
|
+
expect(chunks).toEqual(
|
750
|
+
[
|
751
|
+
'id: chat_1',
|
752
|
+
'event: text',
|
753
|
+
'data: "123"\n',
|
754
|
+
|
755
|
+
'id: chat_1',
|
756
|
+
'event: grounding',
|
757
|
+
'data: {}\n',
|
758
|
+
|
759
|
+
'id: chat_1',
|
760
|
+
'event: text',
|
761
|
+
'data: "45678"\n',
|
762
|
+
|
763
|
+
'id: chat_1',
|
764
|
+
'event: grounding',
|
765
|
+
`data: {\"citations\":[{\"favicon\":\"npmjs.com\",\"title\":\"npmjs.com\",\"url\":\"https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXG1234545\"},{\"favicon\":\"google.dev\",\"title\":\"google.dev\",\"url\":\"https://vertexaisearch.cloud.google.com/grounding-api-redirect/AbF9wXE9288334\"}],\"searchQueries\":[\"sdk latest version\"]}\n`,
|
766
|
+
// stop
|
767
|
+
'id: chat_1',
|
768
|
+
'event: stop',
|
769
|
+
`data: "STOP"\n`,
|
770
|
+
// usage
|
771
|
+
'id: chat_1',
|
772
|
+
'event: usage',
|
773
|
+
`data: {"inputTextTokens":9,"outputTextTokens":122,"totalInputTokens":9,"totalOutputTokens":122,"totalTokens":131}\n`,
|
774
|
+
].map((i) => i + '\n'),
|
775
|
+
);
|
776
|
+
});
|
378
777
|
});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { GenerateContentResponse } from '@google/genai';
|
2
2
|
|
3
3
|
import { ModelTokensUsage } from '@/types/message';
|
4
4
|
import { GroundingSearch } from '@/types/search';
|
@@ -16,7 +16,7 @@ import {
|
|
16
16
|
} from './protocol';
|
17
17
|
|
18
18
|
const transformGoogleGenerativeAIStream = (
|
19
|
-
chunk:
|
19
|
+
chunk: GenerateContentResponse,
|
20
20
|
context: StreamContext,
|
21
21
|
): StreamProtocolChunk | StreamProtocolChunk[] => {
|
22
22
|
// maybe need another structure to add support for multiple choices
|
@@ -24,22 +24,22 @@ const transformGoogleGenerativeAIStream = (
|
|
24
24
|
const usage = chunk.usageMetadata;
|
25
25
|
const usageChunks: StreamProtocolChunk[] = [];
|
26
26
|
if (candidate?.finishReason && usage) {
|
27
|
-
|
28
|
-
const
|
27
|
+
// totalTokenCount = promptTokenCount + candidatesTokenCount + thoughtsTokenCount
|
28
|
+
const reasoningTokens = usage.thoughtsTokenCount;
|
29
|
+
const outputTextTokens = usage.candidatesTokenCount ?? 0;
|
30
|
+
const totalOutputTokens = outputTextTokens + (reasoningTokens ?? 0);
|
29
31
|
|
30
32
|
usageChunks.push(
|
31
33
|
{ data: candidate.finishReason, id: context?.id, type: 'stop' },
|
32
34
|
{
|
33
35
|
data: {
|
34
36
|
// TODO: Google SDK 0.24.0 don't have promptTokensDetails types
|
35
|
-
inputImageTokens:
|
36
|
-
|
37
|
-
)
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
outputReasoningTokens,
|
42
|
-
outputTextTokens: totalOutputTokens - (outputReasoningTokens ?? 0),
|
37
|
+
inputImageTokens: usage.promptTokensDetails?.find((i: any) => i.modality === 'IMAGE')
|
38
|
+
?.tokenCount,
|
39
|
+
inputTextTokens: usage.promptTokensDetails?.find((i: any) => i.modality === 'TEXT')
|
40
|
+
?.tokenCount,
|
41
|
+
outputReasoningTokens: reasoningTokens,
|
42
|
+
outputTextTokens,
|
43
43
|
totalInputTokens: usage.promptTokenCount,
|
44
44
|
totalOutputTokens,
|
45
45
|
totalTokens: usage.totalTokenCount,
|
@@ -50,7 +50,7 @@ const transformGoogleGenerativeAIStream = (
|
|
50
50
|
);
|
51
51
|
}
|
52
52
|
|
53
|
-
const functionCalls = chunk.functionCalls
|
53
|
+
const functionCalls = chunk.functionCalls;
|
54
54
|
|
55
55
|
if (functionCalls) {
|
56
56
|
return [
|
@@ -73,11 +73,11 @@ const transformGoogleGenerativeAIStream = (
|
|
73
73
|
];
|
74
74
|
}
|
75
75
|
|
76
|
-
const text = chunk.text
|
76
|
+
const text = chunk.text;
|
77
77
|
|
78
78
|
if (candidate) {
|
79
79
|
// 首先检查是否为 reasoning 内容 (thought: true)
|
80
|
-
if (Array.isArray(candidate.content
|
80
|
+
if (Array.isArray(candidate.content?.parts) && candidate.content.parts.length > 0) {
|
81
81
|
for (const part of candidate.content.parts) {
|
82
82
|
if (part && part.text && (part as any).thought === true) {
|
83
83
|
return { data: part.text, id: context.id, type: 'reasoning' };
|
@@ -122,7 +122,7 @@ const transformGoogleGenerativeAIStream = (
|
|
122
122
|
if (!!text?.trim()) return { data: text, id: context?.id, type: 'text' };
|
123
123
|
|
124
124
|
// streaming the image
|
125
|
-
if (Array.isArray(candidate.content
|
125
|
+
if (Array.isArray(candidate.content?.parts) && candidate.content.parts.length > 0) {
|
126
126
|
const part = candidate.content.parts[0];
|
127
127
|
|
128
128
|
if (part && part.inlineData && part.inlineData.data && part.inlineData.mimeType) {
|
@@ -148,7 +148,7 @@ export interface GoogleAIStreamOptions {
|
|
148
148
|
}
|
149
149
|
|
150
150
|
export const GoogleGenerativeAIStream = (
|
151
|
-
rawStream: ReadableStream<
|
151
|
+
rawStream: ReadableStream<GenerateContentResponse>,
|
152
152
|
{ callbacks, inputStartAt }: GoogleAIStreamOptions = {},
|
153
153
|
) => {
|
154
154
|
const streamStack: StreamContext = { id: 'chat_' + nanoid() };
|