Skip to content

Commit

Permalink
Azure OpenAI Tools support added
Browse files Browse the repository at this point in the history
  • Loading branch information
qikram committed Feb 13, 2024
1 parent 99ee993 commit 2bd4555
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ codeunit 7772 "Azure OpenAI Impl"
TelemetryGenerateTextCompletionLbl: Label 'Generate Text Completion', Locked = true;
TelemetryGenerateEmbeddingLbl: Label 'Generate Embedding', Locked = true;
TelemetryGenerateChatCompletionLbl: Label 'Generate Chat Completion', Locked = true;
TelemetryChatCompletionToolCallLbl: Label 'The chat completion called tools.', Locked = true;
TelemetryChatCompletionToolUsedLbl: Label 'Tools added to chat completion.', Locked = true;
TelemetrySetCapabilityLbl: Label 'Set Capability', Locked = true;
TelemetryCopilotCapabilityNotRegisteredLbl: Label 'Copilot capability was not registered.', Locked = true;
TelemetryIsEnabledLbl: Label 'Is Enabled', Locked = true;
Expand Down Expand Up @@ -298,8 +300,9 @@ codeunit 7772 "Azure OpenAI Impl"
procedure GenerateChatCompletion(var ChatMessages: Codeunit "AOAI Chat Messages"; AOAIChatCompletionParams: Codeunit "AOAI Chat Completion Params"; var AOAIOperationResponse: Codeunit "AOAI Operation Response"; CallerModuleInfo: ModuleInfo)
var
CustomDimensions: Dictionary of [Text, Text];
Payload: JsonObject;
PayloadText: Text;
Payload, ToolChoicePayload : JsonObject;
ToolsPayload: JsonArray;
PayloadText, ToolChoice : Text;
MetapromptTokenCount: Integer;
PromptTokenCount: Integer;
begin
Expand All @@ -308,34 +311,60 @@ codeunit 7772 "Azure OpenAI Impl"
CheckCapabilitySet();
CheckEnabled(CallerModuleInfo);
CheckAuthorizationEnabled(ChatCompletionsAOAIAuthorization, CallerModuleInfo);
AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);

AOAIChatCompletionParams.AddChatCompletionsParametersToPayload(Payload);
Payload.Add('messages', ChatMessages.AssembleHistory(MetapromptTokenCount, PromptTokenCount));

if ChatMessages.ToolExists() then begin
ToolsPayload := ChatMessages.AssembleTools();
Payload.Add('tools', ToolsPayload);
ToolChoice := ChatMessages.GetToolChoice();
if ToolChoice = 'auto' then
Payload.Add('tool_choice', ToolChoice)
else begin
ToolChoicePayload.ReadFrom(ToolChoice);
Payload.Add('tool_choice', ToolChoicePayload);
end;

CustomDimensions.Add('ToolsCount', Format(ToolsPayload.Count));
FeatureTelemetry.LogUsage('0000MFG', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryChatCompletionToolUsedLbl, CustomDimensions);
end;

Payload.WriteTo(PayloadText);

AddTelemetryCustomDimensions(CustomDimensions, CallerModuleInfo);
SendTokenCountTelemetry(MetapromptTokenCount, PromptTokenCount, CustomDimensions);
if not SendRequest(Enum::"AOAI Model Type"::"Chat Completions", ChatCompletionsAOAIAuthorization, PayloadText, AOAIOperationResponse) then begin
FeatureTelemetry.LogError('0000KVF', CopilotCapabilityImpl.GetAzureOpenAICategory(), TelemetryGenerateChatCompletionLbl, ChatCompletionsFailedWithCodeErr, '', CustomDimensions);
exit;
end;

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

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

[NonDebuggable]
[TryFunction]
local procedure ProcessChatCompletionResponse(ResponseText: Text; var ChatMessages: Codeunit "AOAI Chat Messages")
local procedure ProcessChatCompletionResponse(ResponseText: Text; var ChatMessages: Codeunit "AOAI Chat Messages"; CallerModuleInfo: ModuleInfo)
var
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.SelectToken(XPathLbl, CompletionToken);
ChatMessages.AddAssistantMessage(CompletionToken.AsValue().AsText());
if Response.SelectToken(XPathLbl, CompletionToken) then
ChatMessages.AddAssistantMessage(CompletionToken.AsValue().AsText());
if Response.SelectToken(XPathToolCallsLbl, CompletionToken) then begin
CompletionToken.AsArray().WriteTo(ToolsCall);
ChatMessages.AddAssistantMessage(ToolsCall);

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

