@@ -736,5 +736,160 @@ describe('BedrockRuntime', () => {
736736 expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_RESPONSE_FINISH_REASONS ] ) . toEqual ( [ 'stop' ] ) ;
737737 expect ( invokeModelSpan . kind ) . toBe ( SpanKind . CLIENT ) ;
738738 } ) ;
739+
740+ describe ( 'Response Body Type Handling' , ( ) => {
741+ it ( 'handles string response body correctly' , async ( ) => {
742+ const modelId : string = 'anthropic.claude-3-5-sonnet-20240620-v1:0' ;
743+ const mockRequestBody : string = JSON . stringify ( {
744+ anthropic_version : 'bedrock-2023-05-31' ,
745+ max_tokens : 1000 ,
746+ messages : [ { role : 'user' , content : [ { type : 'text' , text : 'test' } ] } ] ,
747+ } ) ;
748+
749+ // Mock response body as already converted string
750+ const mockResponseBodyString = JSON . stringify ( {
751+ stop_reason : 'end_turn' ,
752+ usage : { input_tokens : 15 , output_tokens : 13 } ,
753+ } ) ;
754+
755+ nock ( `https://bedrock-runtime.${ region } .amazonaws.com` )
756+ . post ( `/model/${ encodeURIComponent ( modelId ) } /invoke` )
757+ . reply ( 200 , mockResponseBodyString ) ;
758+
759+ await bedrock
760+ . invokeModel ( {
761+ modelId : modelId ,
762+ body : mockRequestBody ,
763+ } )
764+ . catch ( ( err : any ) => { } ) ;
765+
766+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
767+ const invokeModelSpans : ReadableSpan [ ] = testSpans . filter ( ( s : ReadableSpan ) => {
768+ return s . name === 'BedrockRuntime.InvokeModel' ;
769+ } ) ;
770+ expect ( invokeModelSpans . length ) . toBe ( 1 ) ;
771+ const invokeModelSpan = invokeModelSpans [ 0 ] ;
772+
773+ // Verify attributes are set correctly despite body being a string
774+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_INPUT_TOKENS ] ) . toBe ( 15 ) ;
775+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_OUTPUT_TOKENS ] ) . toBe ( 13 ) ;
776+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_RESPONSE_FINISH_REASONS ] ) . toEqual ( [ 'end_turn' ] ) ;
777+ } ) ;
778+
779+ it ( 'handles Anthropic Claude response body correctly' , async ( ) => {
780+ const modelId : string = 'anthropic.claude-3-5-sonnet-20240620-v1:0' ;
781+ const mockRequestBody : string = JSON . stringify ( {
782+ anthropic_version : 'bedrock-2023-05-31' ,
783+ max_tokens : 1000 ,
784+ messages : [ { role : 'user' , content : [ { type : 'text' , text : 'test' } ] } ] ,
785+ } ) ;
786+
787+ // Mock response body - use standard object format (AWS SDK will handle type conversion)
788+ const mockResponseBodyObj = {
789+ stop_reason : 'end_turn' ,
790+ usage : { input_tokens : 20 , output_tokens : 15 } ,
791+ } ;
792+
793+ nock ( `https://bedrock-runtime.${ region } .amazonaws.com` )
794+ . post ( `/model/${ encodeURIComponent ( modelId ) } /invoke` )
795+ . reply ( 200 , mockResponseBodyObj ) ;
796+
797+ await bedrock
798+ . invokeModel ( {
799+ modelId : modelId ,
800+ body : mockRequestBody ,
801+ } )
802+ . catch ( ( err : any ) => { } ) ;
803+
804+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
805+ const invokeModelSpans : ReadableSpan [ ] = testSpans . filter ( ( s : ReadableSpan ) => {
806+ return s . name === 'BedrockRuntime.InvokeModel' ;
807+ } ) ;
808+ expect ( invokeModelSpans . length ) . toBe ( 1 ) ;
809+ const invokeModelSpan = invokeModelSpans [ 0 ] ;
810+
811+ // Verify attributes are set correctly - this tests our type handling logic works
812+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_INPUT_TOKENS ] ) . toBe ( 20 ) ;
813+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_OUTPUT_TOKENS ] ) . toBe ( 15 ) ;
814+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_RESPONSE_FINISH_REASONS ] ) . toEqual ( [ 'end_turn' ] ) ;
815+ } ) ;
816+
817+ it ( 'handles Buffer response body correctly' , async ( ) => {
818+ const modelId : string = 'anthropic.claude-3-5-sonnet-20240620-v1:0' ;
819+ const mockRequestBody : string = JSON . stringify ( {
820+ anthropic_version : 'bedrock-2023-05-31' ,
821+ max_tokens : 1000 ,
822+ messages : [ { role : 'user' , content : [ { type : 'text' , text : 'test' } ] } ] ,
823+ } ) ;
824+
825+ // Mock response body as Buffer
826+ const mockResponseBodyObj = {
827+ stop_reason : 'max_tokens' ,
828+ usage : { input_tokens : 25 , output_tokens : 18 } ,
829+ } ;
830+ const mockResponseBodyBuffer = Buffer . from ( JSON . stringify ( mockResponseBodyObj ) , 'utf8' ) ;
831+
832+ nock ( `https://bedrock-runtime.${ region } .amazonaws.com` )
833+ . post ( `/model/${ encodeURIComponent ( modelId ) } /invoke` )
834+ . reply ( 200 , mockResponseBodyBuffer ) ;
835+
836+ await bedrock
837+ . invokeModel ( {
838+ modelId : modelId ,
839+ body : mockRequestBody ,
840+ } )
841+ . catch ( ( err : any ) => { } ) ;
842+
843+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
844+ const invokeModelSpans : ReadableSpan [ ] = testSpans . filter ( ( s : ReadableSpan ) => {
845+ return s . name === 'BedrockRuntime.InvokeModel' ;
846+ } ) ;
847+ expect ( invokeModelSpans . length ) . toBe ( 1 ) ;
848+ const invokeModelSpan = invokeModelSpans [ 0 ] ;
849+
850+ // Verify attributes are set correctly when body is Buffer
851+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_INPUT_TOKENS ] ) . toBe ( 25 ) ;
852+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_OUTPUT_TOKENS ] ) . toBe ( 18 ) ;
853+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_RESPONSE_FINISH_REASONS ] ) . toEqual ( [
854+ 'max_tokens' ,
855+ ] ) ;
856+ } ) ;
857+
858+ it ( 'handles unexpected body type gracefully' , async ( ) => {
859+ const modelId : string = 'anthropic.claude-3-5-sonnet-20240620-v1:0' ;
860+ const mockRequestBody : string = JSON . stringify ( {
861+ anthropic_version : 'bedrock-2023-05-31' ,
862+ max_tokens : 1000 ,
863+ messages : [ { role : 'user' , content : [ { type : 'text' , text : 'test' } ] } ] ,
864+ } ) ;
865+
866+ // Mock response body as unexpected type - using reply function to return a number
867+ nock ( `https://bedrock-runtime.${ region } .amazonaws.com` )
868+ . post ( `/model/${ encodeURIComponent ( modelId ) } /invoke` )
869+ . reply ( 200 , ( ) => 12345 as any ) ;
870+
871+ await bedrock
872+ . invokeModel ( {
873+ modelId : modelId ,
874+ body : mockRequestBody ,
875+ } )
876+ . catch ( ( err : any ) => { } ) ;
877+
878+ const testSpans : ReadableSpan [ ] = getTestSpans ( ) ;
879+ const invokeModelSpans : ReadableSpan [ ] = testSpans . filter ( ( s : ReadableSpan ) => {
880+ return s . name === 'BedrockRuntime.InvokeModel' ;
881+ } ) ;
882+ expect ( invokeModelSpans . length ) . toBe ( 1 ) ;
883+ const invokeModelSpan = invokeModelSpans [ 0 ] ;
884+
885+ // Verify that no AI attributes are set when body type is unexpected
886+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_INPUT_TOKENS ] ) . toBeUndefined ( ) ;
887+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_USAGE_OUTPUT_TOKENS ] ) . toBeUndefined ( ) ;
888+ expect ( invokeModelSpan . attributes [ AwsSpanProcessingUtil . GEN_AI_RESPONSE_FINISH_REASONS ] ) . toBeUndefined ( ) ;
889+
890+ // Note: We can't easily test diag.debug() output in unit tests, but the important part
891+ // is that the function returns early and doesn't crash when encountering unexpected types
892+ } ) ;
893+ } ) ;
739894 } ) ;
740895} ) ;
0 commit comments