1.0 Released, and honest about what it is not

Real similarity search.

Without defining similarity.

No rules to write, no weights to tune, no model to train. Send Hilliance any data: it learns how your records should be compared — scales, meaning, weights, even differently named fields — and returns exact top matches from millions of objects in microseconds, with the evidence behind each one.

docker run -p 8080:8080 hilliance/hilliance
Any JSONno schema
Explainableper property
OfflineCPU-only, 102 languages
search1 / 5
latency8 µs
blocks read1
resultsexact
Timings from the 2M-object benchmark on an M3 Pro. The point cloud is an illustration: hover a point, drag to turn it.
In three comparisons

Nothing matches. Everything matches.

Three pairs that any exact match, and most fuzzy ones, would call different. No mapping written, no rules configured, no model trained: each number is what Hilliance returned after learning the population.

{ "type": "mug" } vs { "type": "cup" }
0.88

Different words. Meaning is compared, not spelling — and "diesel engine" scores 0.37 against the same mug.

{ "color": "yellow" } vs { "colour": "gold" }
0.89

Different field names. colour resolves onto color because their values agree, not because someone mapped them.

{ "diameter_cm": 27 } vs { "diameter_mm": 270 }
1.00

Different units. Same size, so identity. The same two keys holding 27 and 27 score 0.00.

Measured by the engine on its demo population. See a whole comparison, strand by strand →

Put to work

Two jobs people bring it first.

Both are the same engine and the same three requests — what changes is what you do with the numbers that come back.

A real server, answering now

Try it on data that misbehaves.

2,000 synthetic kitchen products, deliberately messy: half say colour and half say color, capacities arrive in millilitres and litres, diameters in centimetres and millimetres, and plenty of fields are simply missing. Nothing maps any of that — the engine worked it out when it learned them, and you can ask it anything below.

live server
Ask with an object
Or start from one of these
The request, as sent

          
Nothing is stored: the server is read-only and holds only this sample catalogue. Every number below came from it just now.
Or run it on your own machine
  1. 1

    Start the server

    docker run -p 8080:8080 -v hilliance-data:/data hilliance/hilliance
  2. 2

    Learn the sample catalogue

    curl -O https://hilliance.com/demo-products.jsonl curl -X POST 'localhost:8080/spaces?name=demo&id_field=sku' \ -H 'Content-Type: application/x-ndjson' \ --data-binary @demo-products.jsonl
  3. 3

    Ask in your own words and units

    This query names the colour field the other way round and gives the capacity in litres. Neither appears in half the catalogue, and it matches anyway.

    curl -X POST localhost:8080/spaces/demo/search \ -H 'content-type: application/json' -d '{ "object": {"type": "coffee mug", "color": "mustard", "capacity_l": 0.35}, "limit": 3, "explain": true }'
  4. 4

    Look at the whole thing

    The viewer is already running inside the server you started.

    open http://localhost:8080/spaces/demo/explore

The sample file is demo-products.jsonl — 2,000 rows, 619 KB, generated for this page. Your own data works the same way: there is nothing to configure between step 1 and step 3.

The problem

Real records don't line up.

Comparing products, vehicles, suppliers or anything else people describe with fields looks simple until you meet the data. Four things break most approaches.

Heterogeneous

One record has color and radius_mm. Another has colour and radius_cm. Same things, different names and units.

Incomplete

No record fully describes its thing. A field that isn't there is no evidence, and shouldn't count against a match.

Relative

Is radius 10 versus 11 a big gap? Among teacups, yes. Among bicycle wheels, no. The population decides.

Semantic

"mug" and "cup" are close. "mug" and "diesel engine" aren't. Matching on spelling gets this wrong.

Anatomy of an edge

Every edge carries its evidence.

An edge between two objects is made of one strand per property. Shared strands are measured, missing ones cost coverage rather than similarity, and differently named ones are lined up from the data. Every edge reports four numbers.

POST /spaces/products/compare
–
similarity
–
coverage
–
score
–
relatedness

Measured in a Space learned from 125,000 products with the bundled semantic model.

Lists

A long list is not evidence.

Offer more values and every value finds a closer partner, so a set score climbs with the length of the lists compared, related or not. Measured on 400 unrelated tags:

2 vs 2
0.22
10 vs 10
0.85
200 vs 200
0.93

Nothing in common in any of them.

So a property holding lists learns a chance curve instead of one prior: what a value expects to find as its best match among 1, 2, 4 … 1024 others, sampled from its own values at learn time. A comparison is then charged the chance its own sizes deserve.

