(* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
                         -****************+.
               .-=++*+::*###############*-
           :=+##*+=-::*##**############=
        .=*#*=:    .+#*=+############+.
       =##+.      +#+-=############*-:
     :*#+.      =+-.=#############- =#*.
    -##-      -=..+#############*:.  ---.
   -##:     .: .+################= :+#*-:.
  .##-        :==========*#####= :*#*- -#*
  =##                   -####*:-*#*-   .##:
  +#+                  -###*--*#*-      *#=
  *#+                 +###==*#*:        *#=
  +#*                *##+=*#*-          ##-
  :##.             .*##*##*:           -##.
   +#*            -#####*:             *#=
    *#+          =####+:             .*#+
     +#*.       +###+.              :##=
      -##+.    *##+:              :+#*:
        =*#+..*#+:             .-*#*-
          :-:#+::.        .:-=*##+:
           -+..+*###****####*+-.
          :.      ..:::::..
        ____                   _
       / ___| _ __   __ _ _ __| | __
       \___ \| '_ \ / _` | '__| |/ /
        ___) | |_) | (_| | |  |   <
       |____/| .__/ \__,_|_|  |_|\_\
             |_|   Game Toolkit™

Copyright © 2024-present tinyBigGAMES™ LLC
         All Rights Reserved.

Website: https://tinybiggames.com
Email  : support@tinybiggames.com

See LICENSE for license information
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *)

unit UGenAI01;

interface

uses
  System.SysUtils,
  WinApi.Windows,
  SGT.Deps,
  SGT.Deps.Ext,
  SGT.OGL,
  SGT.Core,
  SGT.Lua,
  SGT.LuaDebugger,
  SGT.GenAI,
  SGT.CloudDB,
  UCommon;

procedure Demo();

implementation

// The 'OnLoadModelProgress' function handles the progress of loading a model.
// It prints the loading progress to the console.
function OnLoadModelProgress(const AModelName: string; const AProgress: Single; const AUserData: Pointer): Boolean;
begin
  // Print the model loading progress.
  Console.Print('%sLoading model "%s" (%3.2f%s)...', [Console.CR, AModelName, AProgress * 100, '%'], Console.FG_CYAN);

  // Clear the line when loading is complete.
  if AProgress >= 1 then
  begin
    Console.ClearLine(Console.FG_WHITE);
  end;

  Result := True; // Continue loading.
end;

// The 'OnLoadModel' procedure handles the event when a model is fully loaded.
// It prints a message indicating that the model has been loaded.
procedure OnLoadModel(const AModelName: string; const ASuccess: Boolean; const AUserData: Pointer);
begin
  // Print that the model has been loaded successfully.
  Console.PrintLn('Model loaded: "%s"', [AModelName], Console.FG_CYAN);
end;

// The 'OnInferenceCancel' function checks if the inference should be canceled.
// It returns True if the ESC key was released.
function OnInferenceCancel(const AUserData: Pointer): Boolean;
begin
  if Console.WasKeyReleased(VK_ESCAPE) then
    Result := True // Cancel inference.
  else
    Result := False; // Continue inference.
end;

// The 'OnInferenceGetNextToken' procedure handles the reception of new tokens during inference.
// It processes the token and prints it to the console.
procedure OnInferenceGetNextToken(const AToken: string; const AUserData: Pointer);
begin
  // Handle new tokens based on the response type.
  case TokenResponse.AddToken(AToken) of
    tpaWait: // Do nothing, need more tokens.
    begin
    end;

    tpaAppend: // Append the token to the last word.
    begin
      Console.Print(TokenResponse.LastWord(False), Console.FG_WHITE);
    end;

    tpaNewline: // Start a new line with the last word.
    begin
      Console.PrintLn('', Console.FG_WHITE);
      Console.Print(TokenResponse.LastWord(True), Console.FG_WHITE);
    end;
  end;
end;

// The 'OnInfo' procedure handles informational messages during the inference process.
// This is currently commented out and does not print anything.
procedure OnInfo(const ALevel: Integer; const AText: string; const AUserData: Pointer);
begin
  // Console.Print(AText, Console.FG_DARKGRAY); // Handle info messages.
end;

// The 'OnInferenceStart' procedure is triggered at the start of inference.
// It prints the user message and a start message to the console.
procedure OnInferenceStart(const AUserData: Pointer);
var
  LGenAI: TGenAI;
