Download app/upstream/base.py from li2895/anuma2api: direct link, hf CLI and curl.
- Browser
- Download file 4.39 kB
-
https://huggingface.co/spaces/li2895/anuma2api/resolve/main/app/upstream/base.py
- Command line
-
hf download hf://spaces/li2895/anuma2api/app/upstream/base.py
-
curl -L -o base.py https://huggingface.co/spaces/li2895/anuma2api/resolve/main/app/upstream/base.py
4.39 kB
| """上游适配器接口契约(换目标网站时实现这些;详见 references/upstream-adapters.md)。 | |
| 5 个角色: | |
| - :class:`AuthProvider`:认证(get_auth 返回请求头/cookie;is_auth_failure 判定失效)。 | |
| - :class:`UpstreamClient`:上游请求(``stream(prompt, model_id) → IREvent`` 流;多模态上传)。 | |
| - :class:`EventParser`:原生事件 → IREvent(**换上游唯一核心改动**)。 | |
| - :class:`ModelRegistry`:模型目录(catalog / normalize / upstream_id_for)。 | |
| - tool 策略(native 直通 / prompt 注入解析)由 settings.upstream_strategy 决定。 | |
| """ | |
| from __future__ import annotations | |
| from abc import ABC, abstractmethod | |
| from collections.abc import AsyncIterator | |
| from typing import Any, Literal | |
| from app.account import FailReason | |
| from app.events import IREvent | |
| # 默认的失效 body 关键词(可按目标站在 AuthProvider.classify_failure 中覆盖)。 | |
| _AUTH_HINTS = ("unauthorized", "invalid token", "not authenticated", "login required") | |
| _BAN_HINTS = ("banned", "suspended", "disabled", "forbidden", "封禁", "封号") | |
| _QUOTA_HINTS = ("quota", "limit reached", "insufficient", "credit", "额度", "配额", "余额不足") | |
| _CF_HINTS = ("cloudflare", "captcha", "turnstile", "challenge", "验证码") | |
| class AuthProvider(ABC): | |
| async def get_auth(self) -> dict[str, str]: | |
| """返回要注入上游请求的头/cookie(如 {"Authorization": "Bearer ..."} 或 {"Cookie": ...})。""" | |
| def is_auth_failure(self, exc: BaseException) -> bool: | |
| """判断异常是否为账号认证失败(喂给 app.deps.classify_failure)。""" | |
| def classify_failure(self, exc: BaseException) -> FailReason | None: | |
| """把上游异常映射成 FailReason。 | |
| 默认实现按 HTTP 状态码 + body 关键词分类;按目标站覆盖本方法即可自定义 | |
| (如 Superdesign 的额度耗尽、Pro 模型错误、visitor_id 校验失败等)。 | |
| 返回 ``None`` 表示非账号级失效,deps 会回退到通用逻辑。 | |
| """ | |
| status: int | None = None | |
| body = "" | |
| import httpx | |
| if isinstance(exc, httpx.HTTPStatusError): | |
| status = exc.response.status_code | |
| try: | |
| body = exc.response.text.lower() | |
| except Exception: # noqa: BLE001 | |
| body = "" | |
| text = f"{body} {str(exc).lower()}" | |
| if status in (401, 403) or any(h in text for h in _AUTH_HINTS): | |
| return FailReason.AUTH_FAILED | |
| if status == 429 or any(h in text for h in _QUOTA_HINTS): | |
| return FailReason.QUOTA_EXHAUSTED | |
| if status == 451 or any(h in text for h in _CF_HINTS): | |
| return FailReason.CF_CHALLENGE | |
| if any(h in text for h in _BAN_HINTS): | |
| return FailReason.BANNED | |
| return None | |
| class UpstreamClient(ABC): | |
| async def stream(self, prompt: str, model_id: str | None = None, **kw: Any) -> AsyncIterator[IREvent]: | |
| """发送 prompt 到上游,yield IREvent 流。""" | |
| async def upload_image(self, data: bytes, mime: str, filename: str = "") -> str: | |
| """多模态:上传图片,返回上游引用(url/id)。默认不支持,上游按范式实现。""" | |
| raise NotImplementedError( | |
| "该上游未实现图片上传;参考 references/upstream-adapters.md 的两种上传范式。") | |
| async def upload_file(self, data: bytes, mime: str, filename: str = "") -> str: | |
| raise NotImplementedError("该上游未实现文件上传;参考 references/upstream-adapters.md。") | |
| class EventParser(ABC): | |
| def parse(self, raw: Any) -> list[IREvent]: | |
| """把上游单个原生事件(dict/bytes/str)解析成 0..n 个 IREvent。换上游唯一核心改动。""" | |
| class ModelRegistry(ABC): | |
| def catalog(self) -> list[dict[str, Any]]: | |
| """模型目录:[{"id", "name", "owner", "upstream_id"}]。""" | |
| def normalize(self, model: str | None) -> str: | |
| """客户端传入的 model → catalog id。""" | |
| def upstream_id_for(self, model_id: str) -> str | None: | |
| """catalog id → 上游真实模型标识(如上游 model name / llm_config_id)。""" | |
| ToolStrategy = Literal["native", "prompt"] | |