Files
hunjing/backend/app/ai_provider/reranker/base.py
T
2026-08-21 16:00:13 +08:00

148 lines
4.4 KiB
Python

"""重排序服务类型 — 请求/响应模型和协议定义。
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.",
)