"""anuma.ai ModelRegistry:模型目录(探测自 GET portal.anuma.ai/api/v1/curated-models)。 字段:id(catalog id,即上游 curated-models 的 id 如 ``kimi/kimi-k3``)/ name / owner(provider)/ upstream_id(Fireworks 格式 ``accounts/fireworks/models/<短名>``)。 ⚠️ 列表为 2026-08-01 探测快照;上游增删模型时用 scripts/probe_catalog.py 重新生成。 """ from __future__ import annotations import re from typing import Any from app.upstream.base import ModelRegistry __all__ = [ "DEFAULT_MODEL", "MODEL_CATALOG", "IMAGE_MODEL_CATALOG", "normalize_image_model", "DefaultModelRegistry", ] DEFAULT_MODEL = "openai/gpt-5.6-luna" # 上游未识别 model 时的回退模型(实测所见) # 生图模型(2026-08-08 从 chat.anuma.ai HAR 抓取):/v1/images 路由用。 # key = AnumaMediaMCP-anuma_create_image 工具 model 参数值;name = 对外展示名。 # 前缀 * 为 GPT-5.6 Luna 生图实测可用模型;tier alias(auto/anuma-*)由上游按订阅分层。 IMAGE_MODEL_CATALOG: list[dict[str, Any]] = [ {"id": "gpt-image-2", "name": "GPT Image 2"}, {"id": "flux-2-pro", "name": "Flux 2 Pro"}, {"id": "flux-2-klein-4b", "name": "Flux 2 Klein 4B"}, {"id": "grok-imagine", "name": "Grok Imagine"}, {"id": "nano-banana", "name": "Nano Banana"}, {"id": "nano-banana-pro", "name": "Nano Banana Pro"}, {"id": "nano-banana-2", "name": "Nano Banana 2"}, {"id": "anuma-flash", "name": "Anuma Flash (tier alias)"}, {"id": "anuma-pro", "name": "Anuma Pro (tier alias)"}, {"id": "anuma-flash-private", "name": "Anuma Flash Private (tier alias)"}, {"id": "anuma-pro-private", "name": "Anuma Pro Private (tier alias)"}, ] _IMAGE_BY_KEY: dict[str, dict[str, Any]] = {} for _im in IMAGE_MODEL_CATALOG: _IMAGE_BY_KEY[_im["id"]] = _im _IMAGE_BY_KEY[_im["name"].lower()] = _im def normalize_image_model(model: str | None) -> str | None: """客户端传入的 gen 图片模型 → 上游 tool 参数值;空/未知 → None(由客户端容错)。""" if not model: return None if model in _IMAGE_BY_KEY: return _IMAGE_BY_KEY[model]["id"] low = model.lower() if low in _IMAGE_BY_KEY: return _IMAGE_BY_KEY[low]["id"] norm = re.sub(r"[^a-z0-9]", "", low) for m in IMAGE_MODEL_CATALOG: if re.sub(r"[^a-z0-9]", "", m["id"]) == norm: return m["id"] return None # 探测自 /api/v1/curated-models(active=true 的 text/vision 模型为主) MODEL_CATALOG: list[dict[str, Any]] = [ {"id": "inclusionai/ling-2.6-flash", "name": "Ling 2.6 Flash", "owner": "InclusionAI", "upstream_id": "accounts/fireworks/models/ling-2.6-flash"}, {"id": "kimi/kimi-k3", "name": "Kimi K3", "owner": "Kimi", "upstream_id": "accounts/fireworks/models/kimi-k3"}, {"id": "kimi/kimi-k2.7-code", "name": "Kimi K2.7 Code", "owner": "Kimi", "upstream_id": "accounts/fireworks/models/kimi-k2.7-code"}, {"id": "kimi/kimi-k2.6", "name": "Kimi 2.6", "owner": "Kimi", "upstream_id": "accounts/fireworks/models/kimi-k2.6"}, {"id": "openai/gpt-5.6-sol", "name": "GPT 5.6 Sol", "owner": "OpenAI", "upstream_id": "accounts/fireworks/models/gpt-5.6-sol"}, {"id": "openai/gpt-5.6-terra", "name": "GPT 5.6 Terra", "owner": "OpenAI", "upstream_id": "accounts/fireworks/models/gpt-5.6-terra"}, {"id": "openai/gpt-5.6-luna", "name": "GPT 5.6 Luna", "owner": "OpenAI", "upstream_id": "accounts/fireworks/models/gpt-5.6-luna"}, {"id": "anthropic/claude-sonnet-5", "name": "Claude Sonnet 5", "owner": "Anthropic", "upstream_id": "accounts/fireworks/models/claude-sonnet-5"}, {"id": "anthropic/claude-opus-5", "name": "Claude Opus 5", "owner": "Anthropic", "upstream_id": "accounts/fireworks/models/claude-opus-5"}, {"id": "openai/gpt-5.5", "name": "GPT 5.5", "owner": "OpenAI", "upstream_id": "accounts/fireworks/models/gpt-5.5"}, {"id": "qwen/qwen-3.7-plus", "name": "Qwen 3.7 Plus", "owner": "Qwen", "upstream_id": "accounts/fireworks/models/qwen-3.7-plus"}, {"id": "qwen/qwen-3.6-plus", "name": "Qwen 3.6 Plus", "owner": "Qwen", "upstream_id": "accounts/fireworks/models/qwen-3.6-plus"}, {"id": "glm/glm-5.2", "name": "GLM 5.2", "owner": "Zhipu", "upstream_id": "accounts/fireworks/models/glm-5.2"}, {"id": "minimax/minimax-m3", "name": "MiniMax M3", "owner": "MiniMax", "upstream_id": "accounts/fireworks/models/minimax-m3"}, {"id": "minimax/minimax-m2.7", "name": "Minimax 2.7", "owner": "MiniMax", "upstream_id": "accounts/fireworks/models/minimax-m2.7"}, ] _BY_KEY: dict[str, dict[str, Any]] = {} for _m in MODEL_CATALOG: _BY_KEY[_m["id"]] = _m _BY_KEY[_m["name"].lower()] = _m class DefaultModelRegistry(ModelRegistry): def catalog(self) -> list[dict[str, Any]]: return list(MODEL_CATALOG) def normalize(self, model: str | None) -> str: """客户端传入的 model → catalog id;空或未知 → 默认。匹配:精确 id > 显示名(小写) > 模糊。""" if not model: return DEFAULT_MODEL if model in _BY_KEY: return _BY_KEY[model]["id"] low = model.lower() if low in _BY_KEY: return _BY_KEY[low]["id"] norm = re.sub(r"[^a-z0-9]", "", low) for m in MODEL_CATALOG: if (re.sub(r"[^a-z0-9]", "", m["id"]) == norm or re.sub(r"[^a-z0-9]", "", m["name"].lower()) == norm): return m["id"] return DEFAULT_MODEL def upstream_id_for(self, model_id: str) -> str | None: m = _BY_KEY.get(model_id) return m["upstream_id"] if m else None