You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

weight_rerank.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import math
  2. from collections import Counter
  3. from typing import Optional
  4. import numpy as np
  5. from core.model_manager import ModelManager
  6. from core.model_runtime.entities.model_entities import ModelType
  7. from core.rag.datasource.keyword.jieba.jieba_keyword_table_handler import JiebaKeywordTableHandler
  8. from core.rag.embedding.cached_embedding import CacheEmbedding
  9. from core.rag.models.document import Document
  10. from core.rag.rerank.entity.weight import VectorSetting, Weights
  11. from core.rag.rerank.rerank_base import BaseRerankRunner
  12. class WeightRerankRunner(BaseRerankRunner):
  13. def __init__(self, tenant_id: str, weights: Weights) -> None:
  14. self.tenant_id = tenant_id
  15. self.weights = weights
  16. def run(
  17. self,
  18. query: str,
  19. documents: list[Document],
  20. score_threshold: Optional[float] = None,
  21. top_n: Optional[int] = None,
  22. user: Optional[str] = None,
  23. ) -> list[Document]:
  24. """
  25. Run rerank model
  26. :param query: search query
  27. :param documents: documents for reranking
  28. :param score_threshold: score threshold
  29. :param top_n: top n
  30. :param user: unique user id if needed
  31. :return:
  32. """
  33. unique_documents = []
  34. doc_ids = set()
  35. for document in documents:
  36. if (
  37. document.provider == "dify"
  38. and document.metadata is not None
  39. and document.metadata["doc_id"] not in doc_ids
  40. ):
  41. doc_ids.add(document.metadata["doc_id"])
  42. unique_documents.append(document)
  43. else:
  44. if document not in unique_documents:
  45. unique_documents.append(document)
  46. documents = unique_documents
  47. query_scores = self._calculate_keyword_score(query, documents)
  48. query_vector_scores = self._calculate_cosine(self.tenant_id, query, documents, self.weights.vector_setting)
  49. rerank_documents = []
  50. for document, query_score, query_vector_score in zip(documents, query_scores, query_vector_scores):
  51. score = (
  52. self.weights.vector_setting.vector_weight * query_vector_score
  53. + self.weights.keyword_setting.keyword_weight * query_score
  54. )
  55. if score_threshold and score < score_threshold:
  56. continue
  57. if document.metadata is not None:
  58. document.metadata["score"] = score
  59. rerank_documents.append(document)
  60. rerank_documents.sort(key=lambda x: x.metadata["score"] if x.metadata else 0, reverse=True)
  61. return rerank_documents[:top_n] if top_n else rerank_documents
  62. def _calculate_keyword_score(self, query: str, documents: list[Document]) -> list[float]:
  63. """
  64. Calculate BM25 scores
  65. :param query: search query
  66. :param documents: documents for reranking
  67. :return:
  68. """
  69. keyword_table_handler = JiebaKeywordTableHandler()
  70. query_keywords = keyword_table_handler.extract_keywords(query, None)
  71. documents_keywords = []
  72. for document in documents:
  73. # get the document keywords
  74. document_keywords = keyword_table_handler.extract_keywords(document.page_content, None)
  75. if document.metadata is not None:
  76. document.metadata["keywords"] = document_keywords
  77. documents_keywords.append(document_keywords)
  78. # Counter query keywords(TF)
  79. query_keyword_counts = Counter(query_keywords)
  80. # total documents
  81. total_documents = len(documents)
  82. # calculate all documents' keywords IDF
  83. all_keywords = set()
  84. for document_keywords in documents_keywords:
  85. all_keywords.update(document_keywords)
  86. keyword_idf = {}
  87. for keyword in all_keywords:
  88. # calculate include query keywords' documents
  89. doc_count_containing_keyword = sum(1 for doc_keywords in documents_keywords if keyword in doc_keywords)
  90. # IDF
  91. keyword_idf[keyword] = math.log((1 + total_documents) / (1 + doc_count_containing_keyword)) + 1
  92. query_tfidf = {}
  93. for keyword, count in query_keyword_counts.items():
  94. tf = count
  95. idf = keyword_idf.get(keyword, 0)
  96. query_tfidf[keyword] = tf * idf
  97. # calculate all documents' TF-IDF
  98. documents_tfidf = []
  99. for document_keywords in documents_keywords:
  100. document_keyword_counts = Counter(document_keywords)
  101. document_tfidf = {}
  102. for keyword, count in document_keyword_counts.items():
  103. tf = count
  104. idf = keyword_idf.get(keyword, 0)
  105. document_tfidf[keyword] = tf * idf
  106. documents_tfidf.append(document_tfidf)
  107. def cosine_similarity(vec1, vec2):
  108. intersection = set(vec1.keys()) & set(vec2.keys())
  109. numerator = sum(vec1[x] * vec2[x] for x in intersection)
  110. sum1 = sum(vec1[x] ** 2 for x in vec1)
  111. sum2 = sum(vec2[x] ** 2 for x in vec2)
  112. denominator = math.sqrt(sum1) * math.sqrt(sum2)
  113. if not denominator:
  114. return 0.0
  115. else:
  116. return float(numerator) / denominator
  117. similarities = []
  118. for document_tfidf in documents_tfidf:
  119. similarity = cosine_similarity(query_tfidf, document_tfidf)
  120. similarities.append(similarity)
  121. # for idx, similarity in enumerate(similarities):
  122. # print(f"Document {idx + 1} similarity: {similarity}")
  123. return similarities
  124. def _calculate_cosine(
  125. self, tenant_id: str, query: str, documents: list[Document], vector_setting: VectorSetting
  126. ) -> list[float]:
  127. """
  128. Calculate Cosine scores
  129. :param query: search query
  130. :param documents: documents for reranking
  131. :return:
  132. """
  133. query_vector_scores = []
  134. model_manager = ModelManager()
  135. embedding_model = model_manager.get_model_instance(
  136. tenant_id=tenant_id,
  137. provider=vector_setting.embedding_provider_name,
  138. model_type=ModelType.TEXT_EMBEDDING,
  139. model=vector_setting.embedding_model_name,
  140. )
  141. cache_embedding = CacheEmbedding(embedding_model)
  142. query_vector = cache_embedding.embed_query(query)
  143. for document in documents:
  144. # calculate cosine similarity
  145. if document.metadata and "score" in document.metadata:
  146. query_vector_scores.append(document.metadata["score"])
  147. else:
  148. # transform to NumPy
  149. vec1 = np.array(query_vector)
  150. vec2 = np.array(document.vector)
  151. # calculate dot product
  152. dot_product = np.dot(vec1, vec2)
  153. # calculate norm
  154. norm_vec1 = np.linalg.norm(vec1)
  155. norm_vec2 = np.linalg.norm(vec2)
  156. # calculate cosine similarity
  157. cosine_sim = dot_product / (norm_vec1 * norm_vec2)
  158. query_vector_scores.append(cosine_sim)
  159. return query_vector_scores