LoginSignup
2
2

More than 1 year has passed since last update.

GPT4でc++ほぼ書けない人がwhisperAIを叩けるプラグインを作ってみた

Posted at

GPT4を使ってc++ほぼ書けない人がunrealengineのプラグインを作ってみた

タイトルにある通りc++を触る必要性がいままでなかったので触ってこなかったのですが、apiを叩く上でue4だと既存のプラグインだとstringでしかリクエストを送れない問題がありました。ですので今回はgpt4君にwhisper APIを使って文字起こししてくれるpluginを作ってもらいました。

完成したもの

先にお見せします
スクリーンショット 2023-03-15 202713.png

入力したワード-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;
}

この関数を呼び出す際に、結果が成功したかどうかを判断することができます。ただし、この実装は同期的に実行されるため、リクエストの処理が終わるまでゲームが停止する可能性があります。

実行してみた

スクリーンショット 2023-03-15 194007.png

いわれた通りフリーズした

入力したワード-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のデモはこちらから

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2