"""Shared multi-protocol model transport used by evaluation features.""" from typing import Any import httpx from agenteval.model_protocols import ProtocolAdapterError, get_protocol_adapter from agenteval.services.model_configs import ModelRuntimeConfig class ModelGatewayError(RuntimeError): pass class ModelGateway: def __init__(self, timeout: float = 60.0, transport: httpx.AsyncBaseTransport | None = None): self.timeout = timeout self.transport = transport async def _post(self, config: ModelRuntimeConfig, payload: dict[str, Any]) -> dict[str, Any]: adapter = self._adapter(config) try: async with httpx.AsyncClient(timeout=self.timeout, transport=self.transport) as client: response = await client.post( config.endpoint_url, headers=adapter.headers(config.api_key), json=payload, ) response.raise_for_status() data = response.json() if not isinstance(data, dict): raise ModelGatewayError("模型接口返回格式不是 JSON 对象") return data except ModelGatewayError: raise except httpx.HTTPStatusError as exc: detail = exc.response.text[:300] raise ModelGatewayError(f"模型接口返回 HTTP {exc.response.status_code}: {detail}") from exc except Exception as exc: raise ModelGatewayError(f"模型接口调用失败: {exc}") from exc @staticmethod def _adapter(config: ModelRuntimeConfig): try: return get_protocol_adapter(config.provider) except ProtocolAdapterError as exc: raise ModelGatewayError(str(exc)) from exc async def chat( self, config: ModelRuntimeConfig, messages: list[dict[str, str]], temperature: float = 0.2, ) -> str: adapter = self._adapter(config) try: payload = adapter.chat_payload(config.model_name, messages, temperature) return adapter.parse_chat(await self._post(config, payload)) except ProtocolAdapterError as exc: raise ModelGatewayError(str(exc)) from exc async def embed(self, config: ModelRuntimeConfig, inputs: str | list[str]) -> list[list[float]]: adapter = self._adapter(config) try: payload = adapter.embedding_payload(config.model_name, inputs) return adapter.parse_embeddings(await self._post(config, payload)) except ProtocolAdapterError as exc: raise ModelGatewayError(str(exc)) from exc async def moderate(self, config: ModelRuntimeConfig, text: str) -> dict[str, Any]: adapter = self._adapter(config) try: payload = adapter.moderation_payload(config.model_name, text) return adapter.parse_moderation(await self._post(config, payload)) except ProtocolAdapterError as exc: raise ModelGatewayError(str(exc)) from exc async def test_connection(self, config: ModelRuntimeConfig) -> str: if config.capability.value == "chat": await self.chat(config, [{"role": "user", "content": "回复 OK"}], temperature=0) elif config.capability.value == "embedding": vectors = await self.embed(config, "connection test") if not vectors or not vectors[0]: raise ModelGatewayError("Embedding 接口未返回向量") else: await self.moderate(config, "connection test") return "连接成功"