Skip to content

Instantly share code, notes, and snippets.

@twobob
Created January 2, 2024 01:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save twobob/74dc94c03a62da0a2ced3a5203fe7ae1 to your computer and use it in GitHub Desktop.
Save twobob/74dc94c03a62da0a2ced3a5203fe7ae1 to your computer and use it in GitHub Desktop.
OPEN AI Unit. in Pascal. Also works for other compatible stuff like LMstudio, Firemonkey cross platfrom test app attached
unit OpenAI;
interface
uses
System.SysUtils,
System.Classes,
System.Generics.Collections,
System.Net.HttpClient,
System.Net.HttpClientComponent,
System.Net.URLClient,
Windows,
System.JSON;
type
TOpenAI = class
private
FBaseURL: string;
FAPIKey: string;
FHttpClient: TNetHTTPClient;
function ConstructJSONBody(AModel: string; AMessages: TArray<TPair<string, string>>; ATemperature: Double): TStringStream;
public
constructor Create(ABaseURL, AAPIKey: string);
destructor Destroy; override;
function ChatCompletionsCreate(AModel: string; AMessages: TArray<TPair<string, string>>; ATemperature: Double): string;
end;
implementation
{ TOpenAI }
constructor TOpenAI.Create(ABaseURL, AAPIKey: string);
begin
FBaseURL := ABaseURL;
FAPIKey := AAPIKey;
FHttpClient := TNetHTTPClient.Create(nil);
end;
destructor TOpenAI.Destroy;
begin
FHttpClient.Free;
inherited;
end;
function TOpenAI.ConstructJSONBody(AModel: string; AMessages: TArray<TPair<string, string>>; ATemperature: Double): TStringStream;
var
JSONMessages: TJSONArray;
I: Integer;
JSONObj: TJSONObject;
begin
JSONMessages := TJSONArray.Create;
try
for I := Low(AMessages) to High(AMessages) do
begin
JSONObj := TJSONObject.Create;
JSONObj.AddPair('role', AMessages[I].Key);
JSONObj.AddPair('content', AMessages[I].Value);
JSONMessages.AddElement(JSONObj);
end;
Result := TStringStream.Create(
TJSONObject.Create
.AddPair('model', AModel)
.AddPair('messages', JSONMessages)
.AddPair('temperature', TJSONNumber.Create(ATemperature))
.AddPair('max_tokens', TJSONNumber.Create(4096))
.AddPair('stream', TJSONFalse.Create)
.ToString);
except
JSONMessages.Free;
raise;
end;
end;
function TOpenAI.ChatCompletionsCreate(AModel: string; AMessages: TArray<TPair<string, string>>; ATemperature: Double): string;
var
I: Integer;
Key, Value, ResultString: string;
RequestBody: TStringStream;
Response: IHTTPResponse;
JSONValue, ChoiceObject: TJSONValue;
ChoicesArray: TJSONArray;
ContentObject : TJSONValue;
FullResponseContent, ContentString: string;
begin
RequestBody := ConstructJSONBody(AModel, AMessages, ATemperature);
try
FHttpClient.ResponseTimeout := 240000; // Timeout set to 60000 milliseconds (4 minute)
FHttpClient.ContentType := 'application/json';
FHttpClient.CustomHeaders['Authorization'] := 'Bearer ' + FAPIKey;
Response := FHttpClient.Post(FBaseURL + '/chat/completions', RequestBody);
FullResponseContent := Response.ContentAsString;
ContentString := FullResponseContent ;
JSONValue := TJSONObject.ParseJSONValue(FullResponseContent);
if JSONValue <> nil then
try
ChoicesArray := (JSONValue as TJSONObject).GetValue('choices') as TJSONArray;
if ChoicesArray <> nil then
begin
ChoiceObject := ChoicesArray.Items[0] as TJSONObject;
if ChoiceObject <> nil then
begin
if ChoiceObject is TJSONObject then
begin
ResultString := '';
for I := 0 to (ChoiceObject as TJSONObject).Count - 1 do
begin
Key := (ChoiceObject as TJSONObject).Pairs[I].JsonString.Value;
Value := (ChoiceObject as TJSONObject).Pairs[I].JsonValue.Value;
ResultString := ResultString + Key + ': ' + Value + '; '; // Concatenate key-value pair with a separator
end;
if ResultString <> '' then
SetLength(ResultString, Length(ResultString) - 2);
end;
ContentObject := (ChoiceObject as TJSONObject).GetValue('message') as TJSONObject;
if ContentObject <> nil then
begin
if ContentObject is TJSONObject then
begin
ContentString := (ContentObject as TJSONObject).GetValue('content').Value ;
end;
end;
Result := ContentString;
end;
end;
finally
JSONValue.Free;
end;
finally
RequestBody.Free;
end;
end;
end.
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 632
ClientWidth = 952
FormFactor.Width = 320
FormFactor.Height = 480
FormFactor.Devices = [Desktop]
DesignerMasterStyle = 0
object GridPanelLayout1: TGridPanelLayout
Anchors = [akLeft, akTop, akRight, akBottom]
Size.Width = 952.000000000000000000
Size.Height = 632.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
ColumnCollection = <
item
Value = 100.000000000000000000
end>
ControlCollection = <
item
Column = 0
Control = Panel1
Row = 0
end
item
Column = 0
Control = Panel2
Row = 1
end>
RowCollection = <
item
Value = 50.000000000000000000
end
item
Value = 50.000000000000000000
end>
object Panel1: TPanel
Align = Center
Size.Width = 952.000000000000000000
Size.Height = 318.000000000000000000
Size.PlatformDefault = False
TabOrder = 0
object tInput: TText
Align = Fit
Enabled = False
Size.Width = 952.000000000000000000
Size.Height = 318.000000000000000000
Size.PlatformDefault = False
Text = 'INPUT AREA'
Visible = False
end
object mInput: TMemo
Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
DataDetectorTypes = []
Align = Center
Size.Width = 952.000000000000000000
Size.Height = 317.000000000000000000
Size.PlatformDefault = False
TabOrder = 2
OnKeyDown = mInputKeyDown
Viewport.Width = 948.000000000000000000
Viewport.Height = 313.000000000000000000
end
end
object Panel2: TPanel
Align = Center
Size.Width = 952.000000000000000000
Size.Height = 317.000000000000000000
Size.PlatformDefault = False
TabOrder = 1
object tOutput: TText
Position.Y = 8.000000000000000000
Size.Width = 961.000000000000000000
Size.Height = 313.000000000000000000
Size.PlatformDefault = False
Text = 'OUTPUT AREA'
end
end
end
end
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.Memo.Types,
FMX.ScrollBox, FMX.Memo, System.Generics.Collections, Windows, OpenAI;
type
TForm1 = class(TForm)
GridPanelLayout1: TGridPanelLayout;
Panel1: TPanel;
tInput: TText;
Panel2: TPanel;
tOutput: TText;
mInput: TMemo;
procedure mInputKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.mInputKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
Shift: TShiftState);
begin
// Check if the Enter key was pressed
if Key = VK_RETURN then
begin
// Prevent the beep sound when pressing Enter
Key := 0;
// Execute the task asynchronously in an anonymous thread
TThread.CreateAnonymousThread(
procedure
var
OpenAI: TOpenAI;
Response: string;
Messages: TArray<TPair<string, string>>;
begin
// Initialize the OpenAI instance
//OpenAI := TOpenAI.Create('http://localhost:1234/v1', 'not-needed');
OpenAI := TOpenAI.Create('https://api.openai.com/v1', 'sk-aWojEH7PSXuM');
try
// Prepare your messages array
SetLength(Messages, 1);
Messages[0] := TPair<string, string>.Create('user', mInput.Text);
// Call the ChatCompletionsCreate method asynchronously
Response := OpenAI.ChatCompletionsCreate('gpt-3.5-turbo-16k', Messages, 0.7);
// Perform the UI update in the main thread
TThread.Synchronize(nil,
procedure
begin
tOutput.Text := Response;
end);
finally
OpenAI.Free;
end;
end
).Start;
end;
end;
end.
@twobob
Copy link
Author

twobob commented Jan 2, 2024

image
OPEN AI

image
LM STUDIO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment