diff --git a/src/System Application/App/AI/src/Azure OpenAI/Operation Response/AOAIOperationResponse.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/Operation Response/AOAIOperationResponse.Codeunit.al index c7ff6637d0..0ed3bc15ff 100644 --- a/src/System Application/App/AI/src/Azure OpenAI/Operation Response/AOAIOperationResponse.Codeunit.al +++ b/src/System Application/App/AI/src/Azure OpenAI/Operation Response/AOAIOperationResponse.Codeunit.al @@ -77,6 +77,43 @@ codeunit 7770 "AOAI Operation Response" exit(AOAIFunctionResponse.IsFunctionCall()); end; + /// + /// Get whether there are any function responses for a given function name. + /// + /// The case sensitive function name to search for. + /// True if any function responses were found + procedure HasFunctionResponsesByName(FunctionName: Text): Boolean + var + MatchedAOAIFunctionResponses: List of [Codeunit "AOAI Function Response"]; + begin + exit(TryGetFunctionReponsesByName(FunctionName, MatchedAOAIFunctionResponses)); + end; + + /// + /// Get all the function responses for a specified function name. + /// + /// The case sensitive function name to search for. + /// The function responses that match the given function name + /// True if any function responses were found + procedure TryGetFunctionReponsesByName(FunctionName: Text; var MatchedAOAIFunctionResponses: List of [Codeunit "AOAI Function Response"]): Boolean + var + AOAIFunctionResponse: Codeunit "AOAI Function Response"; + begin + Clear(MatchedAOAIFunctionResponses); + + if FunctionName = '' then + exit(false); + + if not IsFunctionCall() then + exit(false); + + foreach AOAIFunctionResponse in AOAIFunctionResponses do + if AOAIFunctionResponse.GetFunctionName() = FunctionName then + MatchedAOAIFunctionResponses.Add(AOAIFunctionResponse); + + exit(MatchedAOAIFunctionResponses.Count() > 0); + end; + #if not CLEAN25 /// /// Get the function response codeunit which contains the response details. diff --git a/src/System Application/Test Library/AI/src/AzureOpenAITestLibrary.Codeunit.al b/src/System Application/Test Library/AI/src/AzureOpenAITestLibrary.Codeunit.al index 63dc7c9dad..342189321f 100644 --- a/src/System Application/Test Library/AI/src/AzureOpenAITestLibrary.Codeunit.al +++ b/src/System Application/Test Library/AI/src/AzureOpenAITestLibrary.Codeunit.al @@ -40,7 +40,8 @@ codeunit 132933 "Azure OpenAI Test Library" procedure AddAOAIFunctionResponse(var AOAIOperationResponse: Codeunit "AOAI Operation Response"; var AOAIFunctionResponse: Codeunit "AOAI Function Response"; NewIsFunctionCall: Boolean; NewAOAIFunctionResponseStatus: Enum "AOAI Function Response Status"; NewFunctionCalled: Text; NewFunctionId: Text; NewArguments: Text; NewFunctionResult: Variant; NewFunctionError: Text; NewFunctionErrorCallStack: Text) begin - AOAIOperationResponse.SetOperationResponse(true, 200, '', ''); + if AOAIOperationResponse.GetStatusCode() = 0 then + AOAIOperationResponse.SetOperationResponse(true, 200, '', ''); SetAOAIFunctionResponse(AOAIFunctionResponse, NewIsFunctionCall, NewAOAIFunctionResponseStatus, NewFunctionCalled, NewFunctionId, NewArguments, NewFunctionResult, NewFunctionError, NewFunctionErrorCallStack); AOAIOperationResponse.AddFunctionResponse(AOAIFunctionResponse); end; diff --git a/src/System Application/Test/AI/src/AzureOpenAIToolsTest.Codeunit.al b/src/System Application/Test/AI/src/AzureOpenAIToolsTest.Codeunit.al index 72a8567d88..2db4d5b97b 100644 --- a/src/System Application/Test/AI/src/AzureOpenAIToolsTest.Codeunit.al +++ b/src/System Application/Test/AI/src/AzureOpenAIToolsTest.Codeunit.al @@ -365,11 +365,14 @@ codeunit 132686 "Azure OpenAI Tools Test" [Test] procedure TestToolSelection() var + AzureOpenAITestLibrary: Codeunit "Azure OpenAI Test Library"; AOAIChatMessages: Codeunit "AOAI Chat Messages"; TestFunction1: Codeunit "Test Function 1"; TestFunction2: Codeunit "Test Function 2"; ToolCallId: Text; - ToolSelectionResponseLbl: Label '[{"id":"%1","type":"function","function":{"name":"%2","arguments":"{}"}}]', Locked = true; + ToolCalls: JsonArray; + ToolCall: JsonToken; + TestProperty: JsonToken; begin AOAIChatMessages.AddTool(TestFunction1); AOAIChatMessages.AddTool(TestFunction2); @@ -377,11 +380,22 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; - AOAIChatMessages.AddAssistantMessage(StrSubstNo(ToolSelectionResponseLbl, ToolCallId, TestFunction1.GetName())); + AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); + + ToolCalls := AOAIChatMessages.GetLastToolCalls(); + LibraryAssert.AreEqual(1, ToolCalls.Count(), 'Tool calls should contain one tool call'); + LibraryAssert.IsTrue(ToolCalls.Get(0, ToolCall), 'Could not get the tool call'); + + LibraryAssert.IsTrue(ToolCall.SelectToken('$.type', TestProperty), 'Could not find type parameter'); + LibraryAssert.AreEqual('function', TestProperty.AsValue().AsText(), 'Type was not set to function'); - LibraryAssert.AreEqual(AOAIChatMessages.GetLastMessage(), StrSubstNo(ToolSelectionResponseLbl, ToolCallId, TestFunction1.GetName()), 'Last message should be the tool selection response.'); + LibraryAssert.IsTrue(ToolCall.SelectToken('$.id', TestProperty), 'Could not find id parameter'); + LibraryAssert.AreEqual(ToolCallId, TestProperty.AsValue().AsText(), 'Tool call id was not set correctly'); + + LibraryAssert.IsTrue(ToolCall.SelectToken('$.function.name', TestProperty), 'Could not find function name'); + LibraryAssert.AreEqual(TestFunction1.GetName(), TestProperty.AsValue().AsText(), 'Function name was not set correctly'); end; [Test] @@ -402,7 +416,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -433,7 +447,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -465,7 +479,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -504,7 +518,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -611,7 +625,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -645,7 +659,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -683,7 +697,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -735,7 +749,7 @@ codeunit 132686 "Azure OpenAI Tools Test" AOAIChatMessages.AddSystemMessage('test system message'); AOAIChatMessages.AddUserMessage('test user message'); - // Function is been selected by LLM + // LLM responds with tool calls ToolCallId := 'call_of7GnOMuBT4H95XkuN14qfai'; AzureOpenAITestLibrary.SetToolCalls(AOAIChatMessages, ToolCallId, TestFunction1.GetName()); @@ -751,6 +765,37 @@ codeunit 132686 "Azure OpenAI Tools Test" end; end; + [Test] + procedure TestTryGetFunctionReponsesByName() + var + AzureOpenAITestLibrary: Codeunit "Azure OpenAI Test Library"; + AOAIOperationResponse: Codeunit "AOAI Operation Response"; + AOAIFunctionResponse: Codeunit "AOAI Function Response"; + AOAIFunctionResponses: List of [Codeunit "AOAI Function Response"]; + ToolCallId: Text; + FunctionExecutionResult: Text; + Counter: Integer; + FunctionCount: Integer; + begin + for FunctionCount := 1 to 5 do begin + FunctionExecutionResult := 'test function execution result'; + for Counter := 1 to FunctionCount do begin + Clear(AOAIFunctionResponse); + AzureOpenAITestLibrary.AddAOAIFunctionResponse(AOAIOperationResponse, AOAIFunctionResponse, true, Enum::"AOAI Function Response Status"::"Invoke Success", 'TestFunction' + Format(FunctionCount), ToolCallId, '', FunctionExecutionResult, '', ''); + end; + end; + + LibraryAssert.IsFalse(AOAIOperationResponse.HasFunctionResponsesByName(''), 'A function was found for an empty function name'); + LibraryAssert.IsFalse(AOAIOperationResponse.HasFunctionResponsesByName('TestFunctionThatDoesNotExist'), 'A function was found for a function name that does not exist'); + for FunctionCount := 1 to 5 do begin + LibraryAssert.IsTrue(AOAIOperationResponse.HasFunctionResponsesByName('TestFunction' + Format(FunctionCount)), 'Could not find the expected function'); + LibraryAssert.IsTrue(AOAIOperationResponse.TryGetFunctionReponsesByName('TestFunction' + Format(FunctionCount), AOAIFunctionResponses), 'Could get the list of expected functions'); + LibraryAssert.AreEqual(FunctionCount, AOAIFunctionResponses.Count(), 'Incorrect number of function responses returned'); + foreach AOAIFunctionResponse in AOAIFunctionResponses do + LibraryAssert.AreEqual('TestFunction' + Format(FunctionCount), AOAIFunctionResponse.GetFunctionName(), 'Function name did not match'); + end; + end; + [Test] procedure TestFunctionCallInvokedManually() var