UnrealOllamaConnect

UnrealOllamaConnect ist ein spezieller Blueprint-Node für Unreal Engine. Er nimmt einen Text-Prompt (Eingabe) entgegen, sendet ihn an das lokale KI-Modell Gemma4 (in unserem Fall das Modell gemma4:e4b) und leitet die generierte Antwort zurück an Unreal Engine weiter.

Voraussetzungen

  • Ollama muss installiert und im Hintergrund laufen (ollama serve)

  • Das Modell gemma4:e4b muss gepullt sein (ollama pull gemma4:e4b)

  • Visual Studio 2022 mit C++-Entwicklungskomponenten muss installiert sein

Installation

  1. Öffne dein Unreal-Projekt und füge eine neue C++-Klasse hinzu (Tools → New C++ Class).

  2. Wähle als Klassentyp Blueprint Function Library aus.

  3. Benenne die Klasse MyOllamaLibrary und setze sie auf public.

  4. Es öffnen sich eine .cpp und eine .h (Header) Datei. Kopiere den bereitgestellten Code jeweils in die richtige Datei.

  5. Ersetze MYWORLD_API mit deinem PROJEKTNAME_API in der Header Datei.
  6. Speichere und schließe dein Projekt.

    1. Falls Probleme auftreten: Klicke mit der rechten Maustaste auf deine .uproject-Datei und wähle Generate Visual Studio project files aus.
  7. Öffne die Datei
    /DeinProjekt/Source/DeinProjekt/DeinProjekt.Build.cs
    in einem Texteditor (z. B. Visual Studio) und ändere die folgende Zeile:

    Von:

    PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

    Zu:

    PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HTTP", "Json", "JsonUtilities" });
  8. Öffne Unreal Engine und Visual Studio erneut. Drücke in Unreal Strg + Alt + F11, um Live Coding zu starten und das Projekt zu kompilieren.

  9. Nach erfolgreichem Build findest du im Blueprint-Event-Graph die Funktion Ask Gemma (Async).

    Wichtig: Verwende ausschließlich die Async-Version. Die synchrone Version blockiert Unreal, während die Anfrage läuft, und kann den Editor zum Einfrieren bringen.

//MyOllamaLibrary.cpp
#include "MyOllamaLibrary.h"
#include "HttpModule.h"
#include "Interfaces/IHttpResponse.h"
#include "Json.h"
#include "JsonUtilities.h"

// Hilfsfunktion zum Bereinigen des Antwort-Texts
FString SanitizeOllamaResponse(const FString& RawText)
{
    FString CleanText = RawText;

    // Entferne Escape-Sequenzen
    CleanText = CleanText.Replace(TEXT("\\n"), TEXT("\n"));   // Zeilenumbruch
    CleanText = CleanText.Replace(TEXT("\\t"), TEXT("\t"));   // Tabulator
    CleanText = CleanText.Replace(TEXT("\\\""), TEXT("\""));  // Anführungszeichen
    CleanText = CleanText.Replace(TEXT("\\\\"), TEXT("\\"));  // Backslash

    // Entferne nicht druckbare Zeichen (ASCII 0-31, außer \n = 10, \r = 13, \t = 9)
    FString FilteredText;
    for (int32 i = 0; i < CleanText.Len(); i++)
    {
        TCHAR Char = CleanText[i];
        int32 CharCode = static_cast<int32>(Char);

        // Behalte normale Zeichen, Leerzeichen, Zeilenumbrüche und Tabs
        if (CharCode >= 32 || CharCode == 9 || CharCode == 10 || CharCode == 13)
        {
            FilteredText.AppendChar(Char);
        }
    }

    return FilteredText;
}