begin
  LGenAI := TGenAI(AUserData);
  Console.PrintLn('', Console.FG_WHITE);
  Console.PrintLn(LGenAI.GetLastUserMessage(), Console.FG_DARKGREEN);
  Console.PrintLn('[inference start]', Console.FG_MAGENTA);
end;

// The 'OnInferenceEnd' procedure is triggered at the end of inference.
// It finalizes token processing and prints the end of inference message.
procedure OnInferenceEnd(const ASender: Pointer);
begin
  // Finalize token processing and handle any leftovers.
  if TokenResponse.Finalize() then
  begin
    OnInferenceGetNextToken('', nil); // Handle the last word.
  end;
  Console.PrintLn('[inference end]', Console.FG_MAGENTA);
end;

// The 'Demo' procedure demonstrates the use of a TGenAI model for inference.
// It initializes the model, runs inference on a prompt, and handles callbacks for various events.
procedure Demo();
const
  // models
  CModel = 'gemma-2-2b-it-abliterated-Q8_0';

  // prompts
  //CPrompt = 'hello?';
  //CPrompt = 'what is AI?';
  CPrompt = 'who is bill gates?';
  //CPrompt = 'create a short story about an AI that become self-aware!';
  //CPrompt = 'what is best used to carry a small dog: (a) swimming pool (b) basket (c) backyeard (d) dog show?';

var
  LGenAI: TGenAI;               // The TGenAI object for managing the inference process.
  LTokenOutputSpeed: Single;    // Variable to store the token output speed.
  LInputTokens: Int32;          // Variable to store the number of input tokens.
  LOutputTokens: Int32;         // Variable to store the number of output tokens.
  LTotalTokens: Int32;          // Variable to store the total number of tokens.
begin
  Console.PrintLn();
  Console.PrintLn();

  // Create and configure the TGenAI object.
  LGenAI := TGenAI.Create();
  LGenAI.InitConfig('C:\LLM\gguf', -1, -1);

  // Set various callbacks for handling events during the model's lifecycle.
  LGenAI.SetInfoCallback(OnInfo, LGenAI);
  LGenAI.SetLoadModelProgressCallback(OnLoadModelProgress, LGenAI);
  LGenAI.SetLoadModelCallback(OnLoadModel, LGenAI);
  LGenAI.SetInferenceStartCallback(OnInferenceStart, LGenAI);
  LGenAI.SetInferenceEndCallback(OnInferenceEnd, LGenAI);
  LGenAI.SetInferenceCancelCallback(OnInferenceCancel, LGenAI);
  LGenAI.SetInferenceTokenCallback(OnInferenceGetNextToken, LGenAI);

  // Define models with their corresponding configurations.
  LGenAI.DefineModel('gemma-2-2b-it-abliterated-Q8_0.gguf', 'gemma-2-2b-it-abliterated-Q8_0', 8000, '<start_of_turn>{role}\n{content}<end_of_turn>\n<start_of_turn>model\n<end_of_turn>\n', '<start_of_turn>model');
  LGenAI.DefineModel('Phi-3.5-mini-instruct-Q4_K_M.gguf', 'Phi-3.5-mini-instruct-Q4_K_M', 8000, '<|{role}|> {content}', '');
  LGenAI.DefineModel('Phi-3.5-mini-instruct_Uncensored-Q4_K_M.gguf', 'Phi-3.5-mini-instruct_Uncensored-Q4_K_M', 8000, '<|{role}|>{content}', '\n');

  // Add initial messages to the model.
  LGenAI.AddMessage(ROLE_SYSTEM, 'You are a helpful AI assistant');
  LGenAI.AddMessage(ROLE_USER, CPrompt);

  // Run inference on the specified model.
  if LGenAI.RunInference(CModel, 1024) then
  begin
    // Get inference statistics and print them to the console.
    LGenAI.GetInferenceStats(nil, @LTokenOutputSpeed, @LInputTokens, @LOutputTokens, @LTotalTokens);
    Console.PrintLn();
    Console.PrintLn('Token :: Input: %d, Output: %d, Total: %d, Speed: %3.2f tokens/sec', [LInputTokens, LOutputTokens, LTotalTokens, LTokenOutputSpeed], Console.FG_BRIGHTYELLOW);
  end
  else
  begin
    // Handle inference errors and print them to the console.
    Console.PrintLn('Error: %s', [LGenAI.GetError()], Console.FG_RED);
  end;

  // Unload the model and free the TGenAI object.
  LGenAI.UnloadModel();
  LGenAI.Free();
end;


end.
