17
17
18
18
import {
19
19
aggregateResponses ,
20
+ deleteEmptyTextParts ,
20
21
getResponseStream ,
21
22
processStream
22
23
} from './stream-reader' ;
@@ -33,6 +34,7 @@ import {
33
34
GenerateContentResponse ,
34
35
HarmCategory ,
35
36
HarmProbability ,
37
+ Part ,
36
38
SafetyRating
37
39
} from '../types' ;
38
40
@@ -220,6 +222,23 @@ describe('processStream', () => {
220
222
}
221
223
expect ( foundCitationMetadata ) . to . be . true ;
222
224
} ) ;
225
+ it ( 'removes empty text parts' , async ( ) => {
226
+ const fakeResponse = getMockResponseStreaming (
227
+ 'streaming-success-empty-text-part.txt'
228
+ ) ;
229
+ const result = processStream ( fakeResponse as Response ) ;
230
+ const aggregatedResponse = await result . response ;
231
+ expect ( aggregatedResponse . text ( ) ) . to . equal ( '1' ) ;
232
+ expect ( aggregatedResponse . candidates ?. length ) . to . equal ( 1 ) ;
233
+ expect ( aggregatedResponse . candidates ?. [ 0 ] . content . parts . length ) . to . equal ( 1 ) ;
234
+
235
+ // The chunk with the empty text part will still go through the stream
236
+ let numChunks = 0 ;
237
+ for await ( const _ of result . stream ) {
238
+ numChunks ++ ;
239
+ }
240
+ expect ( numChunks ) . to . equal ( 2 ) ;
241
+ } ) ;
223
242
} ) ;
224
243
225
244
describe ( 'aggregateResponses' , ( ) => {
@@ -404,3 +423,104 @@ describe('aggregateResponses', () => {
404
423
} ) ;
405
424
} ) ;
406
425
} ) ;
426
+
427
+ describe ( 'deleteEmptyTextParts' , ( ) => {
428
+ it ( 'removes empty text parts from a single candidate' , ( ) => {
429
+ const parts : Part [ ] = [
430
+ {
431
+ text : ''
432
+ } ,
433
+ {
434
+ text : 'foo'
435
+ }
436
+ ] ;
437
+ const generateContentResponse : GenerateContentResponse = {
438
+ candidates : [
439
+ {
440
+ index : 0 ,
441
+ content : {
442
+ role : 'model' ,
443
+ parts
444
+ }
445
+ }
446
+ ]
447
+ } ;
448
+
449
+ deleteEmptyTextParts ( generateContentResponse ) ;
450
+ expect ( generateContentResponse . candidates ?. [ 0 ] . content . parts ) . to . deep . equal (
451
+ [
452
+ {
453
+ text : 'foo'
454
+ }
455
+ ]
456
+ ) ;
457
+ } ) ;
458
+ it ( 'removes empty text parts from all candidates' , ( ) => {
459
+ const parts : Part [ ] = [
460
+ {
461
+ text : ''
462
+ } ,
463
+ {
464
+ text : 'foo'
465
+ }
466
+ ] ;
467
+ const generateContentResponse : GenerateContentResponse = {
468
+ candidates : [
469
+ {
470
+ index : 0 ,
471
+ content : {
472
+ role : 'model' ,
473
+ parts
474
+ }
475
+ } ,
476
+ {
477
+ index : 1 ,
478
+ content : {
479
+ role : 'model' ,
480
+ parts
481
+ }
482
+ }
483
+ ]
484
+ } ;
485
+
486
+ deleteEmptyTextParts ( generateContentResponse ) ;
487
+ expect ( generateContentResponse . candidates ?. [ 0 ] . content . parts ) . to . deep . equal (
488
+ [
489
+ {
490
+ text : 'foo'
491
+ }
492
+ ]
493
+ ) ;
494
+ expect ( generateContentResponse . candidates ?. [ 1 ] . content . parts ) . to . deep . equal (
495
+ [
496
+ {
497
+ text : 'foo'
498
+ }
499
+ ]
500
+ ) ;
501
+ } ) ;
502
+ it ( 'does not remove candidate even if all parts are removed' , ( ) => {
503
+ const parts : Part [ ] = [
504
+ {
505
+ text : ''
506
+ }
507
+ ] ;
508
+ const generateContentResponse : GenerateContentResponse = {
509
+ candidates : [
510
+ {
511
+ index : 0 ,
512
+ content : {
513
+ role : 'model' ,
514
+ parts
515
+ }
516
+ }
517
+ ]
518
+ } ;
519
+
520
+ deleteEmptyTextParts ( generateContentResponse ) ;
521
+ expect ( generateContentResponse . candidates ?. length ) . to . equal ( 1 ) ;
522
+ expect ( generateContentResponse . candidates ?. [ 0 ] . content . parts ) . to . deep . equal (
523
+ [ ]
524
+ ) ;
525
+ } ) ;
526
+ } ) ;
0 commit comments