void UOllamaBPLibrary::AskGemmaAsync(const FString& Prompt, FOnOllamaResponse OnComplete)
{
    if (!FModuleManager::Get().IsModuleLoaded("HTTP"))
    {
        FModuleManager::Get().LoadModule("HTTP");
    }

    // HTTP Request erstellen
    TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();

    // JSON Payload erstellen
    TSharedPtr<FJsonObject> JsonPayload = MakeShareable(new FJsonObject);
    JsonPayload->SetStringField("model", "gemma4:e4b");  // oder "gemma3:latest"
    JsonPayload->SetStringField("prompt", Prompt);
    JsonPayload->SetBoolField("stream", false);

    // JSON serialisieren
    FString JsonString;
    TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonString);
    FJsonSerializer::Serialize(JsonPayload.ToSharedRef(), Writer);

    // Request konfigurieren
    Request->SetURL("http://localhost:11434/api/generate");
    Request->SetVerb("POST");
    Request->SetHeader("Content-Type", "application/json");
    Request->SetContentAsString(JsonString);

    // Timeout auf 60 Sekunden erhöhen (Standard ist ca. 20-30 Sekunden)
    Request->SetTimeout(240.0f);  // 240 Sekunden
    Request->SetActivityTimeout(120.0f);

    // Callback binden
    Request->OnProcessRequestComplete().BindStatic(&UOllamaBPLibrary::HandleHttpResponse, OnComplete);

    // Request senden
    if (!Request->ProcessRequest())
    {
        FOllamaResponse ErrorResponse;
        ErrorResponse.bSuccess = false;
        ErrorResponse.ErrorMessage = "Failed to send HTTP request";
        OnComplete.ExecuteIfBound(ErrorResponse);
    }
}

void UOllamaBPLibrary::HandleHttpResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FOnOllamaResponse OnComplete)
{
    FOllamaResponse Result;

    if (!bWasSuccessful || !Response.IsValid())
    {
        Result.bSuccess = false;
        Result.ErrorMessage = "Network error - make sure Ollama is running";
        OnComplete.ExecuteIfBound(Result);
        return;
    }

    if (Response->GetResponseCode() != 200)
    {
        Result.bSuccess = false;
        Result.ErrorMessage = FString::Printf(TEXT("HTTP Error: %d"), Response->GetResponseCode());
        OnComplete.ExecuteIfBound(Result);
        return;
    }

    // JSON Antwort parsen
    TSharedPtr<FJsonObject> JsonResponse;
    TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());

    if (FJsonSerializer::Deserialize(Reader, JsonResponse) && JsonResponse.IsValid())
    {
        if (JsonResponse->HasField("response"))
        {
            Result.bSuccess = true;
            FString RawResponse = JsonResponse->GetStringField("response");
            Result.ResponseText = SanitizeOllamaResponse(RawResponse);  // ← Bereinigen!
        }
        else
        {
            Result.bSuccess = false;
            Result.ErrorMessage = "Invalid response format from Ollama";
        }
    }
    else
    {
        Result.bSuccess = false;
        Result.ErrorMessage = "Failed to parse JSON response";
    }

    OnComplete.ExecuteIfBound(Result);
}

// Synchrone Version (nicht für Haupt-Thread!)
FOllamaResponse UOllamaBPLibrary::AskGemmaSync(const FString& Prompt)
{
    // Implementierung ähnlich wie AskGemmaAsync, aber mit FHttpRequest::ECompletionMode::Blocking
    // Achtung: Blockiert den Thread - nur in Hintergrund-Threads verwenden!

    FOllamaResponse Result;
    // ... (Implementierung analog zur async Version, aber mit Blocking)
    return Result;
}

// MyOllamaLibrary.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Http.h"
#include "MyOllamaLibrary.generated.h"

// Struktur für die Antwort
USTRUCT(BlueprintType)
struct FOllamaResponse
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadOnly)
    FString ResponseText;

    UPROPERTY(BlueprintReadOnly)
    bool bSuccess;

    UPROPERTY(BlueprintReadOnly)
    FString ErrorMessage;
};

// Delegaten für asynchrone Callbacks
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnOllamaResponse, const FOllamaResponse&, Response);

// MYWORLD_API mit deinem Projektnamen "PROJEKTNAME_API" ersetzen!!! Wichtig 
UCLASS()
class MYWORLD_API UOllamaBPLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

public:
    // Asynchrone Funktion für Blueprints (empfohlen)
    UFUNCTION(BlueprintCallable, Category = "Ollama", meta = (DisplayName = "Ask Gemma (Async)"))
    static void AskGemmaAsync(const FString& Prompt, FOnOllamaResponse OnComplete);

    // Synchrone Version (blockiert, nicht für Haupt-Thread empfohlen)
    UFUNCTION(BlueprintCallable, Category = "Ollama", meta = (DisplayName = "Ask Gemma (Sync - Blocking)"))
    static FOllamaResponse AskGemmaSync(const FString& Prompt);

private:
    static void HandleHttpResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FOnOllamaResponse OnComplete);
};

WordPress Cookie Plugin von Real Cookie Banner