Vector Search
Vector search enables similarity search using embedding vectors, supporting use cases like:
- Semantic search: Find similar meanings, not just keywords
- Recommendations: Find similar products, content, users
- Image search: Find similar images by visual features
- Anomaly detection: Find unusual patterns
Fluree supports two complementary approaches:
- Inline similarity functions – compute
dotProduct,cosineSimilarity, oreuclideanDistancedirectly in queries usingbind. No external index required. - HNSW vector indexes – build dedicated approximate-nearest-neighbor (ANN) indexes for large-scale similarity search using the
f:*query pattern.
The @vector Datatype
Why a dedicated datatype?
In RDF, a plain JSON array like [0.5, 0.5, 0.0] is decomposed into individual values. Duplicate elements can be deduplicated, and ordering is not guaranteed. This breaks embedding vectors. The @vector datatype tells Fluree to store the array as a single, ordered, fixed-length vector.
@vector is a shorthand for the full IRI https://ns.flur.ee/db#embeddingVector, which can also be written as f:embeddingVector when the Fluree namespace prefix is declared in your @context.
Storage: f32 precision contract
All @vector values are stored as IEEE-754 binary32 (f32) arrays. This means:
- Each element in your JSON array is quantized to f32 at ingest time
- Values that are not representable as finite f32 (NaN, Infinity, values exceeding f32 range) are rejected
- Round-trip reads return the f32-quantized values (e.g.,
0.1in JSON becomes0.10000000149011612after f32 quantization) - This provides a compact, cache-friendly representation optimized for SIMD similarity computation
If you need higher precision (f64) or different vector formats (sparse, integer), store them as a custom RDF datatype string.
Inserting vectors (JSON-LD)
Use "@type": "@vector" to annotate a numeric array as a vector:
{
"@context": {
"ex": "http://example.org/"
},
"@graph": [
{
"@id": "ex:doc1",
"@type": "ex:Document",
"ex:embedding": {
"@value": [0.1, 0.2, 0.3, 0.4],
"@type": "@vector"
}
}
]
}
You can also use the full IRI or the f: prefix form, which is equivalent:
{
"@context": {
"ex": "http://example.org/",
"f": "https://ns.flur.ee/db#"
},
"@graph": [
{
"@id": "ex:doc1",
"ex:embedding": {
"@value": [0.1, 0.2, 0.3, 0.4],
"@type": "f:embeddingVector"
}
}
]
}
Incorrect – plain array (will not work for similarity):
{
"@id": "ex:doc1",
"ex:embedding": [0.1, 0.2, 0.3, 0.4]
}
Plain arrays are decomposed into individual RDF values where duplicates may be removed and order is lost.
Inserting vectors (Turtle / SPARQL UPDATE)
In Turtle and SPARQL UPDATE, the @vector shorthand is not available. Use the f:embeddingVector datatype IRI with the standard ^^ typed-literal syntax:
PREFIX ex: <http://example.org/>
PREFIX f: <https://ns.flur.ee/db#>
INSERT DATA {
ex:doc1 ex:embedding "[0.1, 0.2, 0.3, 0.4]"^^f:embeddingVector .
}
The vector is represented as a JSON array string with the ^^f:embeddingVector datatype annotation.
Multiple vectors per entity
An entity can have multiple vectors on the same property:
{
"@id": "ex:doc1",
"ex:embedding": [
{"@value": [0.1, 0.9], "@type": "@vector"},
{"@value": [0.2, 0.8], "@type": "@vector"}
]
}
Each vector produces separate rows in query results.
Vector literals in query VALUES clauses
When passing a vector literal in a query values clause, use the full IRI or the f: prefix form – the @vector shorthand is only resolved in the transaction parser:
"values": [
["?queryVec"],
[{"@value": [0.7, 0.6], "@type": "f:embeddingVector"}]
]
Or with the full IRI:
"values": [
["?queryVec"],
[{"@value": [0.7, 0.6], "@type": "https://ns.flur.ee/db#embeddingVector"}]
]
Inline Similarity Functions (JSON-LD Query)
Fluree provides three vector similarity functions that can be used in bind expressions within JSON-LD queries. These compute similarity scores directly during query execution without requiring a pre-built index.
Function names are case-insensitive; dotProduct, dotproduct, and dot_product are all equivalent.
dotProduct
Computes the dot product (inner product) of two vectors. Higher scores indicate greater similarity when vectors represent aligned directions.
{
"@context": {
"ex": "http://example.org/ns/",
"f": "https://ns.flur.ee/db#"
},
"select": ["?doc", "?score"],
"values": [
["?queryVec"],
[{"@value": [0.7, 0.6], "@type": "f:embeddingVector"}]
],
"where": [
{"@id": "?doc", "ex:embedding": "?vec"},
["bind", "?score", "(dotProduct ?vec ?queryVec)"]
],
"orderBy": [["desc", "?score"]],
"limit": 10
}
Score range: (-inf, +inf). Best when vector magnitude encodes importance.
cosineSimilarity
Computes the cosine of the angle between two vectors. Ignores magnitude, focusing purely on directional similarity.
{
"@context": {
"ex": "http://example.org/ns/",
"f": "https://ns.flur.ee/db#"
},
"select": ["?doc", "?score"],
"values": [
["?queryVec"],
[{"@value": [0.7, 0.6], "@type": "f:embeddingVector"}]
],
"where": [
{"@id": "?doc", "ex:embedding": "?vec"},
["bind", "?score", "(cosineSimilarity ?vec ?queryVec)"]
],
"orderBy": [["desc", "?score"]],
"limit": 10
}
Score range: [-1, 1] (1 = identical direction, 0 = orthogonal, -1 = opposite). Returns null if either vector has zero magnitude. Best for text embeddings and normalized vectors.
euclideanDistance
Computes the L2 (straight-line) distance between two vectors. Lower scores indicate greater similarity.
{
"@context": {
"ex": "http://example.org/ns/",
"f": "https://ns.flur.ee/db#"
},
"select": ["?doc", "?distance"],
"values": [
["?queryVec"],
[{"@value": [0.7, 0.6], "@type": "f:embeddingVector"}]
],
"where": [
{"@id": "?doc", "ex:embedding": "?vec"},
["bind", "?distance", "(euclideanDistance ?vec ?queryVec)"]
],
"orderBy": "?distance",
"limit": 10
}
Score range: [0, +inf) (0 = identical). Best for geometric similarity and when absolute position matters.
Alternative array syntax
The similarity functions also accept array form instead of the S-expression string:
["bind", "?score", ["dotProduct", "?vec", "?queryVec"]]
This is equivalent to:
["bind", "?score", "(dotProduct ?vec ?queryVec)"]
Filtering by score threshold
Combine bind with filter to return only results above a similarity threshold:
{
"@context": {
"ex": "http://example.org/ns/",
"f": "https://ns.flur.ee/db#"
},
"select": ["?doc", "?score"],
"values": [
["?queryVec"],
[{"@value": [0.7, 0.6], "@type": "f:embeddingVector"}]
],
"where": [
{"@id": "?doc", "ex:embedding": "?vec"},
["bind", "?score", "(dotProduct ?vec ?queryVec)"],
["filter", "(> ?score 0.7)"]
]
}
Combining with graph patterns
Vector similarity can be combined with standard graph patterns to filter by type, property values, or relationships:
{
"@context": {
"ex": "http://example.org/ns/",
"f": "https://ns.flur.ee/db#"
},
"select": ["?doc", "?title", "?score"],
"values": [
["?queryVec"],
[{"@value": [0.9, 0.1, 0.05], "@type": "f:embeddingVector"}]
],
"where": [
{"@id": "?doc", "@type": "ex:Article", "ex:title": "?title", "ex:embedding": "?vec"},
["bind", "?score", "(cosineSimilarity ?vec ?queryVec)"],
["filter", "(> ?score 0.5)"]
],
"orderBy": [["desc", "?score"]],
"limit": 5
}
Using a stored vector as the query vector
Instead of providing a literal vector, you can use a stored entity’s vector:
{
"@context": {
"ex": "http://example.org/ns/"
},
"select": ["?similar", "?score"],
"where": [
{"@id": "ex:reference-doc", "ex:embedding": "?queryVec"},
{"@id": "?similar", "ex:embedding": "?vec"},
["filter", "(!= ?similar ex:reference-doc)"],
["bind", "?score", "(cosineSimilarity ?vec ?queryVec)"]
],
"orderBy": [["desc", "?score"]],
"limit": 10
}
Mixed datatypes
If a property contains both vector and non-vector values, the similarity functions return null for non-vector bindings:
{
"@graph": [
{"@id": "ex:a", "ex:data": {"@value": [0.6, 0.5], "@type": "@vector"}},
{"@id": "ex:b", "ex:data": "Not a vector"}
]
}
Querying with dotProduct on ?data will return a numeric score for ex:a and null for ex:b.
SPARQL support
Inline vector similarity functions (dotProduct, cosineSimilarity, euclideanDistance) are available in both JSON-LD Query and SPARQL. In SPARQL, use them as built-in function calls within BIND expressions:
dotProduct (SPARQL)
PREFIX ex: <http://example.org/ns/>
PREFIX f: <https://ns.flur.ee/db#>
SELECT ?doc ?score
WHERE {
VALUES ?queryVec { "[0.7, 0.6]"^^f:embeddingVector }
?doc ex:embedding ?vec ;
ex:title ?title .
BIND(dotProduct(?vec, ?queryVec) AS ?score)
}
ORDER BY DESC(?score)
LIMIT 10
cosineSimilarity (SPARQL)
PREFIX ex: <http://example.org/ns/>
PREFIX f: <https://ns.flur.ee/db#>
SELECT ?doc ?score
WHERE {
VALUES ?queryVec { "[0.88, 0.12, 0.08]"^^f:embeddingVector }
?doc a ex:Article ;
ex:embedding ?vec ;
ex:title ?title .
BIND(cosineSimilarity(?vec, ?queryVec) AS ?score)
FILTER(?score > 0.5)
}
ORDER BY DESC(?score)
LIMIT 5
euclideanDistance (SPARQL)
PREFIX ex: <http://example.org/ns/>
PREFIX f: <https://ns.flur.ee/db#>
SELECT ?doc ?distance
WHERE {
VALUES ?queryVec { "[0.7, 0.6]"^^f:embeddingVector }
?doc ex:embedding ?vec .
BIND(euclideanDistance(?vec, ?queryVec) AS ?distance)
}
ORDER BY ?distance
LIMIT 10
Vector literals in SPARQL
In SPARQL, vectors are passed as JSON array strings with the ^^f:embeddingVector typed literal syntax:
VALUES ?queryVec { "[0.1, 0.2, 0.3]"^^f:embeddingVector }
Or with the full IRI:
VALUES ?queryVec { "[0.1, 0.2, 0.3]"^^<https://ns.flur.ee/db#embeddingVector> }
Function name variants
Function names are case-insensitive in SPARQL. All of these are equivalent:
dotProduct,DOTPRODUCT,dot_productcosineSimilarity,COSINESIMILARITY,cosine_similarityeuclideanDistance,EUCLIDEANDISTANCE,euclidean_distance
HNSW Vector Indexes
For large-scale similarity search, Fluree provides dedicated HNSW (Hierarchical Navigable Small World) vector indexes. These are approximate nearest-neighbor (ANN) indexes that trade exact results for dramatically faster query times on large datasets.
Vector indexes are implemented using embedded usearch following the same architecture as BM25:
- Embedded in-process HNSW indexes (no external service required)
- Remote mode via dedicated search service (
fluree-search-httpd) - Snapshot-based persistence with watermarks
- Incremental sync for efficient updates
- Feature-gated via
vectorfeature flag
v1 limitation: HNSW vector search is head-only. Time-travel queries (e.g. @t:) are not supported.
Creating Vector Indexes
HTTP/Docker users: there is no HTTP endpoint for creating vector indexes today. Index creation is Rust-API-only. To use HNSW vector search from an HTTP-only deployment, create the index using a Rust program (or the Rust API embedded in your application) against the same storage path your Fluree server reads, then run queries normally via
POST /v1/fluree/query.
Rust API
#![allow(unused)]
fn main() {
use fluree_db_api::{FlureeBuilder, VectorCreateConfig};
use fluree_db_query::vector::DistanceMetric;
let fluree = FlureeBuilder::memory().build_memory();
// Create indexing query to select documents with embeddings
let indexing_query = json!({
"@context": { "ex": "http://example.org/" },
"where": [{ "@id": "?x", "@type": "ex:Document" }],
"select": { "?x": ["@id", "ex:embedding"] }
});
// Create vector index
let config = VectorCreateConfig::new(
"doc-embeddings", // index name
"mydb:main", // source ledger
indexing_query, // what to index
"ex:embedding", // embedding property
768 // dimensions
)
.with_metric(DistanceMetric::Cosine);
let result = fluree.create_vector_index(config).await?;
println!("Indexed {} vectors", result.vector_count);
}
Configuration Options
| Option | Description | Default |
|---|---|---|
name | Index name (creates graph source ID name:branch) | Required |
ledger | Source ledger ID (name:branch) | Required |
query | JSON-LD query selecting documents | Required |
embedding_property | Property containing embeddings | Required |
dimensions | Vector dimensions | Required |
metric | Distance metric (Cosine, Dot, Euclidean) | Cosine |
connectivity | HNSW M parameter | 16 |
expansion_add | efConstruction parameter | 128 |
expansion_search | efSearch parameter | 64 |
Query Syntax
Vector index search uses the f:* pattern syntax in WHERE clauses:
{
"@context": {
"ex": "http://example.org/",
"f": "https://ns.flur.ee/db#"
},
"from": "mydb:main",
"where": [
{
"f:graphSource": "doc-embeddings:main",
"f:queryVector": [0.1, 0.2, 0.3],
"f:distanceMetric": "cosine",
"f:searchLimit": 10,
"f:searchResult": {
"f:resultId": "?doc",
"f:resultScore": "?score"
}
}
],
"select": ["?doc", "?score"]
}
Query Parameters
| Parameter | Description | Required |
|---|---|---|
f:graphSource | Vector index alias | Yes |
f:queryVector | Query vector (array or variable) | Yes |
f:distanceMetric | Distance metric (“cosine”, “dot”, “euclidean”) | No (uses index default) |
f:searchLimit | Maximum results | No |
f:searchResult | Result binding (variable or object) | Yes |
f:syncBeforeQuery | Wait for index sync before query | No (default: false) |
f:timeoutMs | Query timeout in ms | No |
Result Binding
Simple variable binding:
"f:searchResult": "?doc"
Structured binding with score and ledger:
"f:searchResult": {
"f:resultId": "?doc",
"f:resultScore": "?similarity",
"f:resultLedger": "?source"
}
Variable Query Vectors
Query vector can be a variable bound earlier:
{
"where": [
{ "@id": "ex:reference-doc", "ex:embedding": "?queryVec" },
{
"f:graphSource": "embeddings:main",
"f:queryVector": "?queryVec",
"f:searchLimit": 5,
"f:searchResult": "?similar"
}
]
}
Index Maintenance
Sync Updates
After committing new data, sync the vector index:
#![allow(unused)]
fn main() {
let sync_result = fluree.sync_vector_index("doc-embeddings:main").await?;
println!("Upserted: {}, Removed: {}", sync_result.upserted, sync_result.removed);
}
Full Resync
Rebuild the entire index from scratch:
#![allow(unused)]
fn main() {
let resync_result = fluree.resync_vector_index("doc-embeddings:main").await?;
}
Check Staleness
#![allow(unused)]
fn main() {
let check = fluree.check_vector_staleness("doc-embeddings:main").await?;
if check.is_stale {
println!("Index is {} commits behind", check.commits_behind);
}
}
Drop Index
#![allow(unused)]
fn main() {
fluree.drop_vector_index("doc-embeddings:main").await?;
}
Distance Metrics
Cosine (Default)
Measures angle between vectors. Best for:
- Text embeddings (e.g., sentence transformers)
- Normalized vectors
- When magnitude doesn’t matter
Score range: [-1, 1] (1 = identical, 0 = orthogonal, -1 = opposite)
For unit-normalized vectors, cosine similarity equals dot product. Fluree’s SIMD kernels exploit this for faster computation when vectors are pre-normalized.
Dot Product
Measures alignment and magnitude. Best for:
- Maximum inner product search (MIPS)
- When vector magnitude encodes importance
Score range: (-inf, +inf)
Euclidean (L2)
Measures straight-line distance. Best for:
- Geometric similarity
- Image feature vectors
- When absolute position matters
Raw score range: [0, +inf). In HNSW index results, normalized to (0, 1] via 1 / (1 + distance).
Note: In HNSW index results (f:* queries), all metrics are normalized to “higher is better”. In inline similarity functions, euclideanDistance returns the raw L2 distance (lower = more similar).
Deployment Modes
Vector indexes support two deployment topologies: searching in-process (embedded) or via a dedicated fluree-search-httpd service that mounts the same storage. Both topologies use identical distance-metric computation, score normalization, and snapshot serialization, so results are identical.
Embedded Mode (Default)
The vector index is loaded and searched within the same process as the Fluree server. No additional services. This is the default and is appropriate for most deployments.
Dedicated Search Service
For large indexes or when you want search traffic isolated from the main Fluree process, run the standalone fluree-search-httpd binary on the same storage volume and have your application send vector requests directly to it.
Note: Today, vector search is invoked from a Fluree query (the
f:graphSource/f:queryVectorpattern) using the embedded path — the main Fluree server does not yet route those queries to a remote service. The dedicated service is reachable directly via its ownPOST /v1/searchAPI (the same protocol BM25 uses), which is suitable for applications that issue vector queries outside of a Fluree query context. Transparent delegation from inside a Fluree query is a planned follow-up; the wiring is in place but the deployment config is not yet persisted bycreate_vector_index.
See Remote Search Service for fluree-search-httpd configuration, env vars, the request/response protocol (vector and vector_similar_to query kinds), and Docker deployment.
Performance and Scaling
The importance of binary indexing
Fluree’s binary columnar index dramatically accelerates vector queries. Queries against novelty-only (unindexed) data perform a linear scan through the in-memory commit log, while indexed queries read pre-sorted, cache-friendly columnar data. Ensure background indexing is running for production workloads – the difference is substantial.
The following benchmarks use 768-dimensional vectors (typical for transformer embeddings like sentence-transformers or OpenAI text-embedding-3-small) on Apple M-series hardware:
Novelty-only (no binary index)
| Scenario | Vectors | Query time | Throughput |
|---|---|---|---|
| Scan all | 1,000 | 9.9 ms | ~101K vec/s |
| Scan all | 5,000 | 45.1 ms | ~111K vec/s |
| Filtered + score | 1,000 (75 pass filter) | 13.5 ms | ~5.5K vec/s |
| Filtered + score | 5,000 (402 pass filter) | 62.1 ms | ~6.5K vec/s |
With binary index
| Scenario | Vectors | Query time | Throughput | Speedup vs novelty |
|---|---|---|---|---|
| Scan all | 1,000 | 1.68 ms | ~595K vec/s | 5.9x |
| Scan all | 5,000 | 7.69 ms | ~650K vec/s | 5.9x |
| Filtered + score | 1,000 (75 pass filter) | 533 us | ~141K vec/s | 25x |
| Filtered + score | 5,000 (402 pass filter) | 2.40 ms | ~168K vec/s | 26x |
Key takeaways:
- Unfiltered scans are ~6x faster with the binary index
- Filtered queries (where graph patterns reduce the candidate set before scoring) are ~25x faster – the index enables efficient predicate-first access that avoids loading irrelevant vectors entirely
- At 5,000 vectors, a filtered indexed query completes in 2.4 ms – well within interactive latency budgets
The flat-rank top-k fast path
Queries matching the canonical similarity signature skip the generic operator pipeline entirely and execute as a direct parallel scan of the packed f32 vector shards, with a bounded top-k heap:
{
"select": ["?s", "?score"],
"values": [["?target"], [{"@value": [/* query vector */], "@type": "https://ns.flur.ee/db#embeddingVector"}]],
"where": [
{"@id": "?s", "ex:embedding": "?vec"},
["bind", "?score", ["dotProduct", "?vec", "?target"]],
["filter", "(> ?score 0.5)"]
],
"orderBy": [["desc", "?score"]],
"limit": 10
}
The signature: a single triple pattern over one vector predicate, a bind of
dotProduct(?vec, <constant or single-row VALUES target>), an optional score
threshold filter, optional ORDER BY DESC(?score), optional LIMIT/OFFSET,
projecting only ?s and/or ?score. Scores are bit-identical to the general
pipeline, results are exact (every vector is scored — no approximation), and
novelty is folded correctly (unindexed asserts appear, retractions cancel).
Anything outside the signature — additional join patterns, cosineSimilarity
/ euclideanDistance, projecting ?vec, policy-enforced views, multi-ledger
datasets, time travel — runs the general pipeline instead, correct but slower.
Measured at 50,000 × 768-dim vectors (release, Apple M-series), threshold +
ORDER BY DESC query:
| Lane | Query time | Throughput |
|---|---|---|
| Novelty-only (no binary index) | 324 ms | ~155K vec/s |
| Binary index, general pipeline | 30 ms | ~1.6M vec/s |
| Binary index, top-k fast path | 8.1 ms | ~6.2M vec/s |
The scan is memory-bandwidth-bound and partitions across cores, so it scales near-linearly with corpus bytes: roughly 6M vectors/sec at 768 dims on laptop-class hardware.
Inline similarity functions (flat scan)
- Best for: Small to medium datasets, ad-hoc similarity queries, prototyping
- Complexity: O(n) linear scan – computes similarity against every matching vector
- Advantage: No index setup required, works immediately after insert; results are exact and strongly consistent (novelty included)
- SIMD acceleration: Fluree uses runtime-detected SIMD kernels (SSE2/AVX on x86_64, NEON on ARM) for vectorized dot/cosine/L2 computation, reading indexed vectors zero-copy from the packed f32 shards
- Prefer
dotProductfor normalized embeddings: for unit-normalized vectors (most transformer embeddings), dot product ranks identically to cosine similarity, computes fewer terms, and is the only scoring function served by the top-k fast path today
When to consider HNSW
Inline similarity functions perform a brute-force scan over all candidate vectors. This scales linearly and remains fast for moderate datasets, but at larger scales an HNSW index provides O(log n) approximate nearest-neighbor search.
Rule of thumb (768-dim; scale thresholds down for higher dimensions). The two columns matter: queries matching the top-k fast-path signature scale much further than general-shape queries (extra joins, cosine/euclidean, projected vectors, policy-enforced views), which pay pipeline costs per row.
| Vector count (per property) | Top-k signature (dotProduct + order/limit) | General-shape queries |
|---|---|---|
| < 100K | Flat scan is interactive (< ~20 ms indexed). | Flat scan works well (< ~60 ms indexed). |
| 100K – 1M | Flat scan stays interactive (~20-170 ms, memory-resident). HNSW optional. | Start evaluating HNSW; flat scan runs ~60-600 ms. |
| 1M – 10M | Flat scan viable for sub-second budgets if shards are memory-resident (~0.2-1.7 s). HNSW for strict latency. | HNSW strongly recommended for interactive latency. |
| > 10M | HNSW (or other ANN index) is the default recommendation. | HNSW default; flat scan becomes I/O- and cache-bound. |
Remember the trade: the HNSW graph source is eventually consistent (it lags the ledger until its index refreshes), while the flat scan is exact and strongly consistent, including uncommitted-to-index novelty. If your workload needs read-your-writes similarity search, that pushes the flat-scan ceiling up; if it needs guaranteed low latency at any corpus size, that pulls HNSW in earlier.
Factors that shift the crossover:
- Query shape: only the canonical top-k signature gets the fast path — check both columns above
- Hardware: Fast NVMe / large RAM pushes the threshold higher; object storage (S3) pulls it lower (first-touch shard loads dominate cold queries)
- Latency target: A 50 ms budget favors HNSW earlier than a 2-second budget
- Filter selectivity: If graph patterns reduce candidates to a small fraction before scoring, flat scan remains viable at higher counts
- Dimensions: the scan is memory-bandwidth-bound; 1536-dim costs ~2x the 768-dim numbers
- Binary indexing: an indexed dataset scans ~10-40x faster than novelty-only — ensure background indexing runs for vector-heavy ledgers
HNSW vector indexes
- Best for: Multi-million-vector datasets, strict latency requirements at any size, and query shapes the flat-scan fast path doesn’t serve; note HNSW results are eventually consistent (the index lags the ledger until it refreshes)
- Complexity: O(log n) approximate nearest neighbor
- Space: ~1.5x embedding size + IRI mapping overhead
- Updates: Incremental via affected-subject tracking
Tuning parameters
| Parameter | Effect | Trade-off |
|---|---|---|
connectivity (M) | Graph connectivity | Higher = better recall, more memory |
expansion_add (efConstruction) | Build-time search width | Higher = better index quality, slower build |
expansion_search (efSearch) | Query-time search width | Higher = better recall, slower queries |
Feature Flag
The HNSW vector index functionality requires the vector feature:
[dependencies]
fluree-db-api = { version = "0.1", features = ["vector"] }
Inline similarity functions (dotProduct, cosineSimilarity, euclideanDistance) and the @vector datatype are available without feature flags.
Complete Example: Semantic Search
1. Insert documents with embeddings:
{
"@context": {
"ex": "http://example.org/",
"f": "https://ns.flur.ee/db#"
},
"@graph": [
{
"@id": "ex:doc1",
"@type": "ex:Article",
"ex:title": "Introduction to Machine Learning",
"ex:embedding": {"@value": [0.9, 0.1, 0.05], "@type": "@vector"}
},
{
"@id": "ex:doc2",
"@type": "ex:Article",
"ex:title": "Database Design Patterns",
"ex:embedding": {"@value": [0.1, 0.8, 0.1], "@type": "@vector"}
},
{
"@id": "ex:doc3",
"@type": "ex:Article",
"ex:title": "Neural Network Architectures",
"ex:embedding": {"@value": [0.85, 0.15, 0.1], "@type": "@vector"}
}
]
}
2. Query – find articles similar to a “machine learning” embedding:
{
"@context": {
"ex": "http://example.org/",
"f": "https://ns.flur.ee/db#"
},
"select": ["?title", "?score"],
"values": [
["?queryVec"],
[{"@value": [0.88, 0.12, 0.08], "@type": "f:embeddingVector"}]
],
"where": [
{"@id": "?doc", "@type": "ex:Article", "ex:title": "?title", "ex:embedding": "?vec"},
["bind", "?score", "(cosineSimilarity ?vec ?queryVec)"]
],
"orderBy": [["desc", "?score"]],
"limit": 5
}
Expected results (ordered by similarity):
- “Introduction to Machine Learning” – highest cosine similarity
- “Neural Network Architectures” – similar domain
- “Database Design Patterns” – different domain, lower score
Related Documentation
- Datatypes and Typed Values - All supported datatypes including
@vector - JSON-LD Query - Full query language reference
- BM25 - Full-text search
- Background Indexing - Core indexing
- Graph Sources - Graph source concepts