"""适配器接口与中间数据形(ARCH §4.2/§4.4)。 适配器把 `LlmRequest` 翻译成目标厂商请求,并把响应/流/usage 翻译回统一中间形。 """ from __future__ import annotations from collections.abc import AsyncIterator from typing import Protocol, runtime_checkable from pydantic import BaseModel, ConfigDict from ..types import LlmRequest # 瞬时(可退避重试)的 HTTP 状态码判定:429 限流 或 5xx 服务端错误。 _RATE_LIMITED_STATUS = 429 _SERVER_ERROR_MIN_STATUS = 500 def _is_transient_status(status: object) -> bool: return isinstance(status, int) and ( status == _RATE_LIMITED_STATUS or status >= _SERVER_ERROR_MIN_STATUS ) def is_transient_by_name( exc: Exception, names: frozenset[str], *, extra_codes: bool = False ) -> bool: """统一的瞬时错误判定(DRY:三个适配器共用)。 ① 异常类名命中 `names`(按名匹配,避免硬依赖各厂商 SDK 异常类型);或 ② `status_code` 属性是 429/5xx;或 ③ `extra_codes=True` 时额外检查 `code` 属性是否为 429/5xx(Gemini 用 `exc.code`)。 """ if type(exc).__name__ in names: return True if _is_transient_status(getattr(exc, "status_code", None)): return True if extra_codes and _is_transient_status(getattr(exc, "code", None)): return True return False class Capabilities(BaseModel): structured_output: bool = False prefix_cache: bool = False thinking: bool = False class ProviderUsage(BaseModel): input_tokens: int output_tokens: int cache_read_tokens: int = 0 class ProviderResult(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) text: str usage: ProviderUsage parsed: BaseModel | None = None # output_schema 命中时的结构化结果(§4.4) class StreamChunk(BaseModel): """流式块:文本增量(usage=None),或末尾用量块(text="")。""" text: str = "" usage: ProviderUsage | None = None @runtime_checkable class ProviderAdapter(Protocol): provider: str def capabilities(self) -> Capabilities: ... async def complete(self, req: LlmRequest, model: str) -> ProviderResult: ... def stream(self, req: LlmRequest, model: str) -> AsyncIterator[StreamChunk]: ...