feat(ai): 图像主力从 tokenssr 切到 yunqi(支持 images/edits 图生图)

TOKENSSR 余额将尽,切换图像默认 provider 到 yunqi。yunqi 的图生图
端点需 Azure 风格 ?api-version=2025-04-01-preview,不带会 404。

- provider 加 api_version 字段,OpenAICompatible 生图/改图端点自动附加
  ?api-version=(配了才加,tokenssr 等零影响)
- build_provider 解析 api_version:DB(ModelProvider.metadata)优先 → .env 回退
- 迁移 0007:启用 yunqi:gpt-image-2 为唯一 active 图像模型,禁用其余
- 摘除死代码 YunqiProvider 导出(旧类写死"不支持图生图"会误导)

端到端验证:图生图(三视图,36s)+文生图(14s)均正常出图。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-18 18:15:34 +08:00
co-authored by Claude Opus 4.8
parent a71be19500
commit 4399da2090
6 changed files with 89 additions and 5 deletions
@@ -1,7 +1,6 @@
from .base import AIProvider, AIProviderResult
from .openai_compatible import OpenAICompatibleProvider
from .volcano import TtsNotConfigured, VolcanoArkProvider, VolcanoTtsProvider
from .yunqi import YunqiProvider
__all__ = [
@@ -11,5 +10,4 @@ __all__ = [
"TtsNotConfigured",
"VolcanoArkProvider",
"VolcanoTtsProvider",
"YunqiProvider",
]
@@ -24,6 +24,15 @@ class OpenAICompatibleProvider(VolcanoArkProvider):
if not self.base_url:
raise ValueError("OpenAICompatibleProvider requires base_url (中转站地址)")
def _endpoint_url(self, endpoint: str) -> str:
"""拼端点 URL。若 provider 配了 api_version(如 yunqi),附加 Azure 风格 ?api-version=...
—— yunqi 的 images/edits 不带此参数会 404。tokenssr 等不配则零影响。"""
url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
if self.api_version:
sep = "&" if "?" in url else "?"
url = f"{url}{sep}api-version={self.api_version}"
return url
def image_generation(
self,
*,
@@ -41,7 +50,7 @@ class OpenAICompatibleProvider(VolcanoArkProvider):
body["image"] = image
# 实测中转站生图延迟可达 75s+,超时给到 300s
response = requests.post(
f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}",
self._endpoint_url(endpoint),
headers={"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"},
json=body,
timeout=300,
@@ -79,7 +88,7 @@ class OpenAICompatibleProvider(VolcanoArkProvider):
raise ValueError("image_edit 至少需要一张参考图")
data = {"model": model, "prompt": prompt, "size": size, "n": "1"}
response = requests.post(
f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}",
self._endpoint_url(endpoint),
headers={"Authorization": f"Bearer {self.api_key}"}, # multipart 不要手设 Content-Type
files=files,
data=data,
@@ -15,6 +15,9 @@ from .base import AIProviderResult
class VolcanoArkProvider:
api_key: str | None = None
base_url: str | None = None
# 部分 OpenAI 兼容中转站(如 yunqi)的生图/改图端点要求 Azure 风格的 ?api-version=... 查询参数,
# 不带会 404。火山官方直连用不到,默认 None。仅 OpenAICompatibleProvider 在拼 URL 时附加。
api_version: str | None = None
def __post_init__(self) -> None:
self.api_key = self.api_key or settings.VOLCANO.get("ark_api_key")