"""BFL FLUX HTTP 客户端 — 异步提交 + 轮询。 文档: https://docs.bfl.ai/quick_start/generating_images """ from __future__ import annotations import os from typing import Any import httpx from dotenv import load_dotenv _ = load_dotenv() DEFAULT_API_BASE = "https://api.bfl.ai/v1" def _api_key() -> str: key = os.environ.get("BFL_API_KEY", "").strip() if not key: raise ValueError("缺少环境变量 BFL_API_KEY") return key def _headers() -> dict[str, str]: return { "accept": "application/json", "x-key": _api_key(), "Content-Type": "application/json", } def submit_generation( *, model: str, prompt: str, width: int | None = None, height: int | None = None, parameters: dict[str, Any] | None = None, ) -> dict[str, Any]: """POST {BFL_API_BASE}/{model},返回含 id、polling_url 等(以 BFL 响应为准)。""" base = os.environ.get("BFL_API_BASE", DEFAULT_API_BASE).rstrip("/") model_path = model.strip().lstrip("/") url = f"{base}/{model_path}" body: dict[str, Any] = dict(parameters) if parameters else {} body["prompt"] = prompt if width is not None: body["width"] = width if height is not None: body["height"] = height with httpx.Client(timeout=120.0) as client: r = client.post(url, headers=_headers(), json=body) try: data = r.json() except Exception: r.raise_for_status() raise RuntimeError(r.text[:2000]) from None if r.status_code >= 400: err = data.get("detail") if isinstance(data, dict) else None msg = err if err is not None else str(data) raise RuntimeError(f"BFL HTTP {r.status_code}: {msg}") if not isinstance(data, dict): raise RuntimeError("提交响应不是 JSON 对象") return data def poll_result(*, polling_url: str, request_id: str) -> dict[str, Any]: """GET polling_url,Query: id=request_id(与官方示例一致)。""" with httpx.Client(timeout=60.0) as client: r = client.get( polling_url.strip(), headers={ "accept": "application/json", "x-key": _api_key(), }, params={"id": request_id.strip()}, ) try: data = r.json() except Exception: r.raise_for_status() raise RuntimeError(r.text[:2000]) from None if r.status_code >= 400: msg = data if isinstance(data, dict) else str(data) raise RuntimeError(f"BFL poll HTTP {r.status_code}: {msg}") if not isinstance(data, dict): raise RuntimeError("轮询响应不是 JSON 对象") return data