Unreal C++ · Plugin development · Native ABI
Reuse the full plugin from C++
The Blueprint layer is built on exported UObject classes, not a separate implementation. C++ gameplay code can create the same conversations, bind the same events, use memory/tools/MCP, and opt into the complete native SDK when it truly needs the lower layer.
Choose the highest useful surface
| Your code needs | Use | Why |
|---|---|---|
| One chat with automatic setup | ULiteRtLmQuickChat | Smallest ownership and event surface. |
| Multiple identities, memory, tools | ULiteRtLmAgent | Full product-facing conversation object. |
| Model status or Agent factory overloads | ULiteRtLmSubsystem | Shared Engine Subsystem and C++ conveniences. |
| MCP routes and schema lifecycle | ULiteRtLmMcpGateway | Owns routing, pending calls, and history replay. |
| Direct upstream handles/functions | LiteRtLmNativeSdk.h | Complete official C API plus header-only C++ ownership/overloads. |
1. Add the Unreal module dependency
In your gameplay module or plugin module .Build.cs:
PrivateDependencyModuleNames.AddRange(new[]
{
"Core",
"CoreUObject",
"Engine",
"LiteRTLMUnreal"
});
Use PublicDependencyModuleNames instead if your own public headers expose LiteRT-LM types. The module publishes the scenario headers and native SDK include path.
2. Create one conversation in an Actor
Declare an owned Quick Chat and dynamic handlers in your Actor header:
#include "LiteRtLmQuickChat.h"
#include "LiteRtLmUnrealApi.h"
UPROPERTY(Transient)
TObjectPtr<ULiteRtLmQuickChat> QuickChat;
UFUNCTION()
void HandleAnswer(const FLiteRtLmResult& Result);
UFUNCTION()
void HandleChatError(const FLiteRtLmError& Error);
Create once—typically in BeginPlay—bind events before Ask, then keep using the same object:
#include "LiteRtLmBlueprintLibrary.h"
void ALocalChatActor::BeginPlay()
{
Super::BeginPlay();
QuickChat = ULiteRtLmBlueprintLibrary::CreateQuickChat(
this,
TEXT("Answer concisely in the user's language."));
if (!IsValid(QuickChat))
{
return;
}
QuickChat->OnAnswer.AddDynamic(this, &ALocalChatActor::HandleAnswer);
QuickChat->OnError.AddDynamic(this, &ALocalChatActor::HandleChatError);
QuickChat->AskOnce(TEXT("Hello from Unreal C++."));
}
void ALocalChatActor::HandleAnswer(const FLiteRtLmResult& Result)
{
UE_LOG(LogTemp, Display, TEXT("AI: %s"), *Result.Text);
}
The single-argument AskOnce and AskStreaming overloads use default request options. Use the two-argument overload when a particular request needs custom FLiteRtLmAskOptions.
3. Create multiple independent Agents
Get the shared Engine Subsystem and use its C++ overloads:
#include "LiteRtLmAgent.h"
#include "LiteRtLmSubsystem.h"
ULiteRtLmSubsystem* Runtime =
GEngine->GetEngineSubsystem<ULiteRtLmSubsystem>();
ULiteRtLmAgent* Judge = Runtime
? Runtime->CreateAgent(
TEXT("Judge"),
TEXT("Apply the game rules and return structured decisions."))
: nullptr;
if (IsValid(Judge))
{
Judge->OnCompleted.AddDynamic(this, &AMatchDirector::HandleJudgeResult);
Judge->Ask(TEXT("Evaluate the current round."));
}
CreateAgent has C++ overloads for display name; display name + system prompt; and display name + system prompt + tool declaration JSON. Ask, AskMessagesJson, and SubmitToolResult also have default-options overloads.
Store every Agent in a UPROPERTY array/map owned by the match or gameplay system. For public events, append the actual event to each relevant Agent’s memory; for private events, call only the intended recipients.
Ownership and asynchronous rules
- Keep UObjects referenced: use
UPROPERTY/TObjectPtrfields. A temporary local pointer is not an ownership plan. - Bind before Ask: completion and error delivery is asynchronous.
- Use Request IDs: Ask returns identity immediately; the event carries the terminal result later.
- Do not overlap requests on one Agent: check
IsBusy()or handle the rejection error. - One model, many conversations: Agents share the runtime and serial inference queue, but never share canonical memory unless your code copies messages.
- Close explicitly at a gameplay boundary: especially when replacing a match/scene with outstanding requests.
Use the complete native SDK when required
Include the umbrella header:
#include "LiteRtLmNativeSdk.h"
It includes the complete upstream C declarations from c/engine.h. In C++, it also includes the exception-free header-only litert_lm_native.hpp ownership and overload layer. Every owner exposes get() and release(), so newly added upstream functions remain reachable without waiting for another wrapper method.
LiteRtLm_Native.lib. A Win64 consumer that directly calls native symbols must add the import library from Source/ThirdParty/LiteRtLm/Binaries/Win64 and ship its sibling runtime bundle.Example for a project module whose ModuleDirectory is under Project/Source/YourModule:
if (Target.Platform == UnrealTargetPlatform.Win64)
{
string ProjectRoot = Path.GetFullPath(
Path.Combine(ModuleDirectory, "..", ".."));
string LiteRtLmBin = Path.Combine(
ProjectRoot,
"Plugins", "LiteRT-LM-Unreal", "Source", "ThirdParty",
"LiteRtLm", "Binaries", "Win64");
PublicAdditionalLibraries.Add(
Path.Combine(LiteRtLmBin, "LiteRtLm_Native.lib"));
}
Adjust discovery when the consuming module lives in another plugin. Keep the higher UObject layer for Unreal lifetime, delegates, memory, MCP, and packaging unless direct native control is the actual requirement.
Ship to Windows 11 and Android
You do not need to compile the UE5 project just to run the complete demo. Open the LiteRTDemo v5.1.0 release and download the assembly script. It downloads every part, verifies the SHA-256 of each part and of the final artifact, and only then produces the runnable file.
1. Download and assemble a package
Put Assemble-LiteRTDemo-v5.1.0.ps1 in an empty directory, open PowerShell there, and run one of these commands:
# Android ARM64: produces LiteRTDemo-Android-Shipping-arm64.apk
powershell -ExecutionPolicy Bypass -File .\Assemble-LiteRTDemo-v5.1.0.ps1 -Target Android -Download
# Windows 11: produces LiteRTDemo-Windows-v5.1.0.zip
powershell -ExecutionPolicy Bypass -File .\Assemble-LiteRTDemo-v5.1.0.ps1 -Target Windows -Download
The Android package targets ARM64 only. Extract the Windows package and run LiteRTDemo.exe. The release also includes manifests and SHA256SUMS.txt, so there is no manual joining or guesswork about file integrity.
2. Read real available memory on the phone
Before creating the native model, v5.1.0 queries total physical memory, currently available memory, and physical memory used by the process. It then conservatively caps the configured context limit to a 4K, 8K, 16K, or 32K tier. Multiple Agents still share one runtime and model; multiple conversations do not load multiple model copies.
#include "LiteRtLmSubsystem.h"
ULiteRtLmSubsystem* Runtime =
GetGameInstance()->GetSubsystem<ULiteRtLmSubsystem>();
const FLiteRtLmMemoryPlan Plan = Runtime->GetMemoryPlan();
UE_LOG(LogTemp, Display,
TEXT("LiteRT-LM RAM: total=%lld MB available=%lld MB process=%lld MB, context=%d/%d, %s"),
Plan.TotalPhysicalMB,
Plan.AvailablePhysicalMB,
Plan.ProcessUsedPhysicalMB,
Plan.EffectiveMaxContextTokens,
Plan.ConfiguredMaxContextTokens,
*Plan.PolicySummary);
Auto Tune Context for Device Memory is enabled by default in Project Settings; its config key is bAutoTuneAndroidContext=True. Disable it only after stress-testing the exact target devices. The memory plan reduces context-cache pressure, but it cannot hide a driver, GPU delegate, or native ABI crash; collect the system crash evidence as well.
3. Export evidence after an Android inference crash
The release includes Collect-LiteRTDemoAndroidCrash.ps1. It uses ADB to collect app metadata, device memory, GPU/driver information, Logcat, and Android process-exit reasons. Connect the phone, allow USB debugging, then run:
powershell -ExecutionPolicy Bypass -File .\Collect-LiteRTDemoAndroidCrash.ps1
Reproduce the crash once, finish collection as instructed, and share the resulting directory as an archive. The logs can contain the device model, Android version, process metadata, and application output; review or redact anything you do not want to share.
4. Package your own plugin project
- The consuming module depends on
LiteRTLMUnreal. - UObject references are reflected and survive garbage collection.
- Dynamic handlers are
UFUNCTIONs with exact delegate signatures. - Win64/Android target binaries and the model are present in the staged build.
- Direct native callers explicitly link the import library on supported targets.
- A packaged smoke test creates one conversation, receives one completion, then sends a second message through the same object.