P1-2 非可重试错误(持续 401/403)也 record_failure,坏 key 可触发熔断。 P1-9 _complete_structured 的 assert 改显式 raise ValueError(-O 安全)。 P2 GatewayRun 抽到 orchestrator/_protocols.py 单点(去 4 处重复); _is_transient 抽到 adapters/base.py is_transient_by_name(去 3 处重复); Gemini Protocol 改 async def;gateway._retrying 去无用 async。
80 lines
2.3 KiB
Python
80 lines
2.3 KiB
Python
"""适配器接口与中间数据形(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]: ...
|