diff --git a/content/develop/clients/rust/vecsets.md b/content/develop/clients/rust/vecsets.md new file mode 100644 index 0000000000..b55dd15bd3 --- /dev/null +++ b/content/develop/clients/rust/vecsets.md @@ -0,0 +1,164 @@ +--- +categories: +- docs +- develop +- stack +- oss +- rs +- rc +- oss +- kubernetes +- clients +description: Index and query embeddings with Redis vector sets +linkTitle: Vector set embeddings +title: Vector set embeddings +weight: 40 +scope: example +topics: +- vector sets +- vectors +--- + +A Redis [vector set](/content/develop/data-types/vector-sets/_index.md) lets +you store a set of unique keys, each with its own associated vector. +You can then retrieve keys from the set according to the similarity between +their stored vectors and a query vector that you specify. + +You can use vector sets to store any type of numeric vector but they are +particularly optimized to work with text embedding vectors (see +[Redis for AI](/content/develop/ai/_index.md) to learn more about text +embeddings). The example below shows how to use the +[`fastembed`](https://crates.io/crates/fastembed) crate to generate vector +embeddings and then store and retrieve them using a vector set with +`redis-rs`. + +Vector set support in `redis-rs` is gated behind the opt-in `vector-sets` +Cargo feature: + +```toml +[dependencies] +redis = { version = "1.6", features = ["vector-sets"] } +fastembed = "4" +``` + +## Initialize + +Import the required crates: + +{{< clients-example set="home_vecsets" step="import" lang_filter="Rust-Sync,Rust-Async" description="Foundational: Import required libraries for vector sets, embeddings, and Redis operations" difficulty="beginner" >}} +{{< /clients-example >}} + +`fastembed` runs the ONNX-exported +[`all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) +model locally. This model generates vectors with 384 dimensions, regardless +of the length of the input text, but note that the input is truncated to 256 +tokens (see +[Word piece tokenization](https://huggingface.co/learn/nlp-course/en/chapter6/6) +at the [Hugging Face](https://huggingface.co/) docs to learn more about the +way tokens are related to the original text). + +{{< clients-example set="home_vecsets" step="model" lang_filter="Rust-Sync,Rust-Async" description="Foundational: Initialize an embedding model to generate vector embeddings from text" difficulty="beginner" >}} +{{< /clients-example >}} + +## Create the data + +The example data is a list of famous people with brief descriptions: + +{{< clients-example set="home_vecsets" step="data" lang_filter="Rust-Sync,Rust-Async" description="Foundational: Define sample data with text descriptions for vector embedding and storage" difficulty="beginner" >}} +{{< /clients-example >}} + +## Add the data to a vector set + +The next step is to connect to Redis and add the data to a new vector set. + +The code below iterates through the list and adds a corresponding element to +a vector set called `famousPeople` for each entry. + +`TextEmbedding::embed()` returns already mean-pooled, L2-normalized 384-element +`Vec` embeddings, matching the pooling and normalization +`sentence-transformers` applies by default in the other client examples on +this page. Pass the embedding directly to +[`vadd_options()`](/content/commands/vadd.md) along with the `born` and +`died` values as attribute data, using +[`VAddOptions::set_attributes()`](/content/commands/vadd.md). You can access +this attribute data during a query or with the +[`vgetattr()`](/content/commands/vgetattr.md) method. + +{{< clients-example set="home_vecsets" step="add_data" lang_filter="Rust-Sync,Rust-Async" description="Foundational: Add vector embeddings and attributes to a vector set using VADD command" difficulty="beginner" >}} +{{< /clients-example >}} + +## Query the vector set + +You can now query the data in the set. The basic approach is to embed the +query text and pass the resulting vector to +[`vsim()`](/content/commands/vsim.md), which returns elements of the set, +ranked in order of similarity to the query. + +Start with a simple query for "actors": + +{{< clients-example set="home_vecsets" step="basic_query" lang_filter="Rust-Sync,Rust-Async" description="Vector similarity search: Find semantically similar items in a vector set using VSIM command" difficulty="intermediate" >}} +{{< /clients-example >}} + +This returns the following list of elements: + +``` +'actors': ["Masako Natsume", "Chaim Topol", "Linus Pauling", +"Marie Fredriksson", "Maryam Mirzakhani", "Marie Curie", +"Freddie Mercury", "Paul Erdos"] +``` + +The first two people in the list are the two actors, as expected, but none of +the people from Linus Pauling onward was especially well-known for acting +(and there certainly isn't any information about that in the short +description text). As it stands, the search attempts to rank all the +elements in the set, based on the information contained in the embedding +model. You can use [`VSimOptions::set_count()`](/content/commands/vsim.md) +to limit the list of elements to just the most relevant few items: + +{{< clients-example set="home_vecsets" step="limited_query" lang_filter="Rust-Sync,Rust-Async" description="Vector similarity search with limits: Restrict results to the top K most similar items using the count parameter" difficulty="intermediate" >}} +{{< /clients-example >}} + +The reason for using text embeddings rather than simple text search is that +the embeddings represent semantic information. This allows a query to find +elements with a similar meaning even if the text is different. For example, +the word "entertainer" doesn't appear in any of the descriptions but if you +use it as a query, the actors and musicians are ranked highest in the results +list: + +{{< clients-example set="home_vecsets" step="entertainer_query" lang_filter="Rust-Sync,Rust-Async" description="Semantic search: Leverage text embeddings to find semantically similar items even when exact keywords don't match" difficulty="intermediate" >}} +{{< /clients-example >}} + +Similarly, if you use "science" as a query, you get the following results: + +``` +'science': ["Marie Curie", "Linus Pauling", "Maryam Mirzakhani", +"Paul Erdos", "Marie Fredriksson", "Freddie Mercury", "Masako Natsume", +"Chaim Topol"] +``` + +The scientists are ranked highest but they are then followed by the +mathematicians. This seems reasonable given the connection between +mathematics and science. + +You can also use +[filter expressions](/content/develop/data-types/vector-sets/filtered-search.md) +with [`VSimOptions::set_filter_expression()`](/content/commands/vsim.md) to +restrict the search further. For example, repeat the "science" query, but +this time limit the results to people who died before the year 2000: + +{{< clients-example set="home_vecsets" step="filtered_query" lang_filter="Rust-Sync,Rust-Async" description="Filtered vector search: Combine vector similarity with attribute filters to narrow results based on metadata conditions" difficulty="advanced" >}} +{{< /clients-example >}} + +Note that the boolean filter expression is applied to items in the list +before the vector distance calculation is performed. Items that don't pass +the filter test are removed from the results completely, rather than just +reduced in rank. This can help to improve the performance of the search +because there is no need to calculate the vector distance for elements that +have already been filtered out of the search. + +## More information + +See the [vector sets](/content/develop/data-types/vector-sets/_index.md) +docs for more information and code examples. See the +[Redis for AI](/content/develop/ai/_index.md) section for more details about +text embeddings and other AI techniques you can use with Redis. diff --git a/local_examples/client-specific/rust-async/home_vecsets.rs b/local_examples/client-specific/rust-async/home_vecsets.rs new file mode 100644 index 0000000000..d98395817b --- /dev/null +++ b/local_examples/client-specific/rust-async/home_vecsets.rs @@ -0,0 +1,321 @@ +// EXAMPLE: home_vecsets +#[cfg(all(test, feature = "vector-sets"))] +mod tests { + // STEP_START import + use fastembed::{EmbeddingModel, InitOptions, TextEmbedding}; + use redis::vector_sets::{ + EmbeddingInput, VAddOptions, VSimOptions, VectorAddInput, VectorSimilaritySearchInput, + }; + use redis::{AsyncCommands, Value}; + // STEP_END + + // STEP_START model + // `fastembed` runs the ONNX-exported `all-MiniLM-L6-v2` encoder locally. + // `TextEmbedding::embed` returns mean-pooled, L2-normalized 384-element + // `Vec` embeddings, matching the pooling and normalization the + // other clients' bindings apply. + fn build_model() -> TextEmbedding { + TextEmbedding::try_new(InitOptions::new(EmbeddingModel::AllMiniLML6V2)) + .expect("Failed to initialize embedding model") + } + // STEP_END + + // STEP_START data + struct PersonData { + born: i32, + died: i32, + description: &'static str, + } + + fn people_data() -> Vec<(&'static str, PersonData)> { + vec![ + ( + "Marie Curie", + PersonData { + born: 1867, + died: 1934, + description: "Polish-French chemist and physicist. The only person \ + ever to win two Nobel prizes for two different sciences.", + }, + ), + ( + "Linus Pauling", + PersonData { + born: 1901, + died: 1994, + description: "American chemist and peace activist. One of only two \ + people to win two Nobel prizes in different fields (chemistry \ + and peace).", + }, + ), + ( + "Freddie Mercury", + PersonData { + born: 1946, + died: 1991, + description: "British musician, best known as the lead singer of \ + the rock band Queen.", + }, + ), + ( + "Marie Fredriksson", + PersonData { + born: 1958, + died: 2019, + description: "Swedish multi-instrumentalist, mainly known as the \ + lead singer and keyboardist of the band Roxette.", + }, + ), + ( + "Paul Erdos", + PersonData { + born: 1913, + died: 1996, + description: "Hungarian mathematician, known for his eccentric \ + personality almost as much as his contributions to many \ + different fields of mathematics.", + }, + ), + ( + "Maryam Mirzakhani", + PersonData { + born: 1977, + died: 2017, + description: "Iranian mathematician. The first woman ever to win \ + the Fields medal for her contributions to mathematics.", + }, + ), + ( + "Masako Natsume", + PersonData { + born: 1957, + died: 1985, + description: "Japanese actress. She was very famous in Japan but \ + was primarily known elsewhere in the world for her portrayal \ + of Tripitaka in the TV series Monkey.", + }, + ), + ( + "Chaim Topol", + PersonData { + born: 1935, + died: 2023, + description: "Israeli actor and singer, usually credited simply \ + as 'Topol'. He was best known for his many appearances as \ + Tevye in the musical Fiddler on the Roof.", + }, + ), + ] + } + // STEP_END + + fn parse_names(value: Value) -> Vec { + match value { + Value::Array(items) => items + .into_iter() + .map(|item| match item { + Value::BulkString(bytes) => String::from_utf8(bytes).expect("utf8"), + Value::SimpleString(s) => s, + other => panic!("Unexpected VSIM item: {other:?}"), + }) + .collect(), + other => panic!("Unexpected VSIM response: {other:?}"), + } + } + + #[tokio::test] + async fn run() { + let mut r = match redis::Client::open("redis://127.0.0.1") { + Ok(client) => match client.get_multiplexed_async_connection().await { + Ok(conn) => conn, + Err(e) => { + println!("Failed to connect to Redis: {e}"); + return; + } + }, + Err(e) => { + println!("Failed to create Redis client: {e}"); + return; + } + }; + // REMOVE_START + let _: () = r.flushall().await.expect("Failed to flushall"); + // REMOVE_END + + let model = build_model(); + + // STEP_START add_data + for (name, details) in people_data() { + let embeddings = model + .embed(vec![details.description], None) + .expect("Failed to embed"); + + let opts = VAddOptions::default().set_attributes(serde_json::json!({ + "born": details.born, + "died": details.died, + })); + + let _: bool = r + .vadd_options( + "famousPeople", + VectorAddInput::Values(EmbeddingInput::Float32(&embeddings[0])), + name, + &opts, + ) + .await + .expect("Failed to run VADD"); + } + // STEP_END + + // STEP_START basic_query + let query_embedding = model.embed(vec!["actors"], None).expect("Failed to embed"); + + let res: Value = r + .vsim( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + ) + .await + .expect("Failed to run VSIM"); + + let actors_results = parse_names(res); + println!("'actors': {actors_results:?}"); + // >>> 'actors': ["Masako Natsume", "Chaim Topol", "Linus Pauling", + // "Marie Fredriksson", "Maryam Mirzakhani", "Marie Curie", + // "Freddie Mercury", "Paul Erdos"] + // REMOVE_START + assert_eq!( + actors_results, + vec![ + "Masako Natsume", + "Chaim Topol", + "Linus Pauling", + "Marie Fredriksson", + "Maryam Mirzakhani", + "Marie Curie", + "Freddie Mercury", + "Paul Erdos", + ] + ); + // REMOVE_END + // STEP_END + + // STEP_START limited_query + let query_embedding = model.embed(vec!["actors"], None).expect("Failed to embed"); + + let opts = VSimOptions::default().set_count(2); + let res: Value = r + .vsim_options( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + &opts, + ) + .await + .expect("Failed to run VSIM"); + + let two_actors_results = parse_names(res); + println!("'actors (2)': {two_actors_results:?}"); + // >>> 'actors (2)': ["Masako Natsume", "Chaim Topol"] + // REMOVE_START + assert_eq!(two_actors_results, vec!["Masako Natsume", "Chaim Topol"]); + // REMOVE_END + // STEP_END + + // STEP_START entertainer_query + let query_embedding = model + .embed(vec!["entertainer"], None) + .expect("Failed to embed"); + + let res: Value = r + .vsim( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + ) + .await + .expect("Failed to run VSIM"); + + let entertainer_results = parse_names(res); + println!("'entertainer': {entertainer_results:?}"); + // >>> 'entertainer': ["Chaim Topol", "Freddie Mercury", + // "Marie Fredriksson", "Linus Pauling", "Masako Natsume", + // "Paul Erdos", "Maryam Mirzakhani", "Marie Curie"] + // REMOVE_START + assert_eq!( + entertainer_results, + vec![ + "Chaim Topol", + "Freddie Mercury", + "Marie Fredriksson", + "Linus Pauling", + "Masako Natsume", + "Paul Erdos", + "Maryam Mirzakhani", + "Marie Curie", + ] + ); + // REMOVE_END + // STEP_END + + let query_embedding = model.embed(vec!["science"], None).expect("Failed to embed"); + + let res: Value = r + .vsim( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + ) + .await + .expect("Failed to run VSIM"); + + let science_results = parse_names(res); + println!("'science': {science_results:?}"); + // >>> 'science': ["Marie Curie", "Linus Pauling", + // "Maryam Mirzakhani", "Paul Erdos", "Marie Fredriksson", + // "Freddie Mercury", "Masako Natsume", "Chaim Topol"] + // REMOVE_START + assert_eq!( + science_results, + vec![ + "Marie Curie", + "Linus Pauling", + "Maryam Mirzakhani", + "Paul Erdos", + "Marie Fredriksson", + "Freddie Mercury", + "Masako Natsume", + "Chaim Topol", + ] + ); + // REMOVE_END + + // STEP_START filtered_query + let query_embedding = model.embed(vec!["science"], None).expect("Failed to embed"); + + let opts = VSimOptions::default().set_filter_expression(".died < 2000"); + let res: Value = r + .vsim_options( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + &opts, + ) + .await + .expect("Failed to run VSIM"); + + let science2000_results = parse_names(res); + println!("'science2000': {science2000_results:?}"); + // >>> 'science2000': ["Marie Curie", "Linus Pauling", + // "Paul Erdos", "Freddie Mercury", "Masako Natsume"] + // REMOVE_START + assert_eq!( + science2000_results, + vec![ + "Marie Curie", + "Linus Pauling", + "Paul Erdos", + "Freddie Mercury", + "Masako Natsume", + ] + ); + // REMOVE_END + // STEP_END + } +} diff --git a/local_examples/client-specific/rust-sync/home_vecsets.rs b/local_examples/client-specific/rust-sync/home_vecsets.rs new file mode 100644 index 0000000000..43ddbf71a3 --- /dev/null +++ b/local_examples/client-specific/rust-sync/home_vecsets.rs @@ -0,0 +1,315 @@ +// EXAMPLE: home_vecsets +#[cfg(all(test, feature = "vector-sets"))] +mod tests { + // STEP_START import + use fastembed::{EmbeddingModel, InitOptions, TextEmbedding}; + use redis::vector_sets::{ + EmbeddingInput, VAddOptions, VSimOptions, VectorAddInput, VectorSimilaritySearchInput, + }; + use redis::{Commands, Value}; + // STEP_END + + // STEP_START model + // `fastembed` runs the ONNX-exported `all-MiniLM-L6-v2` encoder locally. + // `TextEmbedding::embed` returns mean-pooled, L2-normalized 384-element + // `Vec` embeddings, matching the pooling and normalization the + // other clients' bindings apply. + fn build_model() -> TextEmbedding { + TextEmbedding::try_new(InitOptions::new(EmbeddingModel::AllMiniLML6V2)) + .expect("Failed to initialize embedding model") + } + // STEP_END + + // STEP_START data + struct PersonData { + born: i32, + died: i32, + description: &'static str, + } + + fn people_data() -> Vec<(&'static str, PersonData)> { + vec![ + ( + "Marie Curie", + PersonData { + born: 1867, + died: 1934, + description: "Polish-French chemist and physicist. The only person \ + ever to win two Nobel prizes for two different sciences.", + }, + ), + ( + "Linus Pauling", + PersonData { + born: 1901, + died: 1994, + description: "American chemist and peace activist. One of only two \ + people to win two Nobel prizes in different fields (chemistry \ + and peace).", + }, + ), + ( + "Freddie Mercury", + PersonData { + born: 1946, + died: 1991, + description: "British musician, best known as the lead singer of \ + the rock band Queen.", + }, + ), + ( + "Marie Fredriksson", + PersonData { + born: 1958, + died: 2019, + description: "Swedish multi-instrumentalist, mainly known as the \ + lead singer and keyboardist of the band Roxette.", + }, + ), + ( + "Paul Erdos", + PersonData { + born: 1913, + died: 1996, + description: "Hungarian mathematician, known for his eccentric \ + personality almost as much as his contributions to many \ + different fields of mathematics.", + }, + ), + ( + "Maryam Mirzakhani", + PersonData { + born: 1977, + died: 2017, + description: "Iranian mathematician. The first woman ever to win \ + the Fields medal for her contributions to mathematics.", + }, + ), + ( + "Masako Natsume", + PersonData { + born: 1957, + died: 1985, + description: "Japanese actress. She was very famous in Japan but \ + was primarily known elsewhere in the world for her portrayal \ + of Tripitaka in the TV series Monkey.", + }, + ), + ( + "Chaim Topol", + PersonData { + born: 1935, + died: 2023, + description: "Israeli actor and singer, usually credited simply \ + as 'Topol'. He was best known for his many appearances as \ + Tevye in the musical Fiddler on the Roof.", + }, + ), + ] + } + // STEP_END + + fn parse_names(value: Value) -> Vec { + match value { + Value::Array(items) => items + .into_iter() + .map(|item| match item { + Value::BulkString(bytes) => String::from_utf8(bytes).expect("utf8"), + Value::SimpleString(s) => s, + other => panic!("Unexpected VSIM item: {other:?}"), + }) + .collect(), + other => panic!("Unexpected VSIM response: {other:?}"), + } + } + + #[test] + fn run() { + let mut r = match redis::Client::open("redis://127.0.0.1") { + Ok(client) => match client.get_connection() { + Ok(conn) => conn, + Err(e) => { + println!("Failed to connect to Redis: {e}"); + return; + } + }, + Err(e) => { + println!("Failed to create Redis client: {e}"); + return; + } + }; + // REMOVE_START + let _: () = r.flushall().expect("Failed to flushall"); + // REMOVE_END + + let model = build_model(); + + // STEP_START add_data + for (name, details) in people_data() { + let embeddings = model + .embed(vec![details.description], None) + .expect("Failed to embed"); + + let opts = VAddOptions::default().set_attributes(serde_json::json!({ + "born": details.born, + "died": details.died, + })); + + let _: bool = r + .vadd_options( + "famousPeople", + VectorAddInput::Values(EmbeddingInput::Float32(&embeddings[0])), + name, + &opts, + ) + .expect("Failed to run VADD"); + } + // STEP_END + + // STEP_START basic_query + let query_embedding = model.embed(vec!["actors"], None).expect("Failed to embed"); + + let res: Value = r + .vsim( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + ) + .expect("Failed to run VSIM"); + + let actors_results = parse_names(res); + println!("'actors': {actors_results:?}"); + // >>> 'actors': ["Masako Natsume", "Chaim Topol", "Linus Pauling", + // "Marie Fredriksson", "Maryam Mirzakhani", "Marie Curie", + // "Freddie Mercury", "Paul Erdos"] + // REMOVE_START + assert_eq!( + actors_results, + vec![ + "Masako Natsume", + "Chaim Topol", + "Linus Pauling", + "Marie Fredriksson", + "Maryam Mirzakhani", + "Marie Curie", + "Freddie Mercury", + "Paul Erdos", + ] + ); + // REMOVE_END + // STEP_END + + // STEP_START limited_query + let query_embedding = model.embed(vec!["actors"], None).expect("Failed to embed"); + + let opts = VSimOptions::default().set_count(2); + let res: Value = r + .vsim_options( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + &opts, + ) + .expect("Failed to run VSIM"); + + let two_actors_results = parse_names(res); + println!("'actors (2)': {two_actors_results:?}"); + // >>> 'actors (2)': ["Masako Natsume", "Chaim Topol"] + // REMOVE_START + assert_eq!(two_actors_results, vec!["Masako Natsume", "Chaim Topol"]); + // REMOVE_END + // STEP_END + + // STEP_START entertainer_query + let query_embedding = model + .embed(vec!["entertainer"], None) + .expect("Failed to embed"); + + let res: Value = r + .vsim( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + ) + .expect("Failed to run VSIM"); + + let entertainer_results = parse_names(res); + println!("'entertainer': {entertainer_results:?}"); + // >>> 'entertainer': ["Chaim Topol", "Freddie Mercury", + // "Marie Fredriksson", "Linus Pauling", "Masako Natsume", + // "Paul Erdos", "Maryam Mirzakhani", "Marie Curie"] + // REMOVE_START + assert_eq!( + entertainer_results, + vec![ + "Chaim Topol", + "Freddie Mercury", + "Marie Fredriksson", + "Linus Pauling", + "Masako Natsume", + "Paul Erdos", + "Maryam Mirzakhani", + "Marie Curie", + ] + ); + // REMOVE_END + // STEP_END + + let query_embedding = model.embed(vec!["science"], None).expect("Failed to embed"); + + let res: Value = r + .vsim( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + ) + .expect("Failed to run VSIM"); + + let science_results = parse_names(res); + println!("'science': {science_results:?}"); + // >>> 'science': ["Marie Curie", "Linus Pauling", + // "Maryam Mirzakhani", "Paul Erdos", "Marie Fredriksson", + // "Freddie Mercury", "Masako Natsume", "Chaim Topol"] + // REMOVE_START + assert_eq!( + science_results, + vec![ + "Marie Curie", + "Linus Pauling", + "Maryam Mirzakhani", + "Paul Erdos", + "Marie Fredriksson", + "Freddie Mercury", + "Masako Natsume", + "Chaim Topol", + ] + ); + // REMOVE_END + + // STEP_START filtered_query + let query_embedding = model.embed(vec!["science"], None).expect("Failed to embed"); + + let opts = VSimOptions::default().set_filter_expression(".died < 2000"); + let res: Value = r + .vsim_options( + "famousPeople", + VectorSimilaritySearchInput::Values(EmbeddingInput::Float32(&query_embedding[0])), + &opts, + ) + .expect("Failed to run VSIM"); + + let science2000_results = parse_names(res); + println!("'science2000': {science2000_results:?}"); + // >>> 'science2000': ["Marie Curie", "Linus Pauling", + // "Paul Erdos", "Freddie Mercury", "Masako Natsume"] + // REMOVE_START + assert_eq!( + science2000_results, + vec![ + "Marie Curie", + "Linus Pauling", + "Paul Erdos", + "Freddie Mercury", + "Masako Natsume", + ] + ); + // REMOVE_END + // STEP_END + } +}