Source code for smarter.apps.vectorsearch.models.vectorsearch

"""All models for the Vectorsearch API app."""

from django.core.exceptions import ValidationError
from django.db import models

from smarter.apps.account.models import (
    MetaDataWithOwnershipModel,
)
from smarter.apps.secret.models import Secret
from smarter.apps.vectorstore.models import VectorstoreMeta
from smarter.lib import logging
from smarter.lib.django.waffle.switches import SmarterWaffleSwitches

logger = logging.getSmarterLogger(__name__, any_switches=[SmarterWaffleSwitches.VECTORSEARCH_LOGGING])


[docs] class VectorsearchSearchType(models.TextChoices): """Supported retrieval strategies for a Vectorsearch query.""" SIMILARITY = "similarity", "Similarity" SIMILARITY_SCORE_THRESHOLD = "similarity_score_threshold", "Similarity w/ Score Threshold" MMR = "mmr", "Maximal Marginal Relevance"
[docs] class Vectorsearch(MetaDataWithOwnershipModel): """ Implements the Vectorsearch API model. A Vectorsearch is a named, ownable configuration describing how to execute a semantic search against a single VectorstoreMeta. It does not itself hold embeddings/documents -- it references a VectorstoreMeta for that -- and it does not generate text. Combine a Vectorsearch with a Smarter LLMClient to build a RAG pipeline: the top-k results returned by the Vectorsearch are injected into the LLMClient's system prompt at inference time. """ vectorstore = models.ForeignKey( VectorstoreMeta, on_delete=models.PROTECT, related_name="vectorsearches", help_text="The locally-hosted VectorstoreMeta this search queries against.", ) auth_secret = models.ForeignKey( Secret, on_delete=models.PROTECT, related_name="vectorsearches", null=True, blank=True, help_text="Optional credential used to authenticate against the VectorstoreMeta, if required.", ) search_type = models.CharField( max_length=32, choices=VectorsearchSearchType.choices, default=VectorsearchSearchType.SIMILARITY, help_text="The retrieval strategy to use when querying the VectorstoreMeta.", ) k = models.PositiveSmallIntegerField( default=4, help_text="Number of top results to return from the search.", ) score_threshold = models.FloatField( null=True, blank=True, help_text=( "Minimum relevance score (0.0-1.0) a result must meet to be included. " f"Only applicable when search_type='{VectorsearchSearchType.SIMILARITY_SCORE_THRESHOLD}'." ), ) fetch_k = models.PositiveSmallIntegerField( null=True, blank=True, help_text=( "Number of candidate documents to fetch before applying MMR re-ranking. " f"Only applicable when search_type='{VectorsearchSearchType.MMR}'." ), ) lambda_mult = models.FloatField( null=True, blank=True, help_text=( "Diversity vs. relevance trade-off for MMR, between 0.0 (max diversity) and 1.0 (max relevance). " f"Only applicable when search_type='{VectorsearchSearchType.MMR}'." ), ) metadata_filter = models.JSONField( null=True, blank=True, help_text="Optional metadata filter applied to the VectorstoreMeta query, e.g. {'source': 'faq'}.", ) is_enabled = models.BooleanField( default=True, help_text="Whether this Vectorsearch is active and eligible to be queried.", ) # pylint: disable=C0115 class Meta: verbose_name_plural = "Vectorsearches" unique_together = ("user_profile", "name")
[docs] def clean(self): super().clean() if self.k is not None and self.k < 1: raise ValidationError({"k": "k must be a positive integer."}) if self.search_type == VectorsearchSearchType.SIMILARITY_SCORE_THRESHOLD: if self.score_threshold is None: raise ValidationError( {"score_threshold": "score_threshold is required when search_type is similarity_score_threshold."} ) elif self.score_threshold is not None: raise ValidationError( {"score_threshold": "score_threshold is only valid when search_type is similarity_score_threshold."} ) if self.search_type == VectorsearchSearchType.MMR: if self.fetch_k is not None and self.fetch_k < self.k: raise ValidationError({"fetch_k": "fetch_k must be greater than or equal to k."}) if self.lambda_mult is not None and not 0.0 <= self.lambda_mult <= 1.0: raise ValidationError({"lambda_mult": "lambda_mult must be between 0.0 and 1.0."}) else: if self.fetch_k is not None: raise ValidationError({"fetch_k": "fetch_k is only valid when search_type is mmr."}) if self.lambda_mult is not None: raise ValidationError({"lambda_mult": "lambda_mult is only valid when search_type is mmr."})
def __str__(self) -> str: return f"{self.name} -> {self.vectorstore} ({self.search_type})"
__all__ = ["Vectorsearch", "VectorsearchSearchType"]