Search by Meaning, Not Metadata: Semantic Image Filtering with 4D.Vector

Your users don’t think in filenames or folder hierarchies. They think in ideas.

  • “A robot painted in watercolor.”
  • “A sunny beach filled with color.”
  • “Something that feels like Mona Lisa… but from the future.”

It doesn’t matter if that idea comes from an image, a customer order, an email, or a 4D Write Pro document — the challenge is the same: how do you deliver results that match intent, not just keywords?

With 4D.Vector and 4D AI Kit, your application can finally make sense of meaning. In this post, we’ll illustrate it with semantic image similarity search. And here’s the key: we’re not really working with raw images at all — we’re working with their descriptions. The very same approach works for any kind of text data in your application.

DEMO_AI_Vector_ImageSearch

Why vectors + AI change everything

Traditional search has one job: match exact terms. But your users don’t always know what exact term to use. They describe things. They express intent. They think in messy, beautiful, human language.

To make search work the way your users think, you need more than string matching. You need understanding — and that’s where vector embeddings come in.

So, What Exactly Is an Embedding?

An embedding is how you turn messy, unstructured input — a sentence, a prompt, or image — into a structured vector of numbers. It’s not just a list of tokens or keywords. It’s a mathematical snapshot of meaning.

When you send the phrase “a robot painted in watercolor” to OpenAI’s text-embedding-ada-002 model, it returns a few hundred floating-point values. Each one captures something: the topic, the mood, the style, the semantic relationships baked into that prompt. Similar phrases produce similar vectors. Dissimilar ones? They land further apart.

Think of it as placing meaning on a map. The closer two phrases are in that high-dimensional space, the more alike they are — regardless of wording. That’s the foundation for building search, recommendations, clustering, and categorization based on meaning.

A Real Use Case: Image Search by Meaning

In our demo page, you can let users search semantically. They type a prompt or pick one from a list, and your app returns images ranked by how well they match the idea.

The UI is minimal on purpose:

  • A text field for custom prompts
  • A dropdown of predefined prompts
  • A matrix that displays images, sorted by similarity to the prompt

Here’s the key: we’re not vectorizing the raw images. Instead, each image is analyzed by AIKit’s OpenAIVision, which generates a caption and description in natural language. That description is what gets embedded into a 4D.Vector and stored.

This way, everything is compared on the level of meaning, not pixels.

Note: This example uses 4D Qodly Pro, but the same logic applies perfectly to standard 4D forms too. In fact, all the code runs inside 4D methods and functions. The only difference is how you choose to display the results — in a classic 4D form or a Qodly page.

How You generate the vector

When someone types a prompt and clicks “Use Custom Prompt“, 4D runs the calculate() function from the VectorManagement class:

exposed shared Function calculate($prompt : Text) : cs.PicturesSelection

This kicks off by grabbing your OpenAI API key from memory (Storage.OpenAI.key) — which should be submitted from the first input field in the Info tab. If you skipped that and still clicked Use Custom Prompt, a modal will appear asking you to enter your key before continuing:

