LoginSignup
2

posted at

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

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のデモはこちらから

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
What you can do with signing up
2