[TryFunction]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ codeunit 7763 "AOAI Chat Messages"

var
AOAIChatMessagesImpl: Codeunit "AOAI Chat Messages Impl";
AOAIToolsImpl: Codeunit "AOAI Tools Impl";

/// <summary>
/// Sets the system message which is always at the top of the chat messages history provided to the model.
Expand Down Expand Up @@ -176,4 +177,103 @@ codeunit 7763 "AOAI Chat Messages"
begin
exit(AOAIChatMessagesImpl.PrepareHistory(SystemMessageTokenCount, MessagesTokenCount));
end;

/// <summary>
/// Appends a Tool to the payload.
/// </summary>
/// <param name="NewTool">The Tool to be added to the payload.</param>
/// <remarks>See more details here: https://go.microsoft.com/fwlink/?linkid=2254538</remarks>
[NonDebuggable]
procedure AddTool(NewTool: JsonObject)
var
CallerModuleInfo: ModuleInfo;
begin
NavApp.GetCallerModuleInfo(CallerModuleInfo);
AOAIToolsImpl.AddTool(NewTool, CallerModuleInfo);
end;

/// <summary>
/// Modifies a Tool in the list of Tool.
/// </summary>
/// <param name="Id">Id of the message.</param>
/// <param name="NewTool">The new Tool.</param>
/// <error>Message id does not exist.</error>
[NonDebuggable]
procedure ModifyTool(Id: Integer; NewTool: JsonObject)
begin
AOAIToolsImpl.ModifyTool(Id, NewTool);
end;

/// <summary>
/// Deletes a Tool from the list of Tool.
/// </summary>
/// <param name="Id">Id of the Tool.</param>
/// <error>Message id does not exist.</error>
[NonDebuggable]
procedure DeleteTool(Id: Integer)
begin
AOAIToolsImpl.DeleteTool(Id);
end;

/// <summary>
/// Gets the list of Tools.
/// </summary>
/// <returns>List of Tools.</returns>
[NonDebuggable]
procedure GetTools(): List of [JsonObject]
begin
exit(AOAIToolsImpl.GetTools());
end;

/// <summary>
/// Checks if at least one Tools exists in the list.
/// </summary>
/// <returns>True if Tools exists, false otherwise.</returns>
[NonDebuggable]
procedure ToolExists(): Boolean
begin
exit(AOAIToolsImpl.ToolExists());
end;

/// <summary>
/// Sets the Tools to be added to the payload.
/// </summary>
/// <param name="AddToolsToPayload">True if Tools is to be added to the payload, false otherwise.</param>
[NonDebuggable]
procedure SetAddToolsToPayload(AddToolsToPayload: Boolean)
begin
AOAIToolsImpl.SetAddToolToPayload(AddToolsToPayload);
end;

/// <summary>
/// Sets the Tool choice, which allow model to determine how Tools should be called.
/// </summary>
/// <param name="Toolchoice">The Tool choice parameter. </param>
/// <remarks>See more details here: https://go.microsoft.com/fwlink/?linkid=2254538</remarks>
[NonDebuggable]
procedure SetToolChoice(ToolChoice: Text)
begin
AOAIToolsImpl.SetToolChoice(ToolChoice);
end;

/// <summary>
/// Gets the Tool choice parameter.
/// </summary>
/// <returns>The Tool choice parameter.</returns>
[NonDebuggable]
procedure GetToolChoice(): Text
begin
exit(AOAIToolsImpl.GetToolChoice());
end;

/// <summary>
/// Prepares the Tools to be sent to the deployment model.
/// </summary>
/// <returns>Tools in a JsonArray.</returns>
/// <remarks>Use this after adding Tools, to construct a json array of all Tools.</remarks>
[NonDebuggable]
internal procedure AssembleTools(): JsonArray
begin
exit(AOAIToolsImpl.PrepareTools());
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace System.AI;

codeunit 7781 "AOAI Tools Impl"

Check failure on line 8 in src/System Application/App/AI/src/Azure OpenAI/Chat Completion/AOAIToolsImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AL0297 The application object identifier '7781' is not valid. It must be within the allowed ranges '[7759..7776]'.

