1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UnrealEngineからDifyのボットとチャットしてみる

Last updated at Posted at 2025-09-05

UnrealEngineでDifyとのAIチャットを実装してみたので内容をメモしておきます。

試した環境

  • UE5.5.4
  • Dify:1.7.2

試した内容

以下のようなBlueprintノードを用意して、質問してその回答をSSE(Server-Sent Events)で少しずつ受け取れるようにしました。

image.png

以下のC++クラスを用意してます。

ChatMessageFunction.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "HttpFwd.h"
#include "ChatMessageFunction.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FChatMessageProgress, bool, bResult, FString, ResponseMessage);

/**
 * 
 */
UCLASS()
class DIFYCLIENT_API UChatMessageFunction : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

public:
	UPROPERTY(BlueprintAssignable, Category = "DifyClient")
	FChatMessageProgress Progress;
	UPROPERTY(BlueprintAssignable, Category = "DifyClient")
	FChatMessageProgress Complete;

public:
	UChatMessageFunction(const FObjectInitializer& ObjectInitializer);

	UFUNCTION(BlueprintCallable, Category = "DifyClient", meta = (worldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"))
	static UChatMessageFunction* SendChatMessage(UObject* WorldContextObject, const FString& Message);

	virtual void Activate() override;

private:
	void OnResponseReceivedComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);

	void OnProcessResponse(FHttpRequestPtr Request, uint64 ByteSent, uint64 ByteReceived);

private:
	FString Message;

	int ResponseDataCount;
};

ChatMessageFunction.cpp
#include "ChatMessageFunction.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "HttpModule.h"
#include "Misc/Guid.h" // UUID生成用
#include "DifyClientBPLibrary.h"

UChatMessageFunction::UChatMessageFunction(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer), ResponseDataCount(0)
{
}

UChatMessageFunction* UChatMessageFunction::SendChatMessage(UObject* WorldContextObject, const FString& Message)
{
	UChatMessageFunction* Node = NewObject<UChatMessageFunction>();
	Node->RegisterWithGameInstance(WorldContextObject);
	Node->Message = Message;
	return Node;
}

void UChatMessageFunction::Activate()
{
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HttpRequest = FHttpModule::Get().CreateRequest();
	HttpRequest->SetURL(UDifyClientBPLibrary::GetBaseURL()  + TEXT("/chat-messages"));
	HttpRequest->SetVerb(TEXT("POST"));
	HttpRequest->SetHeader(TEXT("Authorization"), TEXT("Bearer ") + UDifyClientBPLibrary::GetApiKey());
	HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));

	TSharedPtr<FJsonObject> JsonRoot = MakeShareable(new FJsonObject);
	JsonRoot->SetArrayField(TEXT("inputs"), TArray<TSharedPtr<FJsonValue>>());
	JsonRoot->SetStringField(TEXT("query"), Message);
	JsonRoot->SetStringField(TEXT("response_mode"), TEXT("streaming"));
	JsonRoot->SetStringField(TEXT("user"), TEXT("test"));

	FGuid Guid = FGuid::NewGuid();
	FString GuidString = Guid.ToString(EGuidFormats::DigitsWithHyphens);
	JsonRoot->SetStringField(TEXT("conversation_id"), TEXT(""));

	FString OutPutString;
	TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutPutString);
	FJsonSerializer::Serialize(JsonRoot.ToSharedRef(), Writer);

	HttpRequest->SetContentAsString(OutPutString);

	HttpRequest->OnProcessRequestComplete().BindUObject(this, &UChatMessageFunction::OnResponseReceivedComplete);
	HttpRequest->OnRequestProgress64().BindUObject(this, &UChatMessageFunction::OnProcessResponse);
	if (!HttpRequest->ProcessRequest())
	{
		Complete.Broadcast(false, TEXT("Failed to ProcessRequest"));
	}
}

void UChatMessageFunction::OnResponseReceivedComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (Response.IsValid())
	{
		bool bResult = Response->GetResponseCode() == 200;
		Complete.Broadcast(bResult, bResult ? TEXT("") : Response->GetContentAsString());
	}
	else
	{
		Complete.Broadcast(bWasSuccessful, TEXT(""));
	}


	SetReadyToDestroy();
}

void UChatMessageFunction::OnProcessResponse(FHttpRequestPtr Request, uint64 ByteSent, uint64 ByteReceived)
{
	FHttpResponsePtr Response = Request->GetResponse();
	if (Response.IsValid()) {
		FString ResponseStr = Response->GetContentAsString();
		TArray<FString> ResponseArr;
		const int32 ArrNum = ResponseStr.ParseIntoArray(ResponseArr, TEXT("\n\n"));
		FString ProgressStr;
		bool bReceive = false;
		for (int i = ResponseDataCount; i < ArrNum; i++)
		{
			FString& Line = ResponseArr[i];
			// "data:"を先頭から削除
			if (Line.StartsWith(TEXT("data:"))) 
			{
				Line = Line.RightChop(5).TrimStart();
			}
			else 
			{
				continue;
			}

			// JSONパース
			TSharedPtr<FJsonObject> JsonObject;
			TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Line);
			if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid()) {
				continue;
			}

			FString Event = JsonObject->GetStringField("event");
			if (Event == "agent_message") {
				FString Answer = JsonObject->GetStringField("answer");
				ProgressStr = ProgressStr + Answer;
				bReceive = true;
				UE_LOG(LogTemp, Log, TEXT("Progress:%s"), *Answer);
			}
			else
			{
				UE_LOG(LogTemp, Log, TEXT("Progress:%s"), *Line);
			}
		}

		if (bReceive) 
		{
			Progress.Broadcast(true, ProgressStr);
		}

		ResponseDataCount = ArrNum;
	}
}
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?