Skip to content

Commit

Permalink
Merge pull request #86 from lucoiso/development
Browse files Browse the repository at this point in the history
HttpGPT v1.5.7
  • Loading branch information
lucoiso committed Aug 28, 2023
2 parents dbe5b06 + e27fd53 commit 00e3c67
Show file tree
Hide file tree
Showing 15 changed files with 291 additions and 36 deletions.
4 changes: 2 additions & 2 deletions HttpGPT.uplugin
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"FileVersion": 3,
"Version": 19,
"VersionName": "1.5.6",
"Version": 20,
"VersionName": "1.5.7",
"FriendlyName": "HttpGPT - GPT Integration",
"Description": "HttpGPT is an Unreal Engine plugin that facilitates integration with OpenAI's GPT based services (ChatGPT and DALL-E) through asynchronous REST requests, making it easy for developers to communicate with these services. HttpGPT also includes new Editor Tools to integrate Chat GPT and DALL-E image generation directly in the Engine.",
"Category": "Game Features",
Expand Down
66 changes: 55 additions & 11 deletions Source/HttpGPTChatModule/Private/Tasks/HttpGPTChatRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,35 @@
#if WITH_EDITOR
UHttpGPTChatRequest* UHttpGPTChatRequest::EditorTask(const TArray<FHttpGPTChatMessage>& Messages, const FHttpGPTChatOptions Options)
{
UHttpGPTChatRequest* const NewAsyncTask = SendMessages_CustomOptions(GEditor->GetEditorWorldContext().World(), Messages, FHttpGPTCommonOptions(), Options);
UHttpGPTChatRequest* const NewAsyncTask = SendMessages_CustomOptions(GEditor->GetEditorWorldContext().World(), Messages, TArray<FHttpGPTFunction>(), FHttpGPTCommonOptions(), Options);
NewAsyncTask->bIsEditorTask = true;

return NewAsyncTask;
}
#endif

UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessage_DefaultOptions(UObject* const WorldContextObject, const FString& Message)
UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessage_DefaultOptions(UObject* const WorldContextObject, const FString& Message, const TArray<FHttpGPTFunction>& Functions)
{
return SendMessage_CustomOptions(WorldContextObject, Message, FHttpGPTCommonOptions(), FHttpGPTChatOptions());
return SendMessage_CustomOptions(WorldContextObject, Message, Functions, FHttpGPTCommonOptions(), FHttpGPTChatOptions());
}

UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessages_DefaultOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages)
UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessages_DefaultOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages, const TArray<FHttpGPTFunction>& Functions)
{
return SendMessages_CustomOptions(WorldContextObject, Messages, FHttpGPTCommonOptions(), FHttpGPTChatOptions());
return SendMessages_CustomOptions(WorldContextObject, Messages, Functions, FHttpGPTCommonOptions(), FHttpGPTChatOptions());
}

UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessage_CustomOptions(UObject* const WorldContextObject, const FString& Message, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions)
UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessage_CustomOptions(UObject* const WorldContextObject, const FString& Message, const TArray<FHttpGPTFunction>& Functions, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions)
{
return SendMessages_CustomOptions(WorldContextObject, { FHttpGPTChatMessage(EHttpGPTChatRole::User, Message) }, CommonOptions, ChatOptions);
return SendMessages_CustomOptions(WorldContextObject, { FHttpGPTChatMessage(EHttpGPTChatRole::User, Message) }, Functions, CommonOptions, ChatOptions);
}

UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessages_CustomOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions)
UHttpGPTChatRequest* UHttpGPTChatRequest::SendMessages_CustomOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages, const TArray<FHttpGPTFunction>& Functions, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions)
{
UHttpGPTChatRequest* const NewAsyncTask = NewObject<UHttpGPTChatRequest>();
NewAsyncTask->Messages = Messages;
NewAsyncTask->CommonOptions = CommonOptions;
NewAsyncTask->ChatOptions = ChatOptions;
NewAsyncTask->Functions = Functions;

NewAsyncTask->RegisterWithGameInstance(WorldContextObject);

Expand Down Expand Up @@ -86,7 +87,7 @@ bool UHttpGPTChatRequest::CanBindProgress() const

FString UHttpGPTChatRequest::GetEndpointURL() const
{
return FString::Format(TEXT("https://api.openai.com/{0}"), { UHttpGPTHelper::GetEndpointForModel(GetChatOptions().Model).ToString() });
return FString::Format(TEXT("{0}/{1}"), { GetCommonOptions().Endpoint, UHttpGPTHelper::GetEndpointForModel(GetChatOptions().Model, GetCommonOptions().bIsAzureOpenAI, GetCommonOptions().AzureOpenAIAPIVersion) });
}

const FHttpGPTChatOptions UHttpGPTChatRequest::GetChatOptions() const
Expand Down Expand Up @@ -153,6 +154,17 @@ FString UHttpGPTChatRequest::SetRequestContent()
}

