rerank_service.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. """
  2. 重排序服务模块
  3. 支持多个重排序服务提供商:
  4. - 阿里百炼平台 (dashscope)
  5. - 硅基流动平台 (siliconflow)
  6. 作者:开发工程师
  7. 日期:2025年01月
  8. """
  9. import logging
  10. import requests
  11. import json
  12. from enum import Enum
  13. from typing import List, Optional, Dict, Any
  14. from pydantic import BaseModel
  15. from config import Config
  16. # 配置日志
  17. logging.basicConfig(level=logging.INFO)
  18. logger = logging.getLogger(__name__)
  19. class RerankProvider(Enum):
  20. """重排序服务提供商"""
  21. DASHSCOPE = "dashscope" # 阿里百炼
  22. SILICONFLOW = "siliconflow" # 硅基流动
  23. class RerankDocument(BaseModel):
  24. """重排序文档模型"""
  25. index: int # 原始文档索引
  26. content: str # 文档内容
  27. relevance_score: float # 相关性分数
  28. metadata: Optional[Dict[str, Any]] = None # 元数据
  29. class RerankResponse(BaseModel):
  30. """重排序响应模型"""
  31. documents: List[RerankDocument] # 重排序后的文档
  32. success: bool = True
  33. error_message: Optional[str] = None
  34. class RerankService:
  35. """重排序服务类
  36. 支持多个重排序服务提供商,通过统一接口提供文档重排序功能
  37. """
  38. def __init__(self, provider: RerankProvider, api_key: str, model_name: str, endpoint: Optional[str] = None):
  39. """初始化重排序服务
  40. Args:
  41. provider: 服务提供商
  42. api_key: API密钥
  43. model_name: 模型名称
  44. endpoint: 自定义API端点(可选)
  45. """
  46. self.provider = provider
  47. self.api_key = api_key
  48. self.model_name = model_name
  49. self.endpoint = endpoint
  50. logger.info(f"重排序服务初始化完成: provider={provider.value}, model={model_name}, endpoint={self.endpoint}")
  51. def rerank_documents(self, query: str, documents: List[str], top_n: Optional[int] = None) -> RerankResponse:
  52. """重排序文档
  53. Args:
  54. query: 查询文本
  55. documents: 文档列表
  56. top_n: 返回前N个文档,默认返回全部
  57. Returns:
  58. RerankResponse: 重排序结果
  59. """
  60. try:
  61. if not query or not documents:
  62. logger.warning("查询或文档列表为空")
  63. return RerankResponse(
  64. documents=[],
  65. success=False,
  66. error_message="查询或文档列表为空"
  67. )
  68. logger.info(f"🔄 开始重排序: 查询='{query[:50]}...', 文档数量={len(documents)}, top_n={top_n}")
  69. if self.provider == RerankProvider.DASHSCOPE:
  70. response = self._rerank_dashscope(query, documents, top_n)
  71. elif self.provider == RerankProvider.SILICONFLOW:
  72. response = self._rerank_siliconflow(query, documents, top_n)
  73. else:
  74. raise ValueError(f"不支持的重排序提供商: {self.provider}")
  75. if response.success:
  76. logger.info(f"✅ 重排序完成: 返回{len(response.documents)}个文档")
  77. for i, doc in enumerate(response.documents):
  78. logger.info(f" #{i+1}: 相关性={doc.relevance_score:.4f}, 内容='{doc.content[:50]}...'")
  79. return response
  80. except Exception as e:
  81. logger.error(f"❌ 重排序失败: {e}")
  82. return RerankResponse(
  83. documents=[],
  84. success=False,
  85. error_message=str(e)
  86. )
  87. def _rerank_dashscope(self, query: str, documents: List[str], top_n: Optional[int] = None) -> RerankResponse:
  88. """阿里百炼平台重排序"""
  89. headers = {
  90. "Authorization": f"Bearer {self.api_key}",
  91. "Content-Type": "application/json"
  92. }
  93. data = {
  94. "model": self.model_name,
  95. "input": {
  96. "query": query,
  97. "documents": documents
  98. },
  99. "parameters": {
  100. "return_documents": True, # 显式设置返回文档内容
  101. "top_n": top_n or len(documents)
  102. }
  103. }
  104. logger.debug(f"🌐 发送请求到百炼平台: {self.endpoint}")
  105. response = requests.post(self.endpoint, headers=headers, json=data, timeout=30)
  106. response.raise_for_status()
  107. result = response.json()
  108. logger.debug(f"📨 百炼平台响应: {json.dumps(result, ensure_ascii=False)[:200]}...")
  109. # 解析响应
  110. rerank_docs = []
  111. if "output" in result and "results" in result["output"]:
  112. for item in result["output"]["results"]:
  113. # 百炼平台返回格式: document.text
  114. document = item.get("document", {})
  115. content = document.get("text", "") if isinstance(document, dict) else str(document)
  116. rerank_doc = RerankDocument(
  117. index=item.get("index", 0),
  118. content=content,
  119. relevance_score=float(item.get("relevance_score", 0.0))
  120. )
  121. rerank_docs.append(rerank_doc)
  122. else:
  123. logger.warning(f"百炼平台响应格式异常: {result}")
  124. return RerankResponse(
  125. documents=[],
  126. success=False,
  127. error_message="响应格式异常"
  128. )
  129. return RerankResponse(documents=rerank_docs)
  130. def _rerank_siliconflow(self, query: str, documents: List[str], top_n: Optional[int] = None) -> RerankResponse:
  131. """硅基流动平台重排序"""
  132. headers = {
  133. "Authorization": f"Bearer {self.api_key}",
  134. "Content-Type": "application/json"
  135. }
  136. data = {
  137. "model": self.model_name,
  138. "query": query,
  139. "documents": documents,
  140. "return_documents": True # 硅基流动需要明确指定返回文档内容
  141. }
  142. # 硅基流动支持top_n参数
  143. if top_n is not None:
  144. data["top_n"] = top_n
  145. logger.debug(f"🌐 发送请求到硅基流动: {self.endpoint}")
  146. response = requests.post(self.endpoint, headers=headers, json=data, timeout=30)
  147. response.raise_for_status()
  148. result = response.json()
  149. logger.debug(f"📨 硅基流动响应: {json.dumps(result, ensure_ascii=False)[:200]}...")
  150. # 解析响应
  151. rerank_docs = []
  152. if "results" in result:
  153. for item in result["results"]:
  154. # 硅基流动平台返回格式: document.text
  155. document = item.get("document", {})
  156. content = document.get("text", "") if isinstance(document, dict) else str(document)
  157. rerank_doc = RerankDocument(
  158. index=item.get("index", 0),
  159. content=content,
  160. relevance_score=float(item.get("relevance_score", 0.0))
  161. )
  162. rerank_docs.append(rerank_doc)
  163. else:
  164. logger.warning(f"硅基流动响应格式异常: {result}")
  165. return RerankResponse(
  166. documents=[],
  167. success=False,
  168. error_message="响应格式异常"
  169. )
  170. return RerankResponse(documents=rerank_docs)
  171. def health_check(self) -> Dict[str, Any]:
  172. """健康检查"""
  173. status = {
  174. "service": "RerankService",
  175. "provider": self.provider.value,
  176. "model": self.model_name,
  177. "endpoint": self.endpoint,
  178. "status": "unknown"
  179. }
  180. try:
  181. # 简单的健康检查:用最少的文档进行测试
  182. test_response = self.rerank_documents(
  183. query="测试",
  184. documents=["这是一个测试文档"],
  185. top_n=1
  186. )
  187. if test_response.success:
  188. status["status"] = "healthy"
  189. status["test_result"] = "ok"
  190. else:
  191. status["status"] = "error"
  192. status["error"] = test_response.error_message
  193. except Exception as e:
  194. status["status"] = "error"
  195. status["error"] = str(e)
  196. return status
  197. def create_rerank_service() -> Optional[RerankService]:
  198. """创建重排序服务实例(工厂函数)
  199. Returns:
  200. RerankService: 重排序服务实例,失败时返回None
  201. """
  202. try:
  203. # 检查配置
  204. if not Config.RERANK_PROVIDER:
  205. logger.info("未配置RERANK_PROVIDER,跳过重排序服务初始化")
  206. return None
  207. if not Config.RERANK_API_KEY:
  208. logger.warning("缺少RERANK_API_KEY配置")
  209. return None
  210. if not Config.RERANK_MODEL_NAME:
  211. logger.warning("缺少RERANK_MODEL_NAME配置")
  212. return None
  213. # 创建提供商枚举
  214. try:
  215. provider = RerankProvider(Config.RERANK_PROVIDER.lower())
  216. except ValueError:
  217. logger.error(f"不支持的重排序提供商: {Config.RERANK_PROVIDER}")
  218. return None
  219. # 创建服务实例
  220. service = RerankService(
  221. provider=provider,
  222. api_key=Config.RERANK_API_KEY,
  223. model_name=Config.RERANK_MODEL_NAME,
  224. endpoint=Config.RERANK_ENDPOINT
  225. )
  226. logger.info(f"重排序服务创建成功: {provider.value}")
  227. return service
  228. except Exception as e:
  229. logger.error(f"创建重排序服务失败: {e}")
  230. return None
  231. # 全局重排序服务实例
  232. _rerank_service_instance = None
  233. def get_rerank_service() -> Optional[RerankService]:
  234. """获取重排序服务实例(单例模式)"""
  235. global _rerank_service_instance
  236. if _rerank_service_instance is None:
  237. _rerank_service_instance = create_rerank_service()
  238. return _rerank_service_instance
  239. if __name__ == "__main__":
  240. # 测试代码
  241. rerank_service = get_rerank_service()
  242. if rerank_service:
  243. # 健康检查
  244. health = rerank_service.health_check()
  245. print(f"健康检查: {health}")
  246. # 测试重排序
  247. test_docs = [
  248. "苹果是一种水果,含有丰富的维生素",
  249. "量子计算是计算科学的前沿领域,苹果也在做这方面的研究",
  250. "苹果是一家科技企业,主要生产手机、电脑等产品"
  251. ]
  252. result = rerank_service.rerank_documents(
  253. query="苹果公司",
  254. documents=test_docs,
  255. top_n=3
  256. )
  257. if result.success:
  258. print("\n重排序结果:")
  259. for i, doc in enumerate(result.documents):
  260. print(f"{i+1}. [分数: {doc.relevance_score:.4f}] {doc.content}")
  261. else:
  262. print(f"重排序失败: {result.error_message}")
  263. else:
  264. print("重排序服务未配置或初始化失败")