@@ -553,6 +553,93 @@ public async Task SupportsConsecutiveStreamingUpdatesWithFunctionCalls()
553553 Assert . Equal ( "OK bye" , singleUpdateContent . Text ) ;
554554 }
555555
556+ [ Fact ]
557+ public async Task CanAccesssFunctionInvocationContextFromFunctionCall ( )
558+ {
559+ var invocationContexts = new List < FunctionInvokingChatClient . FunctionInvocationContext > ( ) ;
560+ var function = AIFunctionFactory . Create ( async ( int i ) =>
561+ {
562+ // The context should propogate across async calls
563+ await Task . Yield ( ) ;
564+
565+ var context = FunctionInvokingChatClient . CurrentContext ! ;
566+ invocationContexts . Add ( context ) ;
567+
568+ if ( i == 42 )
569+ {
570+ context . Terminate = true ;
571+ }
572+
573+ return $ "Result { i } ";
574+ } , "Func1" ) ;
575+
576+ var options = new ChatOptions
577+ {
578+ Tools = [ function ] ,
579+ } ;
580+
581+ // The invocation loop should terminate after the second function call
582+ List < ChatMessage > planBeforeTermination =
583+ [
584+ new ChatMessage ( ChatRole . User , "hello" ) ,
585+ new ChatMessage ( ChatRole . Assistant , [ new FunctionCallContent ( "callId1" , "Func1" , new Dictionary < string , object ? > { [ "i" ] = 41 } ) ] ) ,
586+ new ChatMessage ( ChatRole . Tool , [ new FunctionResultContent ( "callId1" , "Func1" , result : "Result 41" ) ] ) ,
587+ new ChatMessage ( ChatRole . Assistant , [ new FunctionCallContent ( "callId2" , "Func1" , new Dictionary < string , object ? > { [ "i" ] = 42 } ) ] ) ,
588+ new ChatMessage ( ChatRole . Tool , [ new FunctionResultContent ( "callId2" , "Func1" , result : "Result 42" ) ] ) ,
589+ ] ;
590+
591+ // The full plan should never be fulfilled
592+ List < ChatMessage > plan =
593+ [
594+ .. planBeforeTermination ,
595+ new ChatMessage ( ChatRole . Assistant , [ new FunctionCallContent ( "callId3" , "Func1" , new Dictionary < string , object ? > { [ "i" ] = 43 } ) ] ) ,
596+ new ChatMessage ( ChatRole . Tool , [ new FunctionResultContent ( "callId3" , "Func1" , result : "Result 43" ) ] ) ,
597+ new ChatMessage ( ChatRole . Assistant , "world" ) ,
598+ ] ;
599+
600+ await InvokeAsync ( ( ) => InvokeAndAssertAsync ( options , plan , expected : [
601+ .. planBeforeTermination ,
602+
603+ // The last message is the one returned by the chat client
604+ // This message's content should contain the last function call before the termination
605+ new ChatMessage ( ChatRole . Assistant , [ new FunctionCallContent ( "callId2" , "Func1" , new Dictionary < string , object ? > { [ "i" ] = 42 } ) ] ) ,
606+ ] ) ) ;
607+
608+ await InvokeAsync ( ( ) => InvokeAndAssertStreamingAsync ( options , plan , expected : [
609+ .. planBeforeTermination ,
610+
611+ // The last message is the one returned by the chat client
612+ // When streaming, function call content is removed from this message
613+ new ChatMessage ( ChatRole . Assistant , [ ] ) ,
614+ ] ) ) ;
615+
616+ // The current context should be null outside the async call stack for the function invocation
617+ Assert . Null ( FunctionInvokingChatClient . CurrentContext ) ;
618+
619+ async Task InvokeAsync ( Func < Task < List < ChatMessage > > > work )
620+ {
621+ invocationContexts . Clear ( ) ;
622+
623+ var chatMessages = await work ( ) ;
624+
625+ Assert . Collection ( invocationContexts ,
626+ c => AssertInvocationContext ( c , iteration : 0 , terminate : false ) ,
627+ c => AssertInvocationContext ( c , iteration : 1 , terminate : true ) ) ;
628+
629+ void AssertInvocationContext ( FunctionInvokingChatClient . FunctionInvocationContext context , int iteration , bool terminate )
630+ {
631+ Assert . NotNull ( context ) ;
632+ Assert . Same ( chatMessages , context . ChatMessages ) ;
633+ Assert . Same ( function , context . Function ) ;
634+ Assert . Equal ( "Func1" , context . CallContent . Name ) ;
635+ Assert . Equal ( 0 , context . FunctionCallIndex ) ;
636+ Assert . Equal ( 1 , context . FunctionCount ) ;
637+ Assert . Equal ( iteration , context . Iteration ) ;
638+ Assert . Equal ( terminate , context . Terminate ) ;
639+ }
640+ }
641+ }
642+
556643 private static async Task < List < ChatMessage > > InvokeAndAssertAsync (
557644 ChatOptions options ,
558645 List < ChatMessage > plan ,
0 commit comments