mirror of
https://git.datalinker.icu/comfyanonymous/ComfyUI
synced 2026-09-09 07:27:02 +08:00
295 lines
11 KiB
Python
295 lines
11 KiB
Python
from pydantic import BaseModel
|
|
from typing import Dict, List, Any, Optional, Tuple
|
|
import logging
|
|
import numpy as np
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class Top10DocumentRetrievalConfigs(BaseModel):
|
|
"""Configuration for Top-10 Document Retrieval workflow"""
|
|
retrieval_count: int = 10 # Number of documents to retrieve
|
|
similarity_threshold: float = 0.6 # Minimum similarity score
|
|
ranking_method: str = "cosine_similarity" # Options: cosine_similarity, dot_product, euclidean
|
|
use_filter: bool = False # Whether to filter results
|
|
filter_criteria: Dict[str, Any] = {}
|
|
use_reranking: bool = False # Whether to use reranking
|
|
|
|
class Top10DocumentRetrieval:
|
|
"""
|
|
Top-10 Document Retrieval system based on mermaid chart in README.md
|
|
Performs vector search to find the most relevant documents
|
|
"""
|
|
|
|
def __init__(self, resources: Dict[str, Any], configs: Top10DocumentRetrievalConfigs):
|
|
"""
|
|
Initialize with injected dependencies and configuration
|
|
|
|
Args:
|
|
resources: Dictionary of resources including search services
|
|
configs: Configuration for Top-10 Document Retrieval
|
|
"""
|
|
self.resources = resources
|
|
self.configs = configs
|
|
|
|
# Extract needed services from resources
|
|
self.encoder_service = resources.get("encoder_service")
|
|
self.similarity_search_service = resources.get("similarity_search_service")
|
|
self.document_storage = resources.get("document_storage_service")
|
|
|
|
logger.info("Top10DocumentRetrieval initialized with services")
|
|
|
|
def execute(self,
|
|
input_data_point: str,
|
|
documents: List[Any] = None,
|
|
document_vectors: List[Any] = None
|
|
) -> dict[str, Any]:
|
|
"""
|
|
Execute the document retrieval flow based on the mermaid chart
|
|
|
|
Args:
|
|
input_data_point: The query or information request
|
|
documents: Optional list of documents to search
|
|
document_vectors: Optional list of document vectors to search
|
|
|
|
Returns:
|
|
Dictionary of documents containing potentially relevant documents, along with potentially relevant metadata.
|
|
"""
|
|
logger.info(f"Starting top-10 document retrieval for: {input_data_point}")
|
|
|
|
# Step 1: Encode the query
|
|
encoded_query = self._encode_query(input_data_point)
|
|
|
|
# Step 2: Get vector embeddings and document IDs from storage if not provided
|
|
if documents is None or document_vectors is None:
|
|
documents, document_vectors = self._get_documents_and_vectors()
|
|
|
|
# Step 3: Perform similarity search
|
|
similarity_scores, doc_ids = self._similarity_search(
|
|
encoded_query,
|
|
document_vectors,
|
|
[doc.get("id") for doc in documents]
|
|
)
|
|
|
|
# Step 4: Rank and sort results
|
|
ranked_results = self._rank_and_sort_results(similarity_scores, doc_ids)
|
|
|
|
# Step 5: Filter to top-N results
|
|
top_doc_ids = self._filter_to_top_n(ranked_results)
|
|
|
|
# Step 6: Retrieve potentially relevant documents
|
|
potentially_relevant_docs = self._retrieve_relevant_documents(documents, top_doc_ids)
|
|
|
|
logger.info(f"Retrieved {len(potentially_relevant_docs)} potentially relevant documents")
|
|
return {
|
|
"relevant_documents": potentially_relevant_docs,
|
|
"scores": {doc_id: score for doc_id, score in ranked_results},
|
|
"top_doc_ids": top_doc_ids
|
|
}
|
|
|
|
def retrieve_top_documents(self, input_data_point: str, documents: List[Any], document_vectors: List[Any]) -> List[Any]:
|
|
"""
|
|
Public method to retrieve top documents for an input query
|
|
|
|
Args:
|
|
input_data_point: The query to search for
|
|
documents: Documents to search
|
|
document_vectors: Vectors for the documents
|
|
|
|
Returns:
|
|
List of potentially relevant documents
|
|
"""
|
|
result = self.control_flow(input_data_point, documents, document_vectors)
|
|
return result["relevant_documents"]
|
|
|
|
def _encode_query(self, input_data_point: str) -> Any:
|
|
"""
|
|
Encode the input query into a vector representation
|
|
|
|
Args:
|
|
input_data_point: The query to encode
|
|
|
|
Returns:
|
|
Vector representation of the query
|
|
"""
|
|
logger.debug(f"Encoding query: {input_data_point}")
|
|
return self.encoder_service.encode(input_data_point)
|
|
|
|
def _get_documents_and_vectors(self) -> Tuple[List[Any], List[Any]]:
|
|
"""
|
|
Get all documents and their vectors from storage
|
|
|
|
Returns:
|
|
Tuple of (documents, document_vectors)
|
|
"""
|
|
logger.debug("Getting documents and vectors from storage")
|
|
return self.document_storage.get_documents_and_vectors()
|
|
|
|
def _similarity_search(self, encoded_query: Any, document_vectors: List[Any],
|
|
doc_ids: List[str]) -> Tuple[List[float], List[str]]:
|
|
"""
|
|
Perform similarity search between the query and document vectors
|
|
|
|
Args:
|
|
encoded_query: Vector representation of the query
|
|
document_vectors: List of document vector embeddings
|
|
doc_ids: List of document IDs corresponding to the vectors
|
|
|
|
Returns:
|
|
Tuple of (similarity_scores, document_ids)
|
|
"""
|
|
logger.debug("Performing similarity search")
|
|
|
|
# In a real implementation, this would use an efficient vector search
|
|
similarity_scores = []
|
|
|
|
for vector in document_vectors:
|
|
if self.configs.ranking_method == "cosine_similarity":
|
|
score = self._cosine_similarity(encoded_query, vector.get("embedding"))
|
|
elif self.configs.ranking_method == "dot_product":
|
|
score = self._dot_product(encoded_query, vector.get("embedding"))
|
|
elif self.configs.ranking_method == "euclidean":
|
|
score = self._euclidean_distance(encoded_query, vector.get("embedding"))
|
|
# Convert distance to similarity score (higher is more similar)
|
|
score = 1.0 / (1.0 + score)
|
|
else:
|
|
score = 0.0
|
|
|
|
similarity_scores.append(score)
|
|
|
|
# If the similarity search service is available, use it instead
|
|
if self.similarity_search_service:
|
|
return self.similarity_search_service.search(
|
|
encoded_query, document_vectors, doc_ids
|
|
)
|
|
|
|
return similarity_scores, doc_ids
|
|
|
|
def _rank_and_sort_results(self, similarity_scores: List[float],
|
|
doc_ids: List[str]) -> List[Tuple[str, float]]:
|
|
"""
|
|
Rank and sort results by similarity score
|
|
|
|
Args:
|
|
similarity_scores: List of similarity scores
|
|
doc_ids: List of document IDs
|
|
|
|
Returns:
|
|
List of (document_id, score) tuples sorted by score
|
|
"""
|
|
logger.debug("Ranking and sorting results")
|
|
|
|
# Create a list of (document_id, score) tuples
|
|
result_tuples = list(zip(doc_ids, similarity_scores))
|
|
|
|
# Sort by score in descending order
|
|
sorted_results = sorted(result_tuples, key=lambda x: x[1], reverse=True)
|
|
|
|
return sorted_results
|
|
|
|
def _filter_to_top_n(self, ranked_results: List[Tuple[str, float]]) -> List[str]:
|
|
"""
|
|
Filter to top N results
|
|
|
|
Args:
|
|
ranked_results: List of (document_id, score) tuples
|
|
|
|
Returns:
|
|
List of top N document IDs
|
|
"""
|
|
logger.debug(f"Filtering to top {self.configs.retrieval_count} results")
|
|
|
|
# Apply threshold filter if configured
|
|
filtered_results = []
|
|
|
|
if self.configs.use_filter:
|
|
for doc_id, score in ranked_results:
|
|
if score >= self.configs.similarity_threshold:
|
|
filtered_results.append(doc_id)
|
|
else:
|
|
filtered_results = [doc_id for doc_id, _ in ranked_results]
|
|
|
|
# Return top N results
|
|
return filtered_results[:self.configs.retrieval_count]
|
|
|
|
def _retrieve_relevant_documents(self, documents: List[Any], top_doc_ids: List[str]) -> List[Any]:
|
|
"""
|
|
Retrieve potentially relevant documents
|
|
|
|
Args:
|
|
documents: List of all documents
|
|
top_doc_ids: List of top document IDs
|
|
|
|
Returns:
|
|
List of potentially relevant documents
|
|
"""
|
|
logger.debug("Retrieving potentially relevant documents")
|
|
|
|
# Create a map of document ID to document for faster lookup
|
|
doc_map = {doc.get("id"): doc for doc in documents}
|
|
|
|
# Retrieve documents by ID
|
|
relevant_docs = []
|
|
|
|
for doc_id in top_doc_ids:
|
|
if doc_id in doc_map:
|
|
relevant_docs.append(doc_map[doc_id])
|
|
|
|
return relevant_docs
|
|
|
|
def _cosine_similarity(self, vec1: List[float], vec2: List[float]) -> float:
|
|
"""Calculate cosine similarity between two vectors"""
|
|
if not vec1 or not vec2:
|
|
return 0.0
|
|
|
|
try:
|
|
# Convert to numpy arrays for efficient calculation
|
|
vec1_np = np.array(vec1)
|
|
vec2_np = np.array(vec2)
|
|
|
|
# Calculate dot product
|
|
dot = np.dot(vec1_np, vec2_np)
|
|
|
|
# Calculate norms
|
|
norm1 = np.linalg.norm(vec1_np)
|
|
norm2 = np.linalg.norm(vec2_np)
|
|
|
|
# Calculate cosine similarity
|
|
similarity = dot / (norm1 * norm2)
|
|
return float(similarity)
|
|
except Exception as e:
|
|
logger.error(f"Error calculating cosine similarity: {e}")
|
|
return 0.0
|
|
|
|
def _dot_product(self, vec1: List[float], vec2: List[float]) -> float:
|
|
"""Calculate dot product between two vectors"""
|
|
if not vec1 or not vec2:
|
|
return 0.0
|
|
|
|
try:
|
|
# Convert to numpy arrays for efficient calculation
|
|
vec1_np = np.array(vec1)
|
|
vec2_np = np.array(vec2)
|
|
|
|
# Calculate dot product
|
|
dot = np.dot(vec1_np, vec2_np)
|
|
return float(dot)
|
|
except Exception as e:
|
|
logger.error(f"Error calculating dot product: {e}")
|
|
return 0.0
|
|
|
|
def _euclidean_distance(self, vec1: List[float], vec2: List[float]) -> float:
|
|
"""Calculate Euclidean distance between two vectors"""
|
|
if not vec1 or not vec2:
|
|
return float('inf')
|
|
|
|
try:
|
|
# Convert to numpy arrays for efficient calculation
|
|
vec1_np = np.array(vec1)
|
|
vec2_np = np.array(vec2)
|
|
|
|
# Calculate Euclidean distance
|
|
distance = np.linalg.norm(vec1_np - vec2_np)
|
|
return float(distance)
|
|
except Exception as e:
|
|
logger.error(f"Error calculating Euclidean distance: {e}")
|
|
return float('inf') |