200 unrelated options vs 200
+0.32→−0.00
relatedness
same family vs different family
0.48→0.57
gap, against 0.66 with no list at all

On 5,000 machines with option lists of 2 to 200 entries. The long lists had been costing the properties around them half their discrimination.

similarity

How alike the two objects are on the properties both have, from 0 to 1. null if they share nothing, and distance is 1 − similarity.

coverage

How much of the pair's available evidence could actually be compared.

score

Expected similarity over every property in the Space, with unshared ones filled by the population prior. Search ranks by it.

relatedness

The evidence for or against being related, centred on chance, from −1 to +1. The same ranking as score, on a scale you can read.

Relatedness

Is it more than chance?

Similarity of 0 isn't "unrelated": two random products score about 0.5 on a property. Relatedness puts chance at 0 and scales by the evidence the whole Space could offer, so it is the number to automate on.

"min_relatedness": 0 // beats chance
0.74a product and its true best match, median
0.30an exact match on three properties out of ten
0.02the median random pair: chance

Measured on 125,000 synthetic products. It's an exact threshold on the evidence search ranks by, so it prunes rather than costs.

A space per property

It knows lemon is yellow.

Text is compared by meaning, including values that never appear in your data. Each property is calibrated against its own population, so 0.9 means the same thing whether it's a type, a colour or a number.

typesemantic
.986 .88 .37 mug coffee mug cup diesel engine

Neighbours by meaning, not spelling. A diesel engine is a product too, and that's about all it shares with a mug.

colorsemantic
.893 .91 yellow gold lemon dashed = unseen, embedded at query time

lemon never appears in the data. It's embedded on the fly and calibrated like every other colour.

radiusnumeric
ab mass population of radius values (illustration)

Numbers are placed in their own distribution. The distance between two values is how much of the population lies between them.

What "similar" means here

Interpreted, but not imported.

Every conversation about similarity arrives at "but similarity is subjective". It's worth being exact about which part is, and what this engine does about it.

Identity is the anchor

Two objects described by the same values are the same on the evidence available, and the engine returns exactly 1.0 — not 0.98, and with no setting that could make it anything else.

Below identity some bias is unavoidable. Count every predicate definable over a finite set of objects and any two of them share exactly the same number: treat all evidence as equal and a swan is as similar to a duck as to another swan. A usable scale between 0 and 1 cannot be had without privileging some evidence over the rest. The question is never whether to interpret, only where the interpretation comes from.

meaning isn't in the bits
{ "diameter_cm": 27 } vs { "diameter_mm": 270 } 1.0different numbers, same size
{ "diameter_cm": 27 } vs { "diameter_mm": 27 } 0.0same number, different size

Identical digits, no similarity; different digits, identity. What separates them is a model of what the numbers refer to — here the unit table, elsewhere a property's vocabulary or its distribution.

Which properties exist

The schema is the largest single lever on every number here, and it's yours, decided before any comparison happens.

Where the population ends

The same two objects score differently in a catalogue of mugs and a catalogue of everything. Numbers from different Spaces are not comparable.

What the embedder read

Semantic similarity inherits the text the model was trained on. That fast and firm sit far apart in one corpus is a fact about that corpus, not about beds.

Similar for what

Substitutable on a shelf, priced alike, shipped alike — different questions, and no population picks between them for you.

How it works

Learn a Space. Compare anything in it.

A Space is a learned, versioned comparison environment for one population, like products or suppliers. Learning it works out everything you'd otherwise specify by hand.

{ … } type"mug" maker.country"SE" tags[]coffee, gift colornull → absent
01 · OBSERVE

Values become observations

Objects flatten into path and value pairs. Nested fields get dotted paths, arrays become sets, and objects inside an array keep their fields together: a red S and a blue L is not a red L and a blue S.

semantic numeric temporal categorical identifier
02 · INFER

Kinds come from the data

Each property's kind is inferred from the population. SKUs and UUIDs are recognised as identifiers and given no weight.

colorcolour color width ≠ heightmm → cm ✓
03 · ALIGN

Names are confirmed by data

Names only propose a match. color and colour merge because their values agree, radius_mm and radius_cm merge and convert, and width and height never do.

u ∈ [0,1]
04 · SCALE

Numbers are relative

Every number gets its position in the property's distribution, so skewed data needs no special care. A query beyond the range keeps going rather than pinning to the maximum.

