Skip to content

Commit

Permalink
[Copilot] Find functions results by function name (#1761)
Browse files Browse the repository at this point in the history
#### Summary
Add some helper procedures to the operation result to get functions by a
given name, or test if a function name is present in the list of
responses.

Also fixes some tests to ensure tool calls are set correctly

#### Work Item(s) 
Fixes
[AB#542604](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/542604)
  • Loading branch information
msft-sam authored Aug 21, 2024
1 parent 8f6cbf1 commit 083aa98
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,43 @@ codeunit 7770 "AOAI Operation Response"
exit(AOAIFunctionResponse.IsFunctionCall());
end;

/// <summary>
/// Get whether there are any function responses for a given function name.
/// </summary>
/// <param name="FunctionName">The case sensitive function name to search for.</param>
/// <returns>True if any function responses were found</returns>
procedure HasFunctionResponsesByName(FunctionName: Text): Boolean
var
MatchedAOAIFunctionResponses: List of [Codeunit "AOAI Function Response"];
begin
exit(TryGetFunctionReponsesByName(FunctionName, MatchedAOAIFunctionResponses));
end;

/// <summary>
/// Get all the function responses for a specified function name.
/// </summary>
/// <param name="FunctionName">The case sensitive function name to search for.</param>
/// <param name="MatchedAOAIFunctionResponses">The function responses that match the given function name</param>
/// <returns>True if any function responses were found</returns>
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
/// <summary>
/// Get the function response codeunit which contains the response details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
69 changes: 57 additions & 12 deletions src/System Application/Test/AI/src/AzureOpenAIToolsTest.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -365,23 +365,37 @@ 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);

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]
Expand All @@ -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());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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());

Expand All @@ -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
Expand Down

0 comments on commit 083aa98

Please sign in to comment.