From dbe2f9202dc657b2149015cbb92732a09d2bfe08 Mon Sep 17 00:00:00 2001 From: wsb1224 Date: Sat, 25 Jul 2026 13:45:32 +0800 Subject: [PATCH] feat: support direct LLM provider config with model sync - Fix _call_provider to support custom providers (OpenAI-compatible fallback) instead of raising ValueError for unknown providers - Add POST /admin/ppt/sync-models endpoint that fetches available models from the configured provider's API - Redesign PptSettingsAdmin UI: replace Dify model dropdown with per-provider 'sync models' button + searchable model selector - Remove dead Dify-specific code (fetchDifyModels, applyDifyModel) - Update deepseek default model from deepseek-chat to deepseek-v4-pro - API Key field now always shown (not conditional on non-dify) --- api/insurance/admin/ppt_admin_routes.py | 11 ++ api/insurance/admin/ppt_admin_service.py | 55 ++++++++ api/insurance/ppt/llm_client.py | 41 +++--- frontend/src/pages/admin/PptSettingsAdmin.vue | 131 +++++++++--------- frontend/src/utils/ppt-admin-api.ts | 3 + 5 files changed, 162 insertions(+), 79 deletions(-) diff --git a/api/insurance/admin/ppt_admin_routes.py b/api/insurance/admin/ppt_admin_routes.py index 57808dc..925dcc5 100644 --- a/api/insurance/admin/ppt_admin_routes.py +++ b/api/insurance/admin/ppt_admin_routes.py @@ -244,3 +244,14 @@ def get_settings(): def update_settings(): updated_by = getattr(request, "user_id", "") return jsonify(ppt_admin_service.update_settings(request.get_json(force=True), updated_by)) + + +@ppt_admin_bp.route("/sync-models", methods=["POST"]) +@permission_required("config_manage") +def sync_models(): + data = request.get_json(force=True) + return jsonify(ppt_admin_service.sync_models( + provider=data.get("provider", ""), + api_key=data.get("api_key", ""), + base_url=data.get("base_url", ""), + )) diff --git a/api/insurance/admin/ppt_admin_service.py b/api/insurance/admin/ppt_admin_service.py index 1e0603e..1ac52e4 100644 --- a/api/insurance/admin/ppt_admin_service.py +++ b/api/insurance/admin/ppt_admin_service.py @@ -566,3 +566,58 @@ class PptAdminService: db.session.add(SystemSetting(key=key, value=str(value), updated_by=updated_by)) db.session.commit() return {"code": 0, "data": None} + + def sync_models(self, provider: str, api_key: str, base_url: str = "") -> dict: + """从供应商 API 拉取可用模型列表。""" + if not api_key: + return {"code": 1001, "message": "请填写 API Key", "data": None} + + try: + models = self._fetch_provider_models(provider, api_key, base_url) + except Exception as e: + logger.warning(f"同步模型失败: {e}") + return {"code": 5001, "message": f"同步失败: {e}", "data": None} + + if not models: + return {"code": 0, "data": {"models": [], "message": "未获取到模型"}} + + return {"code": 0, "data": {"models": models}} + + def _fetch_provider_models(self, provider: str, api_key: str, base_url: str) -> list: + """根据供应商类型拉取模型列表。""" + import httpx + + if provider == "deepseek": + url = "https://api.deepseek.com/v1/models" + headers = {"Authorization": f"Bearer {api_key}"} + resp = httpx.get(url, headers=headers, timeout=10) + resp.raise_for_status() + return [m["id"] for m in resp.json().get("data", [])] + + elif provider == "gemini": + url = f"https://generativelanguage.googleapis.com/v1/models?key={api_key}" + resp = httpx.get(url, timeout=10) + resp.raise_for_status() + return [m["name"].split("/")[-1] for m in resp.json().get("models", [])] + + elif provider == "minimax": + url = "https://api.minimax.chat/v1/models" + headers = {"Authorization": f"Bearer {api_key}"} + resp = httpx.get(url, headers=headers, timeout=10) + resp.raise_for_status() + data = resp.json() + models = data.get("models") or data.get("data", []) + return [m.get("id") or m.get("model", "") for m in models if m] + + elif provider == "dify": + return [m["model"] for m in (self._fetch_dify_models() or [])] + + else: + # 自定义供应商:OpenAI 兼容 /models 端点 + if not base_url: + return [] + url = f"{base_url.rstrip('/')}/models" + headers = {"Authorization": f"Bearer {api_key}"} + resp = httpx.get(url, headers=headers, timeout=10) + resp.raise_for_status() + return [m["id"] for m in resp.json().get("data", [])] diff --git a/api/insurance/ppt/llm_client.py b/api/insurance/ppt/llm_client.py index 369e895..794e6ea 100644 --- a/api/insurance/ppt/llm_client.py +++ b/api/insurance/ppt/llm_client.py @@ -35,7 +35,7 @@ PROVIDERS = { "deepseek": LLMProviderConfig( name="deepseek", base_url="https://api.deepseek.com/v1", - model="deepseek-chat", + model="deepseek-v4-pro", max_retries=2, rate_limit=0, ), @@ -149,7 +149,16 @@ async def _call_provider( "generationConfig": {"temperature": 0.3, "maxOutputTokens": 4096}, } else: - raise ValueError(f"Unknown provider: {config.name}") + # 自定义供应商:OpenAI 兼容格式 + headers["Authorization"] = f"Bearer {api_key}" + base = config.base_url.rstrip("/") + url = f"{base}/chat/completions" + body = { + "model": config.model, + "messages": messages, + "temperature": 0.3, + "max_tokens": 4096, + } async with httpx.AsyncClient(timeout=timeout_s) as client: resp = await client.post(url, json=body, headers=headers) @@ -159,20 +168,7 @@ async def _call_provider( latency_ms = (time.monotonic() - start) * 1000 # 解析响应 - if config.name in ("deepseek", "minimax"): - content = "" - choices = data.get("choices", []) - if choices: - content = choices[0].get("message", {}).get("content", "") - usage = data.get("usage") - tokens = None - if usage: - tokens = { - "input": usage.get("prompt_tokens", 0), - "output": usage.get("completion_tokens", 0), - } - else: - # Gemini + if config.name == "gemini": content = "" candidates = data.get("candidates", []) if candidates: @@ -186,6 +182,19 @@ async def _call_provider( "input": usage_meta.get("promptTokenCount", 0), "output": usage_meta.get("candidatesTokenCount", 0), } + else: + # OpenAI 兼容格式(deepseek / minimax / 自定义供应商) + content = "" + choices = data.get("choices", []) + if choices: + content = choices[0].get("message", {}).get("content", "") + usage = data.get("usage") + tokens = None + if usage: + tokens = { + "input": usage.get("prompt_tokens", 0), + "output": usage.get("completion_tokens", 0), + } return LLMResponse( content=content, diff --git a/frontend/src/pages/admin/PptSettingsAdmin.vue b/frontend/src/pages/admin/PptSettingsAdmin.vue index e9ce080..5bfba76 100644 --- a/frontend/src/pages/admin/PptSettingsAdmin.vue +++ b/frontend/src/pages/admin/PptSettingsAdmin.vue @@ -20,34 +20,28 @@
- - 从 Dify 获取 - - 从 Dify 获取 - - + + + + + 同步模型
- - + + - - 通过 Dify 调用,无需配置 API Key - - 海报文案模型 @@ -63,34 +57,30 @@
- - 从 Dify 获取 - - 从 Dify 获取 - - + + + + + + 同步模型 +
- - + + - - 通过 Dify 调用,无需配置 API Key - - 海报图片模型 @@ -133,13 +123,14 @@