Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[241][Copilot] Marketing Text Improvements - Functions and GPT4o #1573

Merged
merged 9 commits into from
Jul 19, 2024
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
Loading