Check failure on line 8 in src/System Application/App/AI/src/Azure OpenAI/Chat Completion/AOAIToolsImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AL0297 The application object identifier '7781' is not valid. It must be within the allowed ranges '[7759..7776]'.
{
Access = Internal;
InherentEntitlements = X;
InherentPermissions = X;

var
Initialized: Boolean;
AddToolToPayload: Boolean;
[NonDebuggable]
ToolChoice: Text;
[NonDebuggable]
Tools: List of [JsonObject];
ToolIdDoesNotExistErr: Label 'Tool id does not exist.';
ToolObjectInvalidErr: Label '%1 object does not contain %2 property.', Comment = '%1 is the object name and %2 is the property that is missing.';
ToolTypeErr: Label 'Tool type must be of function type.';

[NonDebuggable]
procedure AddTool(NewTool: JsonObject; CallerModuleInfo: ModuleInfo)
begin
CapabilityBlocked(CallerModuleInfo);
Initialize();
if ValidateTool(NewTool) then
Tools.Add(NewTool);
end;

[NonDebuggable]
procedure ModifyTool(Id: Integer; NewTool: JsonObject)
begin
if (Id < 1) or (Id > Tools.Count) then
Error(ToolIdDoesNotExistErr);
if ValidateTool(NewTool) then
Tools.Set(Id, NewTool);
end;

procedure DeleteTool(Id: Integer)
begin
if (Id < 1) or (Id > Tools.Count) then
Error(ToolIdDoesNotExistErr);

Tools.RemoveAt(Id);
end;

[NonDebuggable]
procedure GetTools(): List of [JsonObject]
begin
exit(Tools);
end;

[NonDebuggable]
procedure PrepareTools() ToolsResult: JsonArray
var
Counter: Integer;
Tool: JsonObject;
begin
if Tools.Count = 0 then
exit;

Initialize();
Counter := 1;

repeat
Clear(Tool);
Tools.Get(Counter, Tool);
ToolsResult.Add(Tool);
Counter += 1;
until Counter > Tools.Count;
end;

procedure ToolExists(): Boolean
begin
exit(AddToolToPayload and (Tools.Count > 0));
end;

procedure SetAddToolToPayload(AddToolsToPayload: Boolean)
begin
AddToolToPayload := AddToolsToPayload;
end;

[NonDebuggable]
procedure SetToolChoice(NewToolChoice: Text)
begin
ToolChoice := NewToolChoice;
end;

[NonDebuggable]
procedure GetToolChoice(): Text
begin
exit(ToolChoice);
end;

local procedure Initialize()
begin
if Initialized then
exit;

AddToolToPayload := true;
ToolChoice := 'auto';
Initialized := true;
end;

[NonDebuggable]
local procedure ValidateTool(ToolObject: JsonObject): Boolean
var
AzureOpenAIImpl: Codeunit "Azure OpenAI Impl";
TypeToken: JsonToken;
FunctionToken: JsonToken;
ToolObjectText: Text;
ErrorMessage: Text;
begin
ToolObject.WriteTo(ToolObjectText);
ToolObjectText := AzureOpenAIImpl.RemoveProhibitedCharacters(ToolObjectText);

ToolObject.ReadFrom(ToolObjectText);

if ToolObject.Get('type', TypeToken) then begin
if TypeToken.AsValue().AsText() <> 'function' then
Error(ToolTypeErr);

if ToolObject.Get('function', FunctionToken) then begin
if not FunctionToken.AsObject().Contains('name') then begin
ErrorMessage := StrSubstNo(ToolObjectInvalidErr, 'function', 'name');
Error(ErrorMessage);
end;

if not FunctionToken.AsObject().Contains('parameters') then begin
ErrorMessage := StrSubstNo(ToolObjectInvalidErr, 'function', 'parameters');
Error(ErrorMessage);
end;
end
else begin
ErrorMessage := StrSubstNo(ToolObjectInvalidErr, 'Tool', 'function');
Error(ErrorMessage);
end;
end
else begin
ErrorMessage := StrSubstNo(ToolObjectInvalidErr, 'Tool', 'type');
Error(ErrorMessage);
end;
exit(true);
end;

[NonDebuggable]
local procedure CapabilityBlocked(CallerModuleInfo: ModuleInfo)
var
CurrentModuleInfo: ModuleInfo;
CapabilityBlockedErr: Label 'Tools capability is currently blocked for non Microsoft features.';
begin
NavApp.GetCallerModuleInfo(CurrentModuleInfo);

if CallerModuleInfo.Publisher <> CurrentModuleInfo.Publisher then
Error(CapabilityBlockedErr);
end;
}

0 comments on commit 2bd4555

Please sign in to comment.