#[cfg(feature = "embeddings-multimodal")]
use std::path::Path;
#[cfg(feature = "embeddings-multimodal")]
use laurus::CandleClipEmbedder;
#[cfg(feature = "embeddings-multimodal")]
use laurus::Embedder;
#[cfg(feature = "embeddings-multimodal")]
use laurus::Result;
#[cfg(feature = "embeddings-multimodal")]
use laurus::storage::file::FileStorageConfig;
#[cfg(feature = "embeddings-multimodal")]
use laurus::storage::{StorageConfig, StorageFactory};
#[cfg(feature = "embeddings-multimodal")]
use laurus::vector::DistanceMetric;
#[cfg(feature = "embeddings-multimodal")]
use laurus::vector::VectorSearchRequestBuilder;
#[cfg(feature = "embeddings-multimodal")]
use laurus::vector::VectorStore;
#[cfg(feature = "embeddings-multimodal")]
use laurus::vector::{FieldOption, FlatOption};
#[cfg(feature = "embeddings-multimodal")]
use laurus::vector::{VectorFieldConfig, VectorIndexConfig};
#[cfg(feature = "embeddings-multimodal")]
use laurus::{DataValue, Document};
#[cfg(feature = "embeddings-multimodal")]
use tempfile::TempDir;
#[cfg(feature = "embeddings-multimodal")]
#[tokio::main]
async fn main() -> Result<()> {
use std::sync::Arc;
println!("=== Multimodal Search Example (CLIP) ===\n");
let temp_dir = TempDir::new().unwrap();
let storage_config = StorageConfig::File(FileStorageConfig::new(temp_dir.path()));
let storage = StorageFactory::create(storage_config)?;
println!("Loading CLIP model (this may take a while on first run)...");
let embedder = CandleClipEmbedder::new("openai/clip-vit-base-patch32")?;
println!("Model loaded: {}", embedder.name());
let field_config = VectorFieldConfig {
vector: Some(FieldOption::Flat(FlatOption {
dimension: 3,
distance: DistanceMetric::Cosine,
base_weight: 1.0,
quantizer: None,
embedder: None,
})),
lexical: None,
};
let index_config = VectorIndexConfig::builder()
.embedder(Arc::new(embedder))
.field("content", field_config)
.build()?;
let engine = VectorStore::new(storage, index_config)?;
println!("\n--- Indexing Images ---");
let resources_dir = Path::new("resources");
let images_dir = resources_dir.join("images");
if !images_dir.exists() {
eprintln!("Error: resources/images directory not found.");
eprintln!(
"Please ensure you are running from the project root and resources/images exists."
);
return Ok(());
}
let mut indexed_count: u64 = 0;
let entries = std::fs::read_dir(&images_dir)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.is_file()
&& path
.extension()
.is_some_and(|ext| ext == "jpg" || ext == "jpeg" || ext == "png")
{
let filename = path.file_name().unwrap().to_string_lossy().to_string();
println!("Indexing image: {}", filename);
let bytes = std::fs::read(&path)?;
let doc = Document::builder()
.add_field("content", DataValue::Bytes(bytes, None))
.add_field("filename", DataValue::Text(filename.clone()))
.add_field("type", DataValue::Text("image".into()))
.build();
indexed_count += 1;
engine
.upsert_document_by_internal_id(indexed_count, doc)
.await?;
}
}
println!("Indexed {} images.", indexed_count);
println!("\n--- Indexing Text ---");
let texts = vec![
"A cute kitten looking at the camera",
"A loyal dog standing in the grass",
"Two dogs playing together",
"A landscape with mountains and a lake", ];
for text in &texts {
println!("Indexing text: \"{}\"", text);
let doc = Document::builder()
.add_field("content", DataValue::Text((*text).into()))
.add_field("text", DataValue::Text((*text).into()))
.add_field("type", DataValue::Text("text".into()))
.build();
indexed_count += 1;
engine
.upsert_document_by_internal_id(indexed_count, doc)
.await?;
}
engine.commit().await?;
println!("\n--- Search 1: Text-to-Image ---");
let query_text = "a photo of a cat";
println!("Query: \"{}\"", query_text);
let request = VectorSearchRequestBuilder::new()
.add_text("content", query_text)
.limit(3)
.build();
let results = engine.search(request)?;
print_results(&results);
println!("\n--- Search 2: Image-to-Image ---");
let query_image_path = resources_dir.join("query_image.jpg");
if query_image_path.exists() {
println!("Query Image: {}", query_image_path.display());
let query_bytes = std::fs::read(&query_image_path)?;
let request = VectorSearchRequestBuilder::new()
.add_bytes("content", query_bytes, Some("image/jpeg"))
.limit(3)
.build();
let results = engine.search(request)?;
print_results(&results);
} else {
println!(
"Skipping Image-to-Image demo: {} not found",
query_image_path.display()
);
}
Ok(())
}
#[cfg(feature = "embeddings-multimodal")]
fn print_results(results: &laurus::vector::VectorSearchResults) {
for (i, hit) in results.hits.iter().enumerate() {
println!("{}. Doc ID: {}, Score: {:.4}", i + 1, hit.doc_id, hit.score);
}
}
#[cfg(not(feature = "embeddings-multimodal"))]
fn main() {
eprintln!("This example requires the 'embeddings-multimodal' feature.");
eprintln!(
"Please run with: cargo run --example multimodal_search --features embeddings-multimodal"
);
std::process::exit(1);
}