If (Storage.OpenAI.key#"")
	$apiKey:=Storage.OpenAI.key
Else 
	$apiModal:=Web Form.setApiKeyModal
	$apiModal.show()
End if 

Note: This example uses OpenAI for AI operations, but you can use any OpenAI-compatible provider — even a local model with Ollama. We’re using OpenAI here because it’s simple to set up and requires no local installation. If you want to follow along, request an OpenAI key here.

Next, you call the generateVector() function inside the AIManagement class:

var $vector := cs.AIManagement.new($apiKey).generateVector($prompt)

What’s happening here?

You’re using OpenAI’s text-embedding-ada-002 model — optimized for short text — to turn the prompt into a high-dimensional vector. By using the .create() method to send the prompt and the text-embedding-ada-002 model as a request to OpenAI and returns a JSON structure containing the vector. That vector is then wrapped in a 4D.Vector object, giving us something we can compare and sort natively in 4D.

property clientAI : Object

Class constructor($openAIKey : Text)
  This.clientAI := cs.AIKit.OpenAI.new($openAIKey)

Function generateVector($text : Text) : 4D.Vector
  var $model := "text-embedding-ada-002"
  var $result := This.clientAI.embeddings.create($text; $model)
  return $result.vector

At this point, you have translated the user’s prompt into a structured format that 4D can use for semantic comparison.

From Prompt to Ranked Results

With your vector in hand, now you compare it to all your stored image-description vectors in the database:

var $pictureList := This._calculateVectors($vector)

And inside _calculateVectors, here’s what happens: 

For each ($picture; $pictureList)
  $picture.cosineSimilarity := $vector.cosineSimilarity($picture.vector)
  $picture.dotSimilarity := $vector.dotSimilarity($picture.vector)
  $picture.euclideanDistance := $vector.euclideanDistance($picture.vector)
  $picture.save()
End for each 

Each similarity measure gives you a different lens:

  • Cosine Similarity measures how closely two vectors point in the same direction. Ideal for ranking semantic similarity.
  • Dot Product accounts for both direction and magnitude. It’s useful when you want to factor in how “strongly” two vectors align.
  • Euclidean Distance is the raw distance between two points in space. The smaller the distance, the more similar they are.

If you’re new to vector math in 4D, the first blog post in this series covers how to create and compare vectors using these methods and when to use each.

Once all similarity scores are calculated and stored on the image entities, you sort by cosine similarity and return the results: 

return $pictureList.orderBy("cosineSimilarity desc")

This means the most conceptually relevant images appear first, regardless of their filename, description, or metadata.

What If the User Chooses a Predefined Prompt?

If someone chooses a prompt from the dropdown and clicks Use Selected Prompt, you skip the embedding step entirely:

exposed shared Function calculateWithSelectedPrompt($prompt : cs.PromptsEntity) : cs.PicturesSelection
  var $pictureList:=This._calculateVectors($prompt.Vector)
  return $pictureList.orderBy("cosineSimilarity desc")

No extra OpenAI call is needed — the prompt’s vector is already saved in the database. You simply reuse the 4D.Vector stored in the Prompts dataclass when the prompt was first created.

Just like image descriptions, each prompt is vectorized once, then stored for reuse. That gives you:

  • No API call → Instant results, no network delay

  • No token cost → More budget-friendly, especially at scale

  • Works offline → Everything runs locally, no dependency on an external service

FROM IMAGE UPLOAD TO VECTORIZED DESCRIPTION

When someone uploads an image, you don’t embed the pixels. Instead, you describe the image with OpenAI Vision, and embed that description.

In the vectorizeImageDescription function, two prompts are defined: one for a short caption, and one for a fuller description. Both are crafted to return single, fluent sentences optimized for embeddings:

// --- Prompts (single, fluent, embedding-friendly) ---
$captionPrompt:="You are a precise visual captioner. "+\
	"Write one short, clear English sentence (12–20 words) that summarizes the main subject of the image. "+\
	"Keep it straightforward and descriptive, not poetic. Output only the sentence."

$descriptionPrompt:="You are a detailed scene describer. "+\
	"Write a longer English paragraph (80–150 words) that expands on the caption. "+\
	"Include context, secondary elements (icons, arrows, checklists, text, logos), and atmosphere so nothing important is missed. "+\
	"Avoid repeating the caption verbatim. Output only the description."

Using these prompts, OpenAI Vision analyzes the image. This happens in two steps:

  • .create() instantiates a VisionHelper tied to the uploaded image.
  • .prompt() sends a natural-language instruction along with that image to the OpenAI Chat API, returning a caption or description.
// --- Get caption ---
$caption:=$client.chat.vision.create($imageData).prompt($captionPrompt).choice.message.text
	
// --- Get description ---
$description:=$client.chat.vision.create($imageData).prompt($descriptionPrompt).choice.message.text

At this point, you have both a caption and a description. These are saved together with the picture itself, and most importantly, the description is vectorized for semantic search:

// --- Save entity ---
$pictureEntity:=ds.Pictures.new()
$pictureEntity.picture:=$imageToUpload.picture
$pictureEntity.prompt:=$caption
$pictureEntity.description:=$description
$pictureEntity.vector:=cs.AIManagement.new($apiKey).generateVector($description+$caption)
$status:=$pictureEntity.save()

What you see in the ui

Once the comparison is done, your matrix updates instantly. Images are ranked based on similarity score.

  • You search for: “a watercolor painting of a robot” — and see images with soft textures and robotic forms:

Visual results of a semantic AI image search using the prompt 'a watercolor painting of a robot,' showing six top-matching artworks with cosine similarity scores in a futuristic 4D interface.

  • You switch to: “a vibrant and sunny beach scene with colorful elements” — and get a totally different, but equally relevant, result set:

Semantic image search results for the prompt 'a vibrant and sunny beach scene with colorful elements,' ranked by AI using vector comparisons in a real-time 4D interface.

Whether typed or uploaded, you’re always comparing ideas — not pixels, not filenames.

What You Get as a Developer

Using vectors with AI unlocks real, immediate value in your 4D applications:

  • Smarter search: Let users search by meaning, not keywords. No tags, no filters — just relevant results.

  • Native logic: Everything runs inside 4D. No need for external search engines or third-party tools.

  • Reusable pattern: Works for images, documents, products, tickets — anything you want to compare by concept.

  • Low setup, high impact: A few lines of code give you ranking, filtering, and recommendations powered by AI.

  • Cheaper + faster: Embed once, store the vector, reuse forever. No repeated API calls or token costs.

  • AI-ready foundation: You’re not just adding search — you’re laying the groundwork for chatbots, recommendations, and smarter workflows.

With 4D.Vector and 4D AI Kit, you don’t need to build intelligence — you plug it in.

Final Thoughts

You’re not bolting AI onto your app — you’re building intelligence into your core logic.

With 4D.Vector and 4D AI Kit, you give your app the ability to understand intent, not just input. To surface meaning, not just matches.

The tools are ready. The code is light. The value is real. All you need now… is a prompt.

Avatar
Product Marketing Manager – Basma joined 4D in 2019 and grew into her current role by working across development, documentation, and content strategy. She collaborates closely with product, engineering, marketing, support, and management teams to shape the “why,” “how,” and “what” behind every feature and release. This deep cross-functional foundation enables her to craft clear messaging frameworks and create in-depth content — including technical articles — for the 4D blog and website. With a Master’s degree in Software Engineering, Basma brings both technical fluency and a strong editorial voice to her work. Her experience in areas like development, migrations, audits, webinars, and training gives her a unique edge in product marketing — helping her translate complexity into clarity and build content that truly connects with developers.