Skip to main content
Version: 0.12

Unreal: Embeddings & Search

Embeddings, vector search, and hybrid search in Unreal Engine. For the concepts behind each step see the guide pages on Embedding Models, Vector Search, and Hybrid & Lexical Search.

KV-store entries and queries are JSON strings; the store takes vectors you produce with EmbedText, not raw text. embed_dim must match the model — 384 for MiniLM/bge-small, 768 for bge-base.

:::info Changed in 0.12.1 Embed(...) is now EmbedText(...), alongside new EmbedImage / EmbedAudio; ClassifierPredict(...) is now ClassifierPredictText(...), alongside ClassifierPredictImage / ClassifierPredictAudio. All are BlueprintCallable. There are no compatibility aliases — rename any 0.12.0 calls. :::

Embed text

const FString Model = TEXT("in-memory::sentence-transformers/all-MiniLM-L6-v2");

const FString Resp = Atelico->EmbedText(FString::Printf(TEXT(R"({
"model": "%s",
"input": ["The guard saw a thief enter the keep at midnight."]
})"), *Model));
// Parse Resp -> data[0].embedding into a TArray<float>.

// Or a direct cosine similarity between two strings:
float Sim = Atelico->Similarity(Model, TEXT("a knight's blade"), TEXT("a warrior's sword"));

Create a store, insert, query

// 1. Create
Atelico->CreateKvStore(TEXT(R"({
"store_id": "npc-memory",
"db_path": "./data/npc-memory",
"embed_dim": 384
})"));

// 2. Insert — key_embedding is the vector from Embed (EmbeddingCsv = comma-joined floats)
const FString Entries = FString::Printf(TEXT(R"([{
"id": "mem-001",
"key_text": "The guard saw a thief enter the keep at midnight.",
"key_embedding": [%s],
"facets": {"npc_id": "guard-01", "location": "keep"},
"priority": 0.8
}])"), *EmbeddingCsv);
Atelico->KvStoreInsert(TEXT("npc-memory"), Entries);

// 3. Query — embed the question first, pass the vector
const FString Query = FString::Printf(TEXT(R"({
"query_embedding": [%s],
"facet_filters": {"npc_id": "guard-01"},
"limit": 5
})"), *QueryEmbeddingCsv);
FString Results = Atelico->KvStoreQuery(TEXT("npc-memory"), Query);
// Results: JSON array of {id, key_text, similarity, priority, combined_score}

uint64 Count = Atelico->KvStoreCount(TEXT("npc-memory"), TEXT(""));
Atelico->KvStoreCreateFtsIndex(TEXT("npc-memory"), TEXT("key_text"));

const FString Hybrid = FString::Printf(TEXT(R"({
"embeddings": [%s],
"text": "thief keep",
"fts_column": "key_text",
"limit": 5,
"vector_weight": 0.6,
"lexical_weight": 0.4
})"), *QueryEmbeddingCsv);
FString Response = Atelico->KvStoreHybridQuery(TEXT("npc-memory"), Hybrid);

See Hybrid & Lexical Search for the scoring details and the lexical-only (KvStoreLexicalQuery) variant.

Embed images and audio

EmbedImage and EmbedAudio take a base64-encoded file in the request and return the same response shape as EmbedText. With a cross-modal model id the image/audio vectors share the text space.

// Image — model ids: "clip", "siglip", "dinov2-small", "dinov2-large"
const FString ImgResp = Atelico->EmbedImage(FString::Printf(TEXT(R"({
"model": "clip",
"image": "%s"
})"), *ImageBase64));

// Audio — model id "clap". WAV / RIFF only (not MP3/FLAC).
const FString AudResp = Atelico->EmbedAudio(FString::Printf(TEXT(R"({
"model": "clap",
"audio": "%s"
})"), *AudioBase64));
// Parse resp -> data[0].embedding into a TArray<float>.

EmbedImage / EmbedAudio read the model from the on-device cache and never launch a silent blocking download — gate with is_model_cached(ModelId) and model_download_async(ModelId) first. See Multimodal Embedding for the model table.

Classify text, images, and audio

ClassifierLoad registers a trained classifier; the ClassifierPredict* calls return a JSON string with a top label, its probability, and a top list of the top-k {label, probability} pairs.

Atelico->ClassifierLoad(TEXT("steering"), TEXT("/abs/path/to/models/steering"));

// top_k is optional: omit for all labels ranked, 0 for just the top label
// (empty `top` list), N for the N best.
const FString TextResult = Atelico->ClassifierPredictText(TEXT(R"({
"model_id": "sentiment", "text": "I love this!", "top_k": 3
})"));

// Image classifier over DINOv2-Small embeddings (labels e.g. "left"/"right").
const FString ImageResult = Atelico->ClassifierPredictImage(TEXT(R"({
"model_id": "steering", "image_path": "/abs/path/frame.png", "top_k": 0
})"));

// Audio classifier — WAV / RIFF input only.
const FString AudioResult = Atelico->ClassifierPredictAudio(TEXT(R"({
"model_id": "ambience", "audio_path": "/abs/path/clip.wav", "top_k": 3
})"));

The image/audio embedder the classifier was trained with ("dinov2-small", "clap") must be cached first. See Classifiers for training and the full result schema.