11import time
22import logging
3+ from statistics import median
34
45from pgvector .django import L2Distance
56
67from .sentencetTransformer_model import TransformerModel
78from ..models .model_embeddings import Embeddings
9+ from ..models .model_search_usage import SemanticSearchUsage
810
911logger = logging .getLogger (__name__ )
1012
1113def get_closest_embeddings (
12- user , message_data , document_name = None , guid = None , num_results = 10 , return_metrics = False
14+ user , message_data , document_name = None , guid = None , num_results = 10
1315):
1416 """
1517 Find the closest embeddings to a given message for a specific user.
@@ -26,42 +28,27 @@ def get_closest_embeddings(
2628 Filter results to a specific document GUID (takes precedence over document_name)
2729 num_results : int, default 10
2830 Maximum number of results to return
29- return_metrics : bool, default False
30- If True, return a tuple of (results, metrics) instead of just results
3131
3232 Returns
3333 -------
34- list[dict] or tuple[list[dict], dict]
35- If return_metrics is False (default):
36- List of dictionaries containing embedding results with keys:
37- - name: document name
38- - text: embedded text content
39- - page_number: page number in source document
40- - chunk_number: chunk number within the document
41- - distance: L2 distance from query embedding
42- - file_id: GUID of the source file
43-
44- If return_metrics is True:
45- Tuple of (results, metrics) where metrics is a dictionary containing:
46- - encoding_time: Time to encode query (seconds)
47- - db_query_time: Time for database query (seconds)
48- - total_time: Total execution time (seconds)
49- - num_results_returned: Number of results returned
50- - min_distance: Minimum L2 distance
51- - max_distance: Maximum L2 distance
52- - avg_distance: Average L2 distance
34+ list[dict]
35+ List of dictionaries containing embedding results with keys:
36+ - name: document name
37+ - text: embedded text content
38+ - page_number: page number in source document
39+ - chunk_number: chunk number within the document
40+ - distance: L2 distance from query embedding
41+ - file_id: GUID of the source file
5342 """
5443
55- start_time = time .time ()
56-
5744 encoding_start = time .time ()
5845 transformerModel = TransformerModel .get_instance ().model
5946 embedding_message = transformerModel .encode (message_data )
6047 encoding_time = time .time () - encoding_start
6148
6249 db_query_start = time .time ()
6350
64- # Start building the query based on the message's embedding
51+ # Django QuerySets are lazily evaluated
6552 closest_embeddings_query = (
6653 Embeddings .objects .filter (upload_file__uploaded_by = user )
6754 .annotate (
@@ -70,18 +57,18 @@ def get_closest_embeddings(
7057 .order_by ("distance" )
7158 )
7259
73- # Filtering results to a document GUID takes precedence over filtering results to document name
60+ # Filtering to a document GUID takes precedence over a document name
7461 if guid :
7562 closest_embeddings_query = closest_embeddings_query .filter (
7663 upload_file__guid = guid
7764 )
7865 elif document_name :
7966 closest_embeddings_query = closest_embeddings_query .filter (name = document_name )
8067
81- # Slice the results to limit to num_results
68+ # Slicing is equivalent to SQL's LIMIT clause
8269 closest_embeddings_query = closest_embeddings_query [:num_results ]
8370
84- # Format the results to be returned
71+ # Iterating evaluates the QuerySet and hits the database
8572 # TODO: Research improving the query evaluation performance
8673 results = [
8774 {
@@ -96,38 +83,41 @@ def get_closest_embeddings(
9683 ]
9784
9885 db_query_time = time .time () - db_query_start
99- total_time = time .time () - start_time
100-
101- # Calculate distance/similarity statistics
102- num_results_returned = len (results )
103-
104- #TODO: Handle user having no uploaded docs or doc filtering returning no matches
105-
106- distances = [r ["distance" ] for r in results ]
107- min_distance = min (distances )
108- max_distance = max (distances )
109- avg_distance = sum (distances ) / num_results_returned
110-
111- logger .info (
112- f"Embedding search completed: "
113- f"Encoding time: { encoding_time :.3f} s, "
114- f"DB query time: { db_query_time :.3f} s, "
115- f"Total time: { total_time :.3f} s, "
116- f"Returned: { num_results_returned } results, "
117- f"Distance range: [{ min_distance :.3f} , { max_distance :.3f} ], "
118- f"Average distance: { avg_distance :.3f} "
119- )
12086
121- if return_metrics :
122- metrics = {
123- "encoding_time" : encoding_time ,
124- "db_query_time" : db_query_time ,
125- "total_time" : total_time ,
126- "num_results_returned" : num_results_returned ,
127- "min_distance" : min_distance ,
128- "max_distance" : max_distance ,
129- "avg_distance" : avg_distance ,
130- }
131- return results , metrics
87+ try :
88+ # Handle user having no uploaded docs or doc filtering returning no matches
89+ if results :
90+ distances = [r ["distance" ] for r in results ]
91+ SemanticSearchUsage .objects .create (
92+ query_text = message_data ,
93+ user = user if (user and user .is_authenticated ) else None ,
94+ document_guid = guid ,
95+ document_name = document_name ,
96+ num_results_requested = num_results ,
97+ encoding_time = encoding_time ,
98+ db_query_time = db_query_time ,
99+ num_results_returned = len (results ),
100+ max_distance = max (distances ),
101+ median_distance = median (distances ),
102+ min_distance = min (distances )
103+ )
104+ else :
105+ logger .warning ("Semantic search returned no results" )
106+
107+ SemanticSearchUsage .objects .create (
108+ query_text = message_data ,
109+ user = user if (user and user .is_authenticated ) else None ,
110+ document_guid = guid ,
111+ document_name = document_name ,
112+ num_results_requested = num_results ,
113+ encoding_time = encoding_time ,
114+ db_query_time = db_query_time ,
115+ num_results_returned = 0 ,
116+ max_distance = None ,
117+ median_distance = None ,
118+ min_distance = None
119+ )
120+ except Exception as e :
121+ logger .error (f"Failed to create semantic search usage database record: { e } " )
132122
133123 return results
0 commit comments