JsonRequest->SetArrayField("messages", MessagesJson);

if (!Functions.IsEmpty())
{
TArray<TSharedPtr<FJsonValue>> FunctionsJson;
for (const FHttpGPTFunction& Iterator : Functions)
{
FunctionsJson.Add(Iterator.GetFunction());
}

JsonRequest->SetArrayField("functions", FunctionsJson);
}
}
else
{
Expand Down Expand Up @@ -352,18 +364,50 @@ void UHttpGPTChatRequest::DeserializeSingleResponse(const FString& Content)

if (const TSharedPtr<FJsonObject>* MessageObj; ChoiceObj->TryGetObjectField("message", MessageObj))
{
Choice->Message = FHttpGPTChatMessage(*(*MessageObj)->GetStringField("role"), *(*MessageObj)->GetStringField("content"));
if (FString RoleStr; (*MessageObj)->TryGetStringField("role", RoleStr))
{
Choice->Message.Role = RoleStr == "user" ? EHttpGPTChatRole::User : EHttpGPTChatRole::Assistant;
}

if (FString ContentStr; (*MessageObj)->TryGetStringField("content", ContentStr))
{
Choice->Message.Content = ContentStr;
}

if (const TSharedPtr<FJsonObject>* FunctionObj; (*MessageObj)->TryGetObjectField("function_call", FunctionObj))
{
if (FString FunctionNameStr; (*FunctionObj)->TryGetStringField("name", FunctionNameStr))
{
Choice->Message.FunctionCall.Name = *FunctionNameStr;
}
if (FString FunctionArgumentsStr; (*FunctionObj)->TryGetStringField("arguments", FunctionArgumentsStr))
{
Choice->Message.FunctionCall.Arguments = FunctionArgumentsStr;
}
}
}
else if (const TSharedPtr<FJsonObject>* DeltaObj; ChoiceObj->TryGetObjectField("delta", DeltaObj))
{
if (FString RoleStr; (*DeltaObj)->TryGetStringField("role", RoleStr))
{
Choice->Message.Role = RoleStr == "user" ? EHttpGPTChatRole::User : EHttpGPTChatRole::Assistant;
Choice->Message.Role = UHttpGPTHelper::NameToRole(*RoleStr);
}
else if (FString ContentStr; (*DeltaObj)->TryGetStringField("content", ContentStr))
{
Choice->Message.Content += ContentStr;
}

if (const TSharedPtr<FJsonObject>* FunctionObj; (*DeltaObj)->TryGetObjectField("function_call", FunctionObj))
{
if (FString FunctionNameStr; (*FunctionObj)->TryGetStringField("name", FunctionNameStr))
{
Choice->Message.FunctionCall.Name = *FunctionNameStr;
}
if (FString FunctionArgumentsStr; (*FunctionObj)->TryGetStringField("arguments", FunctionArgumentsStr))
{
Choice->Message.FunctionCall.Arguments += FunctionArgumentsStr;
}
}
}
else if (FString MessageText; ChoiceObj->TryGetStringField("text", MessageText))
{
Expand Down
17 changes: 9 additions & 8 deletions Source/HttpGPTChatModule/Public/Tasks/HttpGPTChatRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,24 @@ class HTTPGPTCHATMODULE_API UHttpGPTChatRequest : public UHttpGPTBaseTask
static UHttpGPTChatRequest* EditorTask(const TArray<FHttpGPTChatMessage>& Messages, const FHttpGPTChatOptions Options);
#endif

UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Default", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Message with Default Options"))
static UHttpGPTChatRequest* SendMessage_DefaultOptions(UObject* const WorldContextObject, const FString& Message);
UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Default", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Message with Default Options", AutoCreateRefTerm = "Functions"))
static UHttpGPTChatRequest* SendMessage_DefaultOptions(UObject* const WorldContextObject, const FString& Message, const TArray<FHttpGPTFunction>& Functions);

UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Default", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Messages with Default Options"))
static UHttpGPTChatRequest* SendMessages_DefaultOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages);
UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Default", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Messages with Default Options", AutoCreateRefTerm = "Functions"))
static UHttpGPTChatRequest* SendMessages_DefaultOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages, const TArray<FHttpGPTFunction>& Functions);

UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Custom", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Message with Custom Options"))
static UHttpGPTChatRequest* SendMessage_CustomOptions(UObject* const WorldContextObject, const FString& Message, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions);
UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Custom", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Message with Custom Options", AutoCreateRefTerm = "Functions"))
static UHttpGPTChatRequest* SendMessage_CustomOptions(UObject* const WorldContextObject, const FString& Message, const TArray<FHttpGPTFunction>& Functions, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions);

UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Custom", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Messages with Custom Options"))
static UHttpGPTChatRequest* SendMessages_CustomOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions);
UFUNCTION(BlueprintCallable, Category = "HttpGPT | Chat | Custom", meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject", DisplayName = "Send Messages with Custom Options", AutoCreateRefTerm = "Functions"))
static UHttpGPTChatRequest* SendMessages_CustomOptions(UObject* const WorldContextObject, const TArray<FHttpGPTChatMessage>& Messages, const TArray<FHttpGPTFunction>& Functions, const FHttpGPTCommonOptions CommonOptions, const FHttpGPTChatOptions ChatOptions);

UFUNCTION(BlueprintPure, Category = "HttpGPT | Chat")
const FHttpGPTChatOptions GetChatOptions() const;

protected:
TArray<FHttpGPTChatMessage> Messages;
TArray<FHttpGPTFunction> Functions;
FHttpGPTChatOptions ChatOptions;

virtual bool CanActivateTask() const override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ void UHttpGPTSettings::SetToDefaults()
{
CommonOptions.APIKey = NAME_None;
CommonOptions.User = NAME_None;
CommonOptions.bIsAzureOpenAI = false;
CommonOptions.Endpoint = TEXT("https://api.openai.com/");
CommonOptions.AzureOpenAIAPIVersion = TEXT("2023-05-15");

ChatOptions.Model = EHttpGPTChatModel::gpt35turbo;
ChatOptions.MaxTokens = 2048;
Expand Down
59 changes: 54 additions & 5 deletions Source/HttpGPTCommonModule/Private/Structures/HttpGPTChatTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,66 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(HttpGPTChatTypes)
#endif

FHttpGPTChatMessage::FHttpGPTChatMessage(const FName& Role, const FString& Content)
TSharedPtr<FJsonValue> FHttpGPTFunction::GetFunction() const
{
this->Role = UHttpGPTHelper::NameToRole(Role);
this->Content = Content;
TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
JsonObject->SetStringField("name", Name.ToString());
JsonObject->SetStringField("description", Description);

TSharedPtr<FJsonObject> ParametersObject = MakeShared<FJsonObject>();
ParametersObject->SetStringField("type", "object");

TSharedPtr<FJsonObject> PropertiesObject = MakeShared<FJsonObject>();
for (const FHttpGPTFunctionProperty& PropIt : Properties)
{
TSharedPtr<FJsonObject> PropertyObject = MakeShared<FJsonObject>();
PropertyObject->SetStringField("type", UHttpGPTHelper::PropertyTypeToName(PropIt.Type).ToString().ToLower());
PropertyObject->SetStringField("description", PropIt.Description);

TArray<TSharedPtr<FJsonValue>> EnumArr;
for (const FName& EnumIt : PropIt.Enum)
{
EnumArr.Emplace(MakeShared<FJsonValueString>(EnumIt.ToString()));
}
PropertyObject->SetArrayField("enum", EnumArr);

PropertiesObject->SetObjectField(PropIt.Name.ToString(), PropertyObject);
}

ParametersObject->SetObjectField("properties", PropertiesObject);

TArray<TSharedPtr<FJsonValue>> RequiredParams;
for (const FName& ReqIt : RequiredProperties)
{
RequiredParams.Emplace(MakeShared<FJsonValueString>(ReqIt.ToString()));
}

ParametersObject->SetArrayField("required", RequiredParams);
JsonObject->SetObjectField("parameters", ParametersObject);

return MakeShared<FJsonValueObject>(JsonObject);
}

FHttpGPTChatMessage::FHttpGPTChatMessage(const FName& InRole, const FString& InContent)
{
Role = UHttpGPTHelper::NameToRole(InRole);
Content = InContent;
}

TSharedPtr<FJsonValue> FHttpGPTChatMessage::GetMessage() const
{
TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
JsonObject->SetStringField("role", UHttpGPTHelper::RoleToName(Role).ToString().ToLower());
JsonObject->SetStringField("content", Content);

if (Role == EHttpGPTChatRole::Function)
{
JsonObject->SetStringField("name", FunctionCall.Name.ToString());
JsonObject->SetStringField("content", FunctionCall.Arguments);
}
else
{
JsonObject->SetStringField("content", Content);
}

return MakeShared<FJsonValueObject>(JsonObject);
}
Expand All @@ -46,4 +95,4 @@ void FHttpGPTChatOptions::SetDefaults()
FrequencyPenalty = Settings->ChatOptions.FrequencyPenalty;
LogitBias = Settings->ChatOptions.LogitBias;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@ void FHttpGPTCommonOptions::SetDefaults()
{
APIKey = Settings->CommonOptions.APIKey;
User = Settings->CommonOptions.User;
bIsAzureOpenAI = Settings->CommonOptions.bIsAzureOpenAI;
Endpoint = Settings->CommonOptions.Endpoint;
AzureOpenAIAPIVersion = Settings->CommonOptions.AzureOpenAIAPIVersion;
}
}
4 changes: 4 additions & 0 deletions Source/HttpGPTCommonModule/Private/Tasks/HttpGPTBaseTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ void UHttpGPTBaseTask::Activate()
UE_LOG(LogHttpGPT, Display, TEXT("%s (%d): Activating task"), *FString(__func__), GetUniqueID());

bIsTaskActive = true;
if (!CommonOptions.Endpoint.EndsWith(TEXT("/")))
{
CommonOptions.Endpoint += TEXT("/");
}

if (!CanActivateTask())
{
Expand Down
70 changes: 66 additions & 4 deletions Source/HttpGPTCommonModule/Private/Utils/HttpGPTHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ const FName UHttpGPTHelper::RoleToName(const EHttpGPTChatRole Role)

case EHttpGPTChatRole::System:
return "system";

case EHttpGPTChatRole::Function:
return "function";

default:
break;
}

return NAME_None;
Expand All @@ -105,10 +111,52 @@ const EHttpGPTChatRole UHttpGPTHelper::NameToRole(const FName Role)
{
return EHttpGPTChatRole::System;
}
else if (Role.IsEqual("function", ENameCase::IgnoreCase))
{
return EHttpGPTChatRole::Function;
}

return EHttpGPTChatRole::User;
}

const FName UHttpGPTHelper::PropertyTypeToName(const EHttpGPTPropertyType Type)
{
switch (Type)
{
case EHttpGPTPropertyType::Boolean:
return "bool";

case EHttpGPTPropertyType::Number:
return "number";

case EHttpGPTPropertyType::String:
return "string";

default:
break;
}

return NAME_None;
}

const EHttpGPTPropertyType UHttpGPTHelper::NameToPropertyType(const FName Type)
{
if (Type.IsEqual("bool", ENameCase::IgnoreCase))
{
return EHttpGPTPropertyType::Boolean;
}
else if (Type.IsEqual("number", ENameCase::IgnoreCase))
{
return EHttpGPTPropertyType::Number;
}
else if (Type.IsEqual("string", ENameCase::IgnoreCase))
{
return EHttpGPTPropertyType::String;
}

return EHttpGPTPropertyType::Boolean;
}

const TArray<FName> UHttpGPTHelper::GetAvailableGPTModels()
{
TArray<FName> Output;
Expand All @@ -124,25 +172,39 @@ const TArray<FName> UHttpGPTHelper::GetAvailableGPTModels()
return Output;
}

const FName UHttpGPTHelper::GetEndpointForModel(const EHttpGPTChatModel Model)
const FString UHttpGPTHelper::GetEndpointForModel(const EHttpGPTChatModel Model, const bool bIsAzureOpenAI, const FString& AzureOpenAIAPIVersion)
{
switch (Model)
{
case EHttpGPTChatModel::gpt4:
case EHttpGPTChatModel::gpt432k:
case EHttpGPTChatModel::gpt35turbo:
case EHttpGPTChatModel::gpt35turbo16k:
return "v1/chat/completions";
if (bIsAzureOpenAI)
{
return FString::Format(TEXT("/openai/deployments/{0}/chat/completions?api-version={1}"), { ModelToName(Model).ToString(), AzureOpenAIAPIVersion });
}
else
{
return "v1/chat/completions";
}

case EHttpGPTChatModel::textdavinci003:
case EHttpGPTChatModel::textdavinci002:
case EHttpGPTChatModel::codedavinci002:
return "v1/completions";
if (bIsAzureOpenAI)
{
return FString::Format(TEXT("/openai/deployments/{0}/completions?api-version={1}"), { ModelToName(Model).ToString(), AzureOpenAIAPIVersion });
}
else
{
return "v1/completions";
}

default: break;
}

return NAME_None;
return FString();
}

const bool UHttpGPTHelper::ModelSupportsChat(const EHttpGPTChatModel Model)
Expand Down
Loading

0 comments on commit 00e3c67

Please sign in to comment.