color material type redundant
05 · WEIGH

Evidence is counted once

Informative properties weigh more. What several properties say in common counts once, and what each says alone keeps its full weight. A tenant or owner column can be marked filter_only: filterable, but never evidence.

06 · COMBINE

Only shared evidence counts

Properties observed in both objects are compared. With explain, each one reports its similarity, weight and status.

Built-in viewer

See the shape of your data.

This ships inside the server. Nothing to install, nothing to deploy, no separate service and no account: the container you already started serves it, on your own data, offline. One request opens it — every object a point, laid out so alike things sit together.

Already running open http://localhost:8080/spaces/products/explore
In the Docker imageNo build step, no CDNNo extra serviceWorks offline
colour by
GET /spaces/products/explore
789 objects selected

Why is that a group?

distinctiveness, most telling first

Draw around a cluster — or hit Select region — and Hilliance says what those objects have in common: the properties that set them apart, most distinctive first, against the whole population. The same call explains a search result.

0.31ten nearest on screen
0.67true nearest, by search
0.03arbitrary objects

The layout is approximate, and the viewer says so. Ten properties genuinely need about ten dimensions. Everything except the positions is exact: neighbours, numbers, search results and explanations all come from the ordinary API.

Colour by what explains

Properties are ordered by how often neighbours agree on them, so the most informative colouring comes first. In 2D or a 3D turntable: drag to spin, and the cloud never turns over.

Click a point

See the object's JSON and its exact nearest neighbours, with relatedness, similarity and coverage, in microseconds. Lines join it to the nearest plotted objects.

Search in place

Paste an object and the matches light up where they are. The page takes its credential from the address fragment, which never reaches a server's logs — hand it an end user's token and the viewer shows that user's objects and nothing else.

Measured, and reproducible

Fast enough to ask per request.

Top-10 over two million synthetic 10-property objects, ranked by score, median of 31 runs on an Apple M3 Pro. How fast depends on the query, so here are the ends of the range rather than the flattering middle.

8 µsa selective query: one exact type
195 µsa rich query: eight properties at once
4.3 msfree text alone, a million near-unique titles
1.2 msthat same text query with one structured field

Everything above is exact: pruning reads fewer blocks but never changes a result — add "exhaustive": true to any query and compare. Don't take our word for the timings: hilliance-bench generates the population from a seed and measures the published image over its ordinary API, so you can run it on your own hardware in two commands. The full tables, the sustained-load soak and the approximate mode are in the performance documentation.

Pricing

Free for everyone except your future competitor.

Anyone may self-host Hilliance, in production, at any size, behind their own product, free and without telling us. The one thing you may not do is offer Hilliance itself to third parties as a hosted service. That single carve-out is the whole of the licence.

Self-hosted

Run it on your own machines, for your own product.

Free

no licence to buy, no size limit, no reporting

  • Production use, any number of servers, Spaces and objects
  • Behind your own product, serving your own customers
  • The full engine: no feature gates, no telemetry, no call home
  • Source on request, under the same licence
  • Each version turns Apache-2.0 four years after release
Run it locally

Hosted service

Commercial licence

Offer Hilliance itself to third parties, as a service.

Let's talk

priced per engagement, terms to suit

  • A licence to sell Hilliance's functionality as a hosted or managed service
  • Terms that can differ from the BUSL wherever you need them to
  • Support and upgrade arrangements agreed with you
  • Tell us what you're building and we'll quote for it
Get a quote

Both ways include

The full engine, no feature gatesCPU-only, the model in the imageOffline: no network at runtime No telemetry, no data leaves your machinesExact search, filters and relatednessImmutable versions and shardingThe built-in viewerTokens, timeouts and /metricsSource on request

Source-available, under BUSL 1.1

You may read it, modify it, self-host it and run it in production, including behind your own product.
You may not offer it to third parties on a hosted or managed basis — selling its functionality as a service.
Four years after a version is published, that version becomes Apache-2.0. The clock runs per version.
Source on request. The engine's source isn't published in a public repository; ask and we'll share it under the same licence — info@hilliance.com.

Every dependency is permissive, and CI runs cargo deny so a copyleft crate can't arrive unnoticed in an upgrade. Other terms are available: ask.

Why the carve-out exists, plainly: without it, the first person to read the source could run the cloud service before we do. Everything else — your servers, your product, your customers — is free.

Let the data define similarities.

Stop writing rules for what's similar. Bring a population of records, and Hilliance learns the space and shows its working.