GPT4を使ってc++ほぼ書けない人がunrealengineのプラグインを作ってみた
タイトルにある通りc++を触る必要性がいままでなかったので触ってこなかったのですが、apiを叩く上でue4だと既存のプラグインだとstringでしかリクエストを送れない問題がありました。ですので今回はgpt4君にwhisper APIを使って文字起こししてくれるpluginを作ってもらいました。
完成したもの
入力したワード-1
ヘッダーファイル
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"
/***
*/UCLASS()
class MYPROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
};
と
cppファイル
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyBlueprintFunctionLibrary.h"
を修正してwhisper APIのhttpリクエストを送れるようにしてください。whisperは
curl --request POST
--url https://api.openai.com/v1/audio/transcriptions
--header 'Authorization: Bearer TOKEN'
--header 'Content-Type: multipart/form-data'
--form file=@/path/to/file/openai.mp3
--form model=whisper-1
のように投稿できます。今回はTOKENとfileのパスを変更できるようにしてください
送られてきたもの-1
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Whisper API")
static void SendWhisperAPIRequest(FString Token, FString FilePath);
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyBlueprintFunctionLibrary.h"
#include "Runtime/Online/HTTP/Public/Http.h"
void UMyBlueprintFunctionLibrary::SendWhisperAPIRequest(FString Token, FString FilePath)
{
FHttpModule* Http = &FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
Request->SetVerb("POST");
Request->SetURL("https://api.openai.com/v1/audio/transcriptions");
Request->SetHeader("Authorization", FString::Printf(TEXT("Bearer %s"), *Token));
Request->SetHeader("Content-Type", "multipart/form-data");
// Load file
TArray<uint8> FileContent;
if (!FFileHelper::LoadFileToArray(FileContent, *FilePath))
{
UE_LOG(LogTemp, Error, TEXT("Failed to load file: %s"), *FilePath);
return;
}
// Add file content to request
FString Boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
FString PreFileBoundary = FString::Printf(TEXT("--%s\r\nContent-Disposition: form-data; name=\"file\"; filename=\"openai.mp3\"\r\nContent-Type: audio/mpeg\r\n\r\n"), *Boundary);
FString PostFileBoundary = FString::Printf(TEXT("\r\n--%s\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nwhisper-1\r\n--%s--\r\n"), *Boundary, *Boundary);
Request->SetHeader("Content-Type", FString::Printf(TEXT("multipart/form-data; boundary=%s"), *Boundary));
TArray<uint8> RequestContent;
RequestContent.Append((uint8*)TCHAR_TO_UTF8(*PreFileBoundary), PreFileBoundary.Len());
RequestContent.Append(FileContent);
RequestContent.Append((uint8*)TCHAR_TO_UTF8(*PostFileBoundary), PostFileBoundary.Len());
Request->SetContent(RequestContent);
Request->OnProcessRequestComplete().BindLambda([](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (!bWasSuccessful || !Response.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("Whisper API request failed"));
return;
}
UE_LOG(LogTemp, Log, TEXT("Whisper API response: %s"), *Response->GetContentAsString());
});
Request->ProcessRequest();
}
入力したワード-2
CompilerResultsLog: Error: MyBlueprintFunctionLibrary.cpp.obj : error LNK2019: ???????̊O???V???{?? "__declspec(dllimport) public: static class FHttpModule & __cdecl FHttpModule::Get(void)" (_imp?Get@FHttpModule@@SAAEAV1@XZ) ???? "public: static void __cdecl UMyBlueprintFunctionLibrary::SendWhisperAPIRequest(class FString,class FString)"....
送られてきたもの-2
すみません。エラーが発生しているようです。このエラーは、FHttpModuleがリンクされていないために起こっています。プロジェクトのビルド設定でHTTPモジュールを追加することで解決できます。
入力したワード-3
成功したかの判定と、戻り値としてstringを出力して
送られてきたもの-3
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Whisper API")
static FString SendWhisperAPIRequest(FString Token, FString FilePath, bool& bWasSuccessful);
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyBlueprintFunctionLibrary.h"
#include "Runtime/Online/HTTP/Public/Http.h"
FString UMyBlueprintFunctionLibrary::SendWhisperAPIRequest(FString Token, FString FilePath, bool& bWasSuccessful)
{
FHttpModule* Http = &FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
Request->SetVerb("POST");
Request->SetURL("https://api.openai.com/v1/audio/transcriptions");
Request->SetHeader("Authorization", FString::Printf(TEXT("Bearer %s"), *Token));
Request->SetHeader("Content-Type", "multipart/form-data");
// Load file
TArray<uint8> FileContent;
if (!FFileHelper::LoadFileToArray(FileContent, *FilePath))
{
UE_LOG(LogTemp, Error, TEXT("Failed to load file: %s"), *FilePath);
bWasSuccessful = false;
return FString();
}
// Add file content to request
FString Boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
FString PreFileBoundary = FString::Printf(TEXT("--%s\r\nContent-Disposition: form-data; name=\"file\"; filename=\"openai.mp3\"\r\nContent-Type: audio/mpeg\r\n\r\n"), *Boundary);
FString PostFileBoundary = FString::Printf(TEXT("\r\n--%s\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nwhisper-1\r\n--%s--\r\n"), *Boundary, *Boundary);
Request->SetHeader("Content-Type", FString::Printf(TEXT("multipart/form-data; boundary=%s"), *Boundary));
TArray<uint8> RequestContent;
RequestContent.Append((uint8*)TCHAR_TO_UTF8(*PreFileBoundary), PreFileBoundary.Len());
RequestContent.Append(FileContent);
RequestContent.Append((uint8*)TCHAR_TO_UTF8(*PostFileBoundary), PostFileBoundary.Len());
Request->SetContent(RequestContent);
FString ResponseContent;
FEvent* Event = FGenericPlatformProcess::GetSynchEventFromPool(true);
Request->OnProcessRequestComplete().BindLambda([&](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccessful)
{
if (!bSuccessful || !Response.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("Whisper API request failed"));
bWasSuccessful = false;
}
else
{
ResponseContent = Response->GetContentAsString();
bWasSuccessful = true;
}
Event->Trigger();
});
Request->ProcessRequest();
Event->Wait();
FGenericPlatformProcess::ReturnSynchEventToPool(Event);
Event = nullptr;
return ResponseContent;
}
この関数を呼び出す際に、結果が成功したかどうかを判断することができます。ただし、この実装は同期的に実行されるため、リクエストの処理が終わるまでゲームが停止する可能性があります。
実行してみた
いわれた通りフリーズした
入力したワード-4
続けて
送られてきたもの-4
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"
DECLARE_DYNAMIC_DELEGATE_TwoParams(FWhisperAPIResponseDelegate, bool, bWasSuccessful, FString, ResponseContent);
UCLASS()
class MYPROJECT_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Whisper API")
static void SendWhisperAPIRequestAsync(FString Token, FString FilePath, FWhisperAPIResponseDelegate ResponseDelegate);
};
#include "MyBlueprintFunctionLibrary.h"
#include "Runtime/Online/HTTP/Public/Http.h"
void UMyBlueprintFunctionLibrary::SendWhisperAPIRequestAsync(FString Token, FString FilePath, FWhisperAPIResponseDelegate ResponseDelegate)
{
FHttpModule* Http = &FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();
Request->SetVerb("POST");
Request->SetURL("https://api.openai.com/v1/audio/transcriptions");
Request->SetHeader("Authorization", FString::Printf(TEXT("Bearer %s"), *Token));
Request->SetHeader("Content-Type", "multipart/form-data");
TArray<uint8> FileContent;
if (!FFileHelper::LoadFileToArray(FileContent, *FilePath))
{
UE_LOG(LogTemp, Error, TEXT("Failed to load file: %s"), *FilePath);
ResponseDelegate.ExecuteIfBound(false, FString());
return;
}
FString Boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
FString PreFileBoundary = FString::Printf(TEXT("--%s\r\nContent-Disposition: form-data; name=\"file\"; filename=\"openai.mp3\"\r\nContent-Type: audio/mpeg\r\n\r\n"), *Boundary);
FString PostFileBoundary = FString::Printf(TEXT("\r\n--%s\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\nwhisper-1\r\n--%s--\r\n"), *Boundary, *Boundary);
Request->SetHeader("Content-Type", FString::Printf(TEXT("multipart/form-data; boundary=%s"), *Boundary));
TArray<uint8> RequestContent;
RequestContent.Append((uint8*)TCHAR_TO_UTF8(*PreFileBoundary), PreFileBoundary.Len());
RequestContent.Append(FileContent);
RequestContent.Append((uint8*)TCHAR_TO_UTF8(*PostFileBoundary), PostFileBoundary.Len());
Request->SetContent(RequestContent);
Request->OnProcessRequestComplete().BindLambda([ResponseDelegate](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccessful)
{
if (!bSuccessful || !Response.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("Whisper API request failed"));
ResponseDelegate.ExecuteIfBound(false, FString());
}
else
{
FString ResponseContent = Response->GetContentAsString();
ResponseDelegate.ExecuteIfBound(true, ResponseContent);
}
});
Request->ProcessRequest();
}
↑動いた
まとめ
一回じゃ成功しませんがエラー文章コピペすれば大体直してくれます。
すごい時代になりましたね。
UE4のデモはこちらから