diff --git a/admin/dashboard/blueprints/system.py b/admin/dashboard/blueprints/system.py index b93d685..4b10997 100644 --- a/admin/dashboard/blueprints/system.py +++ b/admin/dashboard/blueprints/system.py @@ -395,6 +395,46 @@ def _extract_ai_runtime_snapshot() -> dict: return snapshot +def _build_llm_runtime_analytics_payload() -> dict: + """构建 LLM 最近窗口分析载荷。 + + 为什么单独抽这个函数: + 1. 首页 AI 卡片只看摘要,而 `system_llm` 页面需要更细粒度的分组表; + 2. 两边都依赖同一套运行时快照,避免把 scene/backend/provider/model 聚合逻辑散在多个接口里; + 3. 第一阶段先做“最近窗口分析”,让管理员快速识别慢场景、失败模型和异常后端。 + """ + runtime_breakdown = UnifiedLLMClient.get_runtime_breakdown() or {} + overview_snapshot = _extract_ai_runtime_snapshot() + catalog_summary = _extract_llm_catalog_summary() + + return { + "overview": { + "window_size": _safe_int(runtime_breakdown.get("window_size")), + "total_calls": _safe_int(runtime_breakdown.get("total_calls")), + "success_calls": _safe_int(runtime_breakdown.get("success_calls")), + "failed_calls": _safe_int(runtime_breakdown.get("failed_calls")), + "success_rate": _safe_float(runtime_breakdown.get("success_rate")), + "avg_latency_ms": _safe_float(runtime_breakdown.get("avg_latency_ms")), + "last_error": str(runtime_breakdown.get("last_error") or "").strip(), + "status": str(overview_snapshot.get("status") or "warning").strip(), + "summary": str(overview_snapshot.get("summary") or "").strip(), + "last_call": dict(runtime_breakdown.get("last_call") or {}), + "provider_count": _safe_int(catalog_summary.get("provider_count")), + "scene_count": _safe_int(catalog_summary.get("scene_count")), + "target_count": _safe_int(catalog_summary.get("target_count")), + "default_scene": str(catalog_summary.get("default_scene") or "").strip(), + "default_backend": str(catalog_summary.get("default_backend") or "").strip(), + "has_routing": bool(catalog_summary.get("has_routing")), + }, + # 这里保留原始最近窗口明细,方便后续如果要做“最近 10 次调用”列表时直接复用。 + "recent_rows": runtime_breakdown.get("rows", []) or [], + "by_scene": runtime_breakdown.get("by_scene", []) or [], + "by_backend": runtime_breakdown.get("by_backend", []) or [], + "by_provider": runtime_breakdown.get("by_provider", []) or [], + "by_model": runtime_breakdown.get("by_model", []) or [], + } + + def _extract_scheduler_runtime_snapshot() -> dict: """聚合 async_job 运行态,生成首页任务调度摘要。 @@ -1115,6 +1155,26 @@ def get_system_llm_config(): return jsonify({"success": False, "message": str(e)}), 500 +@system_bp.route('/api/system/llm_runtime_analytics', methods=['GET']) +@login_required +def get_system_llm_runtime_analytics(): + """返回 LLM 最近窗口分析结果。 + + 这里不主动发起探活请求,也不做持久化成本结算,只消费统一客户端已经记录的最近窗口埋点: + 1. 避免后台刷新页面反过来给 AI 服务制造额外压力; + 2. 先把“按场景/后端/模型看成功率与耗时”做扎实; + 3. 为后续真正的 token 成本中心预留接口形态。 + """ + try: + return jsonify({ + "success": True, + "data": _build_llm_runtime_analytics_payload(), + }) + except Exception as e: + logger.error(f"读取 LLM 运行分析失败: {e}") + return jsonify({"success": False, "message": str(e)}), 500 + + @system_bp.route('/api/system/llm_config', methods=['POST']) @login_required def update_system_llm_config(): diff --git a/admin/dashboard/templates/system_llm.html b/admin/dashboard/templates/system_llm.html index 3604102..53dc82a 100644 --- a/admin/dashboard/templates/system_llm.html +++ b/admin/dashboard/templates/system_llm.html @@ -8,14 +8,181 @@
按 Provider 模板、Dify 应用、Scene 绑定三层维护,减少重复配置和切换成本。
+按 Provider 模板、Dify 应用、Scene 绑定三层维护,并结合最近窗口运行分析判断哪条 AI 路由更慢、更容易失败。
基于统一 LLM 客户端最近窗口埋点做被动观测,不额外发起探活请求。
+定位哪个业务场景最常调用、最容易失败。
+观察 backend 层是否存在集中失败或慢请求。
+区分 Dify 与 OpenAI Compatible 等不同接入形态的表现。
+帮助判断是否需要按场景切换模型或做降级策略。
+