mod indexing_node;
mod persist;
mod retrieve;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use anyhow::{Context as _, Result, bail};
use derive_builder::Builder;
pub use qdrant_client;
use qdrant_client::qdrant::{self, SparseVectorParamsBuilder, SparseVectorsConfigBuilder};
use swiftide_core::indexing::{EmbeddedField, TextNode};
const DEFAULT_COLLECTION_NAME: &str = "swiftide";
const DEFAULT_QDRANT_URL: &str = "http://localhost:6334";
const DEFAULT_BATCH_SIZE: usize = 50;
#[derive(Builder, Clone)]
#[builder(
pattern = "owned",
setter(strip_option),
build_fn(error = "anyhow::Error")
)]
pub struct Qdrant {
#[builder(setter(into), default = "self.default_client()?")]
#[allow(clippy::missing_fields_in_debug)]
client: Arc<qdrant_client::Qdrant>,
#[builder(default = "DEFAULT_COLLECTION_NAME.to_string()")]
#[builder(setter(into))]
collection_name: String,
vector_size: u64,
#[builder(default = "Distance::Cosine")]
vector_distance: Distance,
#[builder(default = "Some(DEFAULT_BATCH_SIZE)")]
batch_size: Option<usize>,
#[builder(private, default = "Self::default_vectors()")]
pub(crate) vectors: HashMap<EmbeddedField, VectorConfig>,
#[builder(private, default)]
pub(crate) sparse_vectors: HashMap<EmbeddedField, SparseVectorConfig>,
}
impl Qdrant {
pub fn builder() -> QdrantBuilder {
QdrantBuilder::default()
}
pub fn try_from_url(url: impl AsRef<str>) -> Result<QdrantBuilder> {
Ok(QdrantBuilder::default().client(
qdrant_client::Qdrant::from_url(url.as_ref())
.api_key(std::env::var("QDRANT_API_KEY"))
.build()?,
))
}
pub async fn create_index_if_not_exists(&self) -> Result<()> {
let collection_name = &self.collection_name;
tracing::debug!("Checking if collection {collection_name} exists");
if self.client.collection_exists(collection_name).await? {
tracing::warn!(
"Collection {collection_name} exists, skipping collection creation; if vector configurations have not changed, you can ignore this message"
);
return Ok(());
}
let vectors_config = self.create_vectors_config()?;
tracing::debug!(?vectors_config, "Adding vectors config");
let mut collection =
qdrant::CreateCollectionBuilder::new(collection_name).vectors_config(vectors_config);
if let Some(sparse_vectors_config) = self.create_sparse_vectors_config() {
tracing::debug!(?sparse_vectors_config, "Adding sparse vectors config");
collection = collection.sparse_vectors_config(sparse_vectors_config);
}
tracing::info!("Creating collection {collection_name}");
self.client.create_collection(collection).await?;
Ok(())
}
fn create_vectors_config(&self) -> Result<qdrant_client::qdrant::vectors_config::Config> {
if self.vectors.is_empty() {
bail!("No configured vectors");
} else if self.vectors.len() == 1 && self.sparse_vectors.is_empty() {
let config = self
.vectors
.values()
.next()
.context("Has one vector config")?;
let vector_params = self.create_vector_params(config);
return Ok(qdrant::vectors_config::Config::Params(vector_params));
}
let mut map = HashMap::<String, qdrant::VectorParams>::default();
for (embedded_field, config) in &self.vectors {
let vector_name = embedded_field.to_string();
let vector_params = self.create_vector_params(config);
map.insert(vector_name, vector_params);
}
Ok(qdrant::vectors_config::Config::ParamsMap(
qdrant::VectorParamsMap { map },
))
}
fn create_sparse_vectors_config(&self) -> Option<qdrant::SparseVectorConfig> {
if self.sparse_vectors.is_empty() {
return None;
}
let mut sparse_vectors_config = SparseVectorsConfigBuilder::default();
for embedded_field in self.sparse_vectors.keys() {
let vector_name = format!("{embedded_field}_sparse");
let vector_params = SparseVectorParamsBuilder::default();
sparse_vectors_config.add_named_vector_params(vector_name, vector_params);
}
Some(sparse_vectors_config.into())
}
fn create_vector_params(&self, config: &VectorConfig) -> qdrant::VectorParams {
let size = config.vector_size.unwrap_or(self.vector_size);
let distance = config.distance.unwrap_or(self.vector_distance);
tracing::debug!(
"Creating vector params: size={}, distance={:?}",
size,
distance
);
qdrant::VectorParamsBuilder::new(size, distance).build()
}
pub fn client(&self) -> &Arc<qdrant_client::Qdrant> {
&self.client
}
}
impl QdrantBuilder {
#[allow(clippy::unused_self)]
fn default_client(&self) -> Result<Arc<qdrant_client::Qdrant>> {
let client = qdrant_client::Qdrant::from_url(
&std::env::var("QDRANT_URL").unwrap_or(DEFAULT_QDRANT_URL.to_string()),
)
.api_key(std::env::var("QDRANT_API_KEY"))
.build()
.context("Could not build default qdrant client")?;
Ok(Arc::new(client))
}
#[must_use]
pub fn with_vector(mut self, vector: impl Into<VectorConfig>) -> QdrantBuilder {
if self.vectors.is_none() {
self = self.vectors(HashMap::default());
}
let vector = vector.into();
if let Some(vectors) = self.vectors.as_mut()
&& let Some(overridden_vector) = vectors.insert(vector.embedded_field.clone(), vector)
{
tracing::warn!(
"Overriding named vector config: {}",
overridden_vector.embedded_field
);
}
self
}
#[must_use]
pub fn with_sparse_vector(mut self, vector: impl Into<SparseVectorConfig>) -> QdrantBuilder {
if self.sparse_vectors.is_none() {
self = self.sparse_vectors(HashMap::default());
}
let vector = vector.into();
if let Some(vectors) = self.sparse_vectors.as_mut()
&& let Some(overridden_vector) = vectors.insert(vector.embedded_field.clone(), vector)
{
tracing::warn!(
"Overriding named vector config: {}",
overridden_vector.embedded_field
);
}
self
}
fn default_vectors() -> HashMap<EmbeddedField, VectorConfig> {
HashMap::from([(EmbeddedField::default(), VectorConfig::default())])
}
}
#[allow(clippy::missing_fields_in_debug)]
impl std::fmt::Debug for Qdrant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Qdrant")
.field("collection_name", &self.collection_name)
.field("vector_size", &self.vector_size)
.field("batch_size", &self.batch_size)
.finish()
}
}
#[derive(Clone, Builder, Default)]
pub struct VectorConfig {
#[builder(default)]
pub(super) embedded_field: EmbeddedField,
#[builder(setter(into, strip_option), default)]
vector_size: Option<u64>,
#[builder(setter(into, strip_option), default)]
distance: Option<qdrant::Distance>,
}
impl VectorConfig {
pub fn builder() -> VectorConfigBuilder {
VectorConfigBuilder::default()
}
}
impl From<EmbeddedField> for VectorConfig {
fn from(value: EmbeddedField) -> Self {
Self {
embedded_field: value,
..Default::default()
}
}
}
#[derive(Clone, Builder, Default)]
pub struct SparseVectorConfig {
embedded_field: EmbeddedField,
}
impl From<EmbeddedField> for SparseVectorConfig {
fn from(value: EmbeddedField) -> Self {
Self {
embedded_field: value,
}
}
}
pub type Distance = qdrant::Distance;
struct NodeWithVectors<'a> {
node: &'a TextNode,
vector_fields: HashSet<&'a EmbeddedField>,
}
impl<'a> NodeWithVectors<'a> {
pub fn new(node: &'a TextNode, vector_fields: HashSet<&'a EmbeddedField>) -> Self {
Self {
node,
vector_fields,
}
}
}