"""重排序服务类型 — 请求/响应模型和协议定义。 Reranker 适配器通过 HTTP 调用重排序端点,经由 ``RerankerService`` 或直接从 ``ProviderManager`` 消费。 用法:: service = RerankerService(manager) result = await service.rerank("查询", ["文档1", "文档2"]) # 或通过工厂直接调用: adapter = manager.create_reranker("rerank-model") result = await adapter.rerank(RerankRequest(query="...", documents=["..."])) """ from __future__ import annotations from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable from app.ai_provider.types.errors import AIProviderError if TYPE_CHECKING: from app.ai_provider.config.models import ModelConfig, ModelEndpointConfig class RerankerServiceError(AIProviderError): """Reranker API 调用失败的基础错误。""" # ── 请求 / 响应 ──────────────────────────────────────────────────────── @dataclass class RerankRequest: """重排序请求。 属性: query: 查询文本。 documents: 待重排序的文档列表。 model: 模型标识符。 top_n: 返回的最相关文档数量(nil 表示全部)。 max_chunks_per_doc: 每个文档最多切分为多少块。 return_documents: 返回结果中是否包含文档文本。 """ query: str documents: list[str] model: str = "" top_n: int | None = None max_chunks_per_doc: int | None = None return_documents: bool = False @dataclass class RerankResult: """单条重排序结果。""" index: int relevance_score: float text: str | None = None @dataclass class RerankResponse: """重排序 API 的完整响应。""" results: list[RerankResult] model: str = "" usage: dict[str, int] | None = None # ── Protocol ──────────────────────────────────────────────────────────── @runtime_checkable class RerankerProtocol(Protocol): """Reranker 适配器的协议。""" async def rerank(self, request: RerankRequest) -> RerankResponse: """对文档进行重排序。 异常: RerankerServiceError: API 调用失败或响应异常时抛出。 """ ... async def close(self) -> None: """释放适配器持有的 HTTP 连接。""" ... # ── 共享工厂基类 ────────────────────────────────────────────────────── class RerankerAdapterBase: """Reranker 适配器基类,提供共享的工厂方法。 子类应在 ``__init__`` 中接受 ``base_url``、``api_key``、``model``、 ``request_timeout_seconds`` 参数。 """ _adapter_type: str = "reranker" _default_timeout: int = 60 @classmethod def from_endpoint( cls, endpoint: ModelEndpointConfig, *, timeout_seconds: int | None = None, ) -> Any: """从已解析的端点配置构建适配器。""" base_url = endpoint.base_url api_key = endpoint.api_key if not base_url: raise RerankerServiceError( status_code=500, code="reranker_endpoint_invalid", message="Reranker endpoint has no resolved base_url.", details={"endpoint": endpoint.name}, ) return cls( base_url=base_url, api_key=api_key, model=endpoint.model, request_timeout_seconds=timeout_seconds or endpoint.request_timeout_seconds or cls._default_timeout, ) @classmethod def from_model_config( cls, config: ModelConfig, *, timeout_seconds: int | None = None, ) -> Any: """从逻辑模型配置构建适配器(使用第一个活跃端点)。""" for ep in config.endpoints: if ep.status == "active": return cls.from_endpoint(ep, timeout_seconds=timeout_seconds) raise RerankerServiceError( status_code=500, code="reranker_no_endpoint", message=f"Reranker model '{config.name}' has no active endpoint.", )