Skip to content

Commit

Permalink
[241][Copilot] Marketing Text Improvements - Functions and GPT4o (#1573)
Browse files Browse the repository at this point in the history
#### Summary

Improvements done to marketing text feature by introduction functions
and upgrading the model to gpt4o. The changes were merged into main as
part of this PR #1275

This PR backports all the commits related to functions/tools that needs
to be backported to make the above changes work in 24. Following are the
list of commits that were cherry-picked in the same order:

1. #824 
2. #882 
3. #928 
4. #945 
5. #719 
6. #1275 

#### Work Item(s) 

Fixes #
[AB#521281](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/521281)

---------

Co-authored-by: Darrick <[email protected]>
Co-authored-by: Sun Haoran <[email protected]>
Co-authored-by: Dmitry Katson <[email protected]>
Co-authored-by: Jesper Schulz-Wedde <[email protected]>
Co-authored-by: Qasim Ikram <[email protected]>
  • Loading branch information
6 people authored Jul 19, 2024
1 parent b2fc7b0 commit e544500
Show file tree
Hide file tree
Showing 25 changed files with 1,436 additions and 184 deletions.
7 changes: 6 additions & 1 deletion src/System Application/App/AI/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,17 @@
"id": "f2d92a20-33a7-4174-a82f-666e8e2ad69e",
"name": "AI Test Library",
"publisher": "Microsoft"
},
{
"id": "81848664-2a4b-4f1d-af93-7173c12be82f",
"name": "AI Test",
"publisher": "Microsoft"
}
],
"platform": "24.0.0.0",
"idRanges": [
{
"from": 7759,
"from": 7758,
"to": 7778
}
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ codeunit 7772 "Azure OpenAI Impl"
CopilotDisabledForTenantErr: Label 'Copilot is not enabled for the tenant. Please contact your system administrator.';
CapabilityNotRegisteredErr: Label 'Copilot capability ''%1'' has not been registered by the module.', Comment = '%1 is the name of the Copilot Capability';
CapabilityNotEnabledErr: Label 'Copilot capability ''%1'' has not been enabled. Please contact your system administrator.', Comment = '%1 is the name of the Copilot Capability';
MessagesMustContainJsonWordWhenResponseFormatIsJsonErr: Label 'The messages must contain the word ''json'' in some form, to use ''response format'' of type ''json_object''.';
EmptyMetapromptErr: Label 'The metaprompt has not been set, please provide a metaprompt.';
MetapromptLoadingErr: Label 'Metaprompt not found.';
EnabledKeyTok: Label 'AOAI-Enabled', Locked = true;
AllowlistedTenantsAkvKeyTok: Label 'AOAI-Allow-1P-Auth', Locked = true;
FunctionCallingFunctionNotFoundErr: Label 'Function call not found, %1.', Comment = '%1 is the name of the function';
TelemetryGenerateTextCompletionLbl: Label 'Generate Text Completion', Locked = true;
TelemetryGenerateEmbeddingLbl: Label 'Generate Embedding', Locked = true;
TelemetryGenerateChatCompletionLbl: Label 'Generate Chat Completion', Locked = true;
Expand All @@ -60,6 +62,7 @@ codeunit 7772 "Azure OpenAI Impl"
TelemetryMetapromptRetrievalErr: Label 'Unable to retrieve metaprompt from Azure Key Vault.', Locked = true;
TelemetryEmptyTenantIdErr: Label 'Empty or malformed tenant ID.', Locked = true;
TelemetryTenantAllowlistedMsg: Label 'The current tenant is allowlisted for first party auth.', Locked = true;
TelemetryFunctionCallingFailedErr: Label 'Function calling failed for function: %1', Comment = '%1 is the name of the function', Locked = true;

procedure IsEnabled(Capability: Enum "Copilot Capability"; CallerModuleInfo: ModuleInfo): Boolean
begin
Expand Down Expand Up @@ -334,6 +337,8 @@ codeunit 7772 "Azure OpenAI Impl"
FeatureTelemetry.LogUsage('0000MFG', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryChatCompletionToolUsedLbl, CustomDimensions);
end;

CheckJsonModeCompatibility(Payload);

Payload.WriteTo(PayloadText);

SendTokenCountTelemetry(MetapromptTokenCount, PromptTokenCount, CustomDimensions);
Expand All @@ -342,35 +347,129 @@ codeunit 7772 "Azure OpenAI Impl"
exit;
end;

ProcessChatCompletionResponse(AOAIOperationResponse.GetResult(), ChatMessages, CallerModuleInfo);
ProcessChatCompletionResponse(ChatMessages, AOAIOperationResponse, CallerModuleInfo);

FeatureTelemetry.LogUsage('0000KVN', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, CustomDimensions);
end;

local procedure CheckJsonModeCompatibility(Payload: JsonObject)
var
ResponseFormatToken: JsonToken;
MessagesToken: JsonToken;
Messages: Text;
TypeToken: JsonToken;
XPathLbl: Label '$.type', Locked = true;
begin
if not Payload.Get('response_format', ResponseFormatToken) then
exit;

if not Payload.Get('messages', MessagesToken) then
exit;

if not ResponseFormatToken.SelectToken(XPathLbl, TypeToken) then
exit;

if TypeToken.AsValue().AsText() <> 'json_object' then
exit;

MessagesToken.WriteTo(Messages);
if not LowerCase(Messages).Contains('json') then
Error(MessagesMustContainJsonWordWhenResponseFormatIsJsonErr);
end;

[NonDebuggable]
[TryFunction]
local procedure ProcessChatCompletionResponse(ResponseText: Text; var ChatMessages: Codeunit "AOAI Chat Messages"; CallerModuleInfo: ModuleInfo)
local procedure ProcessChatCompletionResponse(var ChatMessages: Codeunit "AOAI Chat Messages"; var AOAIOperationResponse: Codeunit "AOAI Operation Response"; CallerModuleInfo: ModuleInfo)
var
AOAIFunctionResponse: Codeunit "AOAI Function Response";
CustomDimensions: Dictionary of [Text, Text];
ToolsCall: Text;
Response: JsonObject;
CompletionToken: JsonToken;
XPathLbl: Label '$.content', Comment = 'For more details on response, see https://aka.ms/AAlrz36', Locked = true;
XPathToolCallsLbl: Label '$.tool_calls', Comment = 'For more details on response, see https://aka.ms/AAlrz36', Locked = true;
begin
Response.ReadFrom(ResponseText);
Response.ReadFrom(AOAIOperationResponse.GetResult());
if Response.SelectToken(XPathLbl, CompletionToken) then
if not CompletionToken.AsValue().IsNull() then
ChatMessages.AddAssistantMessage(CompletionToken.AsValue().AsText());
if Response.SelectToken(XPathToolCallsLbl, CompletionToken) then begin
CompletionToken.AsArray().WriteTo(ToolsCall);
ChatMessages.AddAssistantMessage(ToolsCall);

AOAIFunctionResponse := AOAIOperationResponse.GetFunctionResponse();
if not ProcessFunctionCall(CompletionToken.AsArray(), ChatMessages, AOAIFunctionResponse) then
AOAIFunctionResponse.SetFunctionCallingResponse(true, false, '', '', '', '', '');

AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
if not AOAIFunctionResponse.IsSuccess() then
FeatureTelemetry.LogError('0000MTB', CopilotCapabilityImpl.GetAzureOpenAICategory(), StrSubstNo(TelemetryFunctionCallingFailedErr, AOAIFunctionResponse.GetFunctionName()), AOAIFunctionResponse.GetError(), AOAIFunctionResponse.GetErrorCallstack(), CustomDimensions);

FeatureTelemetry.LogUsage('0000MFH', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryChatCompletionToolCallLbl, CustomDimensions);
end;
end;

local procedure ProcessFunctionCall(Functions: JsonArray; var ChatMessages: Codeunit "AOAI Chat Messages"; var AOAIFunctionResponse: Codeunit "AOAI Function Response"): Boolean
var
Function: JsonObject;
Arguments: JsonObject;
Token: JsonToken;
FunctionName: Text;
FunctionId: Text;
AOAIFunction: Interface "AOAI Function";
FunctionResult: Variant;
begin
if Functions.Count = 0 then
exit(false);

Functions.Get(0, Token);
Function := Token.AsObject();

if Function.Get('type', Token) then begin
if Token.AsValue().AsText() <> 'function' then
exit(false);
end else
exit(false);

if Function.Get('id', Token) then
FunctionId := Token.AsValue().AsText()
else
exit(false);

if Function.Get('function', Token) then
Function := Token.AsObject()
else
exit(false);

if Function.Get('name', Token) then
FunctionName := Token.AsValue().AsText()
else
exit(false);

if Function.Get('arguments', Token) then
// Arguments are stored as a string in the JSON
Arguments.ReadFrom(Token.AsValue().AsText());

if ChatMessages.GetFunctionTool(FunctionName, AOAIFunction) then
if TryExecuteFunction(AOAIFunction, Arguments, FunctionResult) then begin
AOAIFunctionResponse.SetFunctionCallingResponse(true, true, AOAIFunction.GetName(), FunctionId, FunctionResult, '', '');
exit(true);
end else begin
AOAIFunctionResponse.SetFunctionCallingResponse(true, false, AOAIFunction.GetName(), FunctionId, FunctionResult, GetLastErrorText(), GetLastErrorCallStack());
exit(true);
end
else begin
AOAIFunctionResponse.SetFunctionCallingResponse(true, false, FunctionName, FunctionId, FunctionResult, StrSubstNo(FunctionCallingFunctionNotFoundErr, FunctionName), '');
exit(true);
end;
end;

[TryFunction]
local procedure TryExecuteFunction(AOAIFunction: Interface "AOAI Function"; Arguments: JsonObject; var Result: Variant)
begin
Result := AOAIFunction.Execute(Arguments);
end;

[TryFunction]
[NonDebuggable]
local procedure SendRequest(ModelType: Enum "AOAI Model Type"; AOAIAuthorization: Codeunit "AOAI Authorization"; Payload: Text; var AOAIOperationResponse: Codeunit "AOAI Operation Response"; CallerModuleInfo: ModuleInfo)
Expand All @@ -379,6 +478,7 @@ codeunit 7772 "Azure OpenAI Impl"
ALCopilotCapability: DotNet ALCopilotCapability;
ALCopilotFunctions: DotNet ALCopilotFunctions;
ALCopilotOperationResponse: DotNet ALCopilotOperationResponse;
Error: Text;
begin
ClearLastError();
ALCopilotAuthorization := ALCopilotAuthorization.Create(AOAIAuthorization.GetEndpoint(), AOAIAuthorization.GetDeployment(), AOAIAuthorization.GetApiKey());
Expand All @@ -396,7 +496,10 @@ codeunit 7772 "Azure OpenAI Impl"
Error(InvalidModelTypeErr)
end;

AOAIOperationResponse.SetOperationResponse(ALCopilotOperationResponse);
Error := ALCopilotOperationResponse.ErrorText();
if Error = '' then
Error := GetLastErrorText();
AOAIOperationResponse.SetOperationResponse(ALCopilotOperationResponse.IsSuccess(), ALCopilotOperationResponse.StatusCode(), ALCopilotOperationResponse.Result(), Error);

if not ALCopilotOperationResponse.IsSuccess() then
Error(GenerateRequestFailedErr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ codeunit 7762 "AOAI Chat Compl Params Impl"
Initialized: Boolean;
Temperature: Decimal;
MaxTokens: Integer;
JsonMode: Boolean;
MaxHistory: Integer;
PresencePenalty: Decimal;
FrequencyPenalty: Decimal;
Expand All @@ -38,6 +39,14 @@ codeunit 7762 "AOAI Chat Compl Params Impl"
exit(MaxTokens);
end;

procedure IsJsonMode(): Boolean
begin
if not Initialized then
InitializeDefaults();

exit(JsonMode);
end;

procedure GetMaxHistory(): Integer
begin
if not Initialized then
Expand Down Expand Up @@ -81,6 +90,14 @@ codeunit 7762 "AOAI Chat Compl Params Impl"
MaxTokens := NewMaxTokens;
end;

procedure SetJsonMode(NewJsonMode: Boolean)
begin
if not Initialized then
InitializeDefaults();

JsonMode := NewJsonMode;
end;

procedure SetMaxHistory(NewMaxHistory: Integer)
begin
if not Initialized then
Expand Down Expand Up @@ -121,6 +138,14 @@ codeunit 7762 "AOAI Chat Compl Params Impl"
Payload.Add('temperature', GetTemperature());
Payload.Add('presence_penalty', GetPresencePenalty());
Payload.Add('frequency_penalty', GetFrequencyPenalty());

if IsJsonMode() then
Payload.Add('response_format', GetJsonResponseFormat());
end;

local procedure GetJsonResponseFormat() ResponseFormat: JsonObject
begin
ResponseFormat.Add('type', 'json_object');
end;

local procedure InitializeDefaults()
Expand All @@ -132,5 +157,6 @@ codeunit 7762 "AOAI Chat Compl Params Impl"
SetFrequencyPenalty(0);
SetMaxTokens(0);
SetMaxHistory(10);
SetJsonMode(false);
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ codeunit 7761 "AOAI Chat Completion Params"
AOAIChatComplParamsImpl.SetMaxTokens(NewMaxTokens);
end;

/// <summary>
/// Sets if the model should return a valid JSON object as a chat completion.
/// </summary>
/// <param name="NewJsonMode">The new Json mode for the chat completion: true or false.</param>
/// <remarks>Default is false.</remarks>
/// <remarks>When true, the model will return a valid JSON object as a chat completion. Including guidance to the model that it should produce JSON as part of the messages conversation is required</remarks>
/// <remarks>When true, the word 'json' must be included in at least one message.</remarks>
procedure SetJsonMode(NewJsonMode: Boolean)
begin
AOAIChatComplParamsImpl.SetJsonMode(NewJsonMode);
end;

/// <summary>
/// Sets the maximum number of messages to send back as the message history.
/// </summary>
Expand Down
Loading

0 comments on commit e544500

Please sign in to comment.