From 054685826a451f6b470744fa3e829535245e54f4 Mon Sep 17 00:00:00 2001 From: OoO Date: Tue, 5 May 2026 13:17:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(observability):=20=E9=87=8D=E5=A1=91=20AI?= =?UTF-8?q?=20=E8=A7=80=E6=B8=AC=E5=8F=B0=E6=88=B0=E6=83=85=E5=AE=A4=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/memory/README.md | 1 + .../code_modularization_inventory_20260430.md | 12 +- ...oject_phase38_56_observability_war_room.md | 32 + routes/admin_observability_routes.py | 31 +- routes/openclaw_bot_routes.py | 29 +- run_scheduler.py | 12 +- static/css/observability-system.css | 200 ++++ templates/admin/ai_calls_dashboard.html | 465 +++------- templates/admin/budget.html | 382 ++------ templates/admin/host_health.html | 680 +++----------- templates/admin/observability_overview.html | 878 ++++++++++++------ templates/admin/ppt_audit_history.html | 310 +------ templates/admin/promotion_review.html | 346 ++----- templates/admin/quality_trend.html | 310 +------ templates/admin/rag_queries.html | 289 +----- templates/components/_ewoooc_shell.html | 163 +++- templates/components/_navbar.html | 32 +- templates/ewoooc_base.html | 15 +- 18 files changed, 1579 insertions(+), 2608 deletions(-) create mode 100644 docs/memory/project_phase38_56_observability_war_room.md create mode 100644 static/css/observability-system.css diff --git a/docs/memory/README.md b/docs/memory/README.md index 1801268..5916793 100644 --- a/docs/memory/README.md +++ b/docs/memory/README.md @@ -17,6 +17,7 @@ | `credentials_passbook.md` | 伺服器、帳密、埠位對照 | 需要維運、部署、憑證核對時 | | `feedback_db_metadata_import.md` | SQLAlchemy metadata / `create_all()` 漏表鐵律 | 新增 model、修 schema、排查 fresh env 漏表時 | | `db_connection_pool_singleton_20260430.md` | PostgreSQL `too many clients` 連線池放大事故與 DatabaseManager singleton 修正 | 排查 DB 連線數暴增、route 內反覆初始化 DatabaseManager、SQLAlchemy engine/pool 行為時 | +| `project_phase38_56_observability_war_room.md` | Phase 38→56 AI 觀測台戰役落地盤點、已知缺口與後續拆分方向 | 接續觀測台頁面、Telegram 指令、scheduler probe、AutoHeal/CodeReview L2 入口、Chart.js polish 或 deploy_doctor 時 | | `project_phase3f_cleanup_roadmap.md` | ADR-017 執行矩陣與階段紅線 | 正在做 3f 模組化收尾時 | | `code_modularization_inventory_20260430.md` | Python 大檔盤點、分層規範與拆分工作項目 | 新增功能、拆大檔、審查是否違反模組化治理時 | | `schema_inventory_baseline.md` | DB 表分類與 drift 基線 | 要收斂 migration / ORM / raw SQL 真相時 | diff --git a/docs/memory/code_modularization_inventory_20260430.md b/docs/memory/code_modularization_inventory_20260430.md index 5dd9461..0e1ab67 100644 --- a/docs/memory/code_modularization_inventory_20260430.md +++ b/docs/memory/code_modularization_inventory_20260430.md @@ -7,7 +7,8 @@ - Python 總量:約 66,997 行。 - 最大壓力區:`routes/` 約 21,095 行、`services/` 約 26,023 行。 - `app.py` 目前約 1,209 行,功能定位應固定為 bootstrap / Blueprint registration / startup guard,不再承接新 route。 -- 目前工作樹仍有 16 個 Python 檔案超過 800 行;這些不是禁止修 bug,而是禁止繼續塞新功能。 +- 目前工作樹仍有 18 個 Python 檔案超過 800 行;這些不是禁止修 bug,而是禁止繼續塞新功能。 +- 2026-05-05 追記:Phase 38→56 觀測台戰役讓 `routes/admin_observability_routes.py` 與 `run_scheduler.py` 進入大檔治理清單;後續觀測台功能應先抽 query/action service,不再把新 SQL 與 L2 mutation 直接塞回 route。 ## 超過 800 行檔案清單 @@ -16,6 +17,7 @@ | 5240 | `routes/openclaw_bot_routes.py` | P0 巨型 Blueprint | route / bot command service / report service / scheduler hook | | 2707 | `scheduler.py` | P0 排程總管 | task registry / crawler jobs / report jobs / notification jobs | | 2653 | `routes/sales_routes.py` | P0 巨型 Blueprint | page routes / API routes / chart query service / calendar service | +| 2550 | `routes/admin_observability_routes.py` | P0 觀測台巨型 Blueprint | `services/observability_query_service.py` / `services/observability_action_service.py` / route glue | | 1743 | `routes/ai_routes.py` | P1 AI Blueprint | route glue / AI orchestration service / prompt builders | | 1485 | `routes/vendor_routes.py` | P1 Vendor Blueprint | route glue / stockout mutation/email;V2 page query、stockout list/batches API query、vendor list/detail query 已抽到 `services/vendor_stockout_query_service.py` | | 1345 | `services/ppt_generator.py` | P1 報表生成 service | deck orchestration / slide builders / chart builders | @@ -27,6 +29,7 @@ | 986 | `services/telegram_bot_service.py` | P2 Telegram service | command handlers / message formatters / bot client | | 966 | `services/trend_crawler.py` | P2 crawler service | source adapters / parser / persistence | | 946 | `services/elephant_alpha_autonomous_engine.py` | P2 ElephantAlpha engine | HITL / executor / planning policy | +| 868 | `run_scheduler.py` | P2 scheduler entrypoint | observability jobs / token report jobs / task registration 分離 | | 829 | `routes/export_routes.py` | P2 Export flow | export command/router glue / file path / download orchestration | | 818 | `services/import_service.py` | P2 import service | validators / import writers / report builders | | 805 | `routes/bot_api_routes.py` | P2 Bot API Blueprint | route glue / bot action service | @@ -37,9 +40,10 @@ 1. P0:持續拆 `routes/openclaw_bot_routes.py`;Telegram API helper 已搬到 `services/openclaw_bot/telegram_api.py`,Inline Keyboard builders 已搬到 `services/openclaw_bot/menu_keyboards.py`,下一步拆 report formatting 或 command dispatcher。 2. P0:拆 `routes/sales_routes.py`,先把 chart/query/calendar 計算搬到 `services/sales/`。 3. P0:拆 `scheduler.py`,建立 `jobs/` 或 `services/scheduler/` task registry。 -4. P1:把 `routes/ai_routes.py` 與 `routes/vendor_routes.py` 的資料處理移出 route;Vendor V2 page query、stockout API list/batches、vendor list/detail 已完成,下一步可抽 email grouping 或 vendor mutation service。 -5. P1:把 PPT / NemoTron / OpenClaw 大 service 拆成 client、parser、composer、policy。 -6. P2:對 800-1100 行檔案採「碰到就順手抽」策略,但不可讓淨行數繼續增加。 +4. P0:拆 `routes/admin_observability_routes.py`,先搬純查詢函式到 `services/observability_query_service.py`,再搬 AutoHeal / Code Review / AiderHeal / throttle mutation 到 `services/observability_action_service.py`。 +5. P1:把 `routes/ai_routes.py` 與 `routes/vendor_routes.py` 的資料處理移出 route;Vendor V2 page query、stockout API list/batches、vendor list/detail 已完成,下一步可抽 email grouping 或 vendor mutation service。 +6. P1:把 PPT / NemoTron / OpenClaw 大 service 拆成 client、parser、composer、policy。 +7. P2:對 800-1100 行檔案採「碰到就順手抽」策略,但不可讓淨行數繼續增加。 ## 守門 diff --git a/docs/memory/project_phase38_56_observability_war_room.md b/docs/memory/project_phase38_56_observability_war_room.md new file mode 100644 index 0000000..11bc29d --- /dev/null +++ b/docs/memory/project_phase38_56_observability_war_room.md @@ -0,0 +1,32 @@ +# Phase 38→56 AI 觀測台戰役盤點 + +> 日期:2026-05-05 +> 用途:接續觀測台、Telegram、scheduler probe、L2 自動化與 deploy_doctor 時的低成本入口。 + +## 已確認落地 + +- Commit 19f1340→df2311d 覆蓋 Phase 38→55;HEAD 另有 Phase 56 deploy_doctor 擴充。 +- Web 觀測台已形成 10 頁:overview、agent_orchestration、business_intel、host_health、ai_calls、budget、promotion_review、rag_queries、quality_trend、ppt_audit_history。 +- `templates/components/_ewoooc_shell.html` 已加入 AI 觀測 sidebar group;`templates/ewoooc_base.html` 已加入 topbar health indicator。 +- Telegram 已加入 `obs_*` 指令與 `cmd:obs_heal:*`、`cmd:obs_force_throttle`、`cmd:obs_trigger_review` inline action。 +- `run_scheduler.py` 已加入 host probe、cleanup、AI error spike、observability daily summary。 +- `migrations/029_create_host_health_probes.sql` 與 `migrations/030_create_ppt_audit_results.sql` 已補 host health / PPT audit 持久化。 + +## 2026-05-05 修補 + +- AutoHeal Web / Telegram 入口不再只看 `_is_unhealthy()` 30 秒記憶體 TTL;也接受 `host_health_probes` 30 分鐘內最新探針為 unhealthy。 +- AI 呼叫錯誤突增告警補上 1 小時 in-process dedup,避免每 30 分鐘重複推送。 +- 補回 memory 索引與模組化治理清單,避免 Phase 38→56 戰役只存在於聊天戰報。 + +## 已知治理債 + +- `routes/admin_observability_routes.py` 約 2550 行,已是 P0 巨型 Blueprint。後續不可再直接塞新頁面或 SQL,應抽 `observability_query_service` 與 `observability_action_service`。 +- `routes/openclaw_bot_routes.py` 約 9104 行,Telegram 指令應繼續拆到 `services/openclaw_bot/`。 +- 觀測台 template 仍大量使用 Bootstrap `card` / `btn-*`,尚未完全改成 `--momo-*` design token;這是 cosmetic,不是 P0。 +- 現有 smoke test 多數 mock DB/Ollama/MCP,只能證明 route 不 500;不能證明 SQL schema、真實資料或 L2 side effect 正確。 + +## 下一步建議 + +- 優先抽 `routes/admin_observability_routes.py` 的查詢層,保留 route 只做 request parsing 與 render。 +- 補一個整合級 smoke:用測試 DB 建 `host_health_probes`,驗證 Web/TG AutoHeal 在 DB 最新 unhealthy 時不會被 `_is_unhealthy()` TTL 擋下。 +- 若要做視覺 polish,再集中處理 Bootstrap → MOMO V2 token,不要混進功能 patch。 diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index c44e733..ede65d5 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -1878,6 +1878,35 @@ def _build_indicator_tooltip(host_unhealthy, ep_pending, error_rate, budget_aler return "AI 觀測台 — " + " / ".join(parts) +def _latest_host_probe_unhealthy(host_label: str, window_minutes: int = 30) -> bool: + """查 DB 最新 host_health_probe,作為 AutoHeal 按鈕的真實狀態來源。 + + `_is_unhealthy()` 只代表 Ollama client 在 30 秒 TTL 內的記憶體標記; + scheduler / 頁面 probe 寫入的是 `host_health_probes`。L2 AutoHeal 入口 + 必須接受 DB 最新探針異常,避免 Telegram 或 Web 顯示主機已掛、按鈕卻拒絕執行。 + """ + if not host_label: + return False + session = get_session() + try: + row = session.execute( + sa_text(""" + SELECT healthy + FROM host_health_probes + WHERE host_label = :label + AND probed_at >= NOW() - (:minutes || ' minutes')::interval + ORDER BY probed_at DESC + LIMIT 1 + """), + {'label': host_label, 'minutes': int(window_minutes)}, + ).fetchone() + return bool(row is not None and row[0] is False) + except Exception: + return False + finally: + session.close() + + @admin_observability_bp.route('/playbooks/toggle/', methods=['POST']) @login_required def playbook_toggle(playbook_id: int): @@ -1940,7 +1969,7 @@ def host_health_trigger_autoheal(): if not host_url: return jsonify({'ok': False, 'error': f'未知 host_label: {host_label}'}), 400 - if not _is_unhealthy(host_url): + if not (_is_unhealthy(host_url) or _latest_host_probe_unhealthy(host_label)): return jsonify({ 'ok': False, 'error': f'{host_label} 目前未標記異常,無需 AutoHeal', diff --git a/routes/openclaw_bot_routes.py b/routes/openclaw_bot_routes.py index adeb46e..acda14e 100644 --- a/routes/openclaw_bot_routes.py +++ b/routes/openclaw_bot_routes.py @@ -8154,6 +8154,7 @@ def handle_cmd(cmd, arg, chat_id, reply_to): lines = ["🏥 *主機健康監控*", ""] lines.append("*三主機即時狀態:*") + host_states = {} for label, host in [ ('GCP-A (Primary)', OLLAMA_HOST_PRIMARY), ('GCP-B (Secondary)', OLLAMA_HOST_SECONDARY), @@ -8165,6 +8166,8 @@ def handle_cmd(cmd, arg, chat_id, reply_to): healthy = (r.status_code == 200) except Exception: pass + short_label = 'GCP-A' if label.startswith('GCP-A') else 'GCP-B' if label.startswith('GCP-B') else '111' + host_states[short_label] = healthy emoji = "✅" if healthy else "❌" mark = " ⚠️標記異常" if _is_unhealthy(host) else "" lines.append(f"{emoji} *{label}*:`{host}`{mark}") @@ -8204,7 +8207,7 @@ def handle_cmd(cmd, arg, chat_id, reply_to): ) heal_buttons = [] for label, host in [('GCP-A', _P), ('GCP-B', _S), ('111', _F)]: - if _is_unhealthy(host): + if _is_unhealthy(host) or host_states.get(label) is False: heal_buttons.append((f'🩹 修 {label}', f'cmd:obs_heal:{label}')) if heal_buttons: # 兩兩成行 @@ -8237,7 +8240,29 @@ def handle_cmd(cmd, arg, chat_id, reply_to): OLLAMA_HOST_PRIMARY as _P2, OLLAMA_HOST_SECONDARY as _S2, OLLAMA_HOST_FALLBACK as _F2, ) host_url = {'Primary (GCP)': _P2, 'Secondary (GCP)': _S2, 'Fallback (111)': _F2}.get(host_label) - if not _iu(host_url): + + def _latest_probe_unhealthy(label: str) -> bool: + """用 DB 最新探針補足 `_is_unhealthy()` 30 秒記憶體 TTL 的盲點。""" + try: + from database.manager import DatabaseManager + from sqlalchemy import text as _sa + _s = DatabaseManager().get_session() + try: + row = _s.execute(_sa(""" + SELECT healthy + FROM host_health_probes + WHERE host_label = :label + AND probed_at >= NOW() - INTERVAL '30 minutes' + ORDER BY probed_at DESC + LIMIT 1 + """), {'label': label}).fetchone() + return bool(row is not None and row[0] is False) + finally: + _s.close() + except Exception: + return False + + if not (_iu(host_url) or _latest_probe_unhealthy(host_label)): send_message(chat_id, f"⚠️ {host_label} 目前未標記異常,無需 AutoHeal", reply_to, parse_mode=None) return result = auto_heal_service.handle_exception( diff --git a/run_scheduler.py b/run_scheduler.py index 44956fe..2492a77 100644 --- a/run_scheduler.py +++ b/run_scheduler.py @@ -47,6 +47,8 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +_AI_CALLS_ERROR_SPIKE_LAST_PUSH_TS = 0.0 + def _register_schedules(): schedule.every(30).minutes.do(run_auto_import_task) @@ -501,7 +503,14 @@ def run_ai_calls_error_spike_check(): finally: session.close() - # 推 Telegram 告警 + inline Code Review 按鈕 + # 推 Telegram 告警 + inline Code Review 按鈕。 + # 1 小時內同類 spike 只推一次,避免每 30 分鐘洗版。 + global _AI_CALLS_ERROR_SPIKE_LAST_PUSH_TS + now_ts = time.time() + if now_ts - _AI_CALLS_ERROR_SPIKE_LAST_PUSH_TS < 3600: + logger.info("[AICallsErrorSpike] skip duplicate alert within 1h window") + return + from services.telegram_templates import send_telegram_with_result lines = [ "🚨 AI 呼叫錯誤率異常", "", @@ -521,6 +530,7 @@ def run_ai_calls_error_spike_check(): ], } send_telegram_with_result('\n'.join(lines), reply_markup=reply_markup, parse_mode='HTML') + _AI_CALLS_ERROR_SPIKE_LAST_PUSH_TS = now_ts logger.warning( f"[AICallsErrorSpike] alert pushed: total={total} errors={errors} " f"rate={error_rate*100:.1f}%" diff --git a/static/css/observability-system.css b/static/css/observability-system.css new file mode 100644 index 0000000..dce11ea --- /dev/null +++ b/static/css/observability-system.css @@ -0,0 +1,200 @@ +/* + * AI 觀測台共用視覺系統 + * 只在 ewoooc_base.html 的 .momo-observability-mode 啟用,避免污染一般後台頁。 + */ +.momo-observability-mode { + --obs-ink: var(--momo-ink, #302720); + --obs-muted: var(--momo-muted, #8b8077); + --obs-paper: var(--momo-paper, #fff8ef); + --obs-accent: var(--momo-accent, #c96442); + --obs-accent-soft: rgba(201, 100, 66, 0.12); + --obs-line: rgba(86, 64, 48, 0.14); + --obs-card: rgba(255, 252, 246, 0.92); + --obs-green: #4f8a5b; + --obs-amber: #b8792f; + --obs-red: #b94b45; + --obs-blue: #4f6f8f; +} + +.momo-observability-mode .momo-content { + background: + radial-gradient(circle at 8% 4%, rgba(201, 100, 66, 0.12), transparent 24rem), + radial-gradient(circle at 100% 20%, rgba(79, 111, 143, 0.11), transparent 26rem), + linear-gradient(180deg, rgba(255, 248, 239, 0.65), rgba(255, 253, 248, 0.96)); +} + +.momo-observability-mode .container-fluid > h2:first-child { + position: relative; + margin-bottom: 1.1rem !important; + padding: 1.2rem 1.25rem; + border: 1px solid var(--obs-line); + border-radius: 24px; + background: + radial-gradient(circle at 16% 10%, rgba(201, 100, 66, 0.16), transparent 20rem), + linear-gradient(135deg, rgba(255, 248, 239, 0.96), rgba(255, 255, 255, 0.76)); + box-shadow: 0 18px 42px rgba(70, 46, 28, 0.08); + color: var(--obs-ink); + font-family: Georgia, 'Times New Roman', 'Noto Sans TC', serif; + font-size: clamp(1.65rem, 3vw, 2.65rem); + letter-spacing: -0.045em; +} + +.momo-observability-mode .container-fluid > h2:first-child small { + display: block; + margin-top: 0.3rem; + color: var(--obs-muted) !important; + font-family: 'Noto Sans TC', sans-serif; + font-size: 0.88rem; + font-weight: 500; + letter-spacing: 0; +} + +.momo-observability-mode .card { + border: 1px solid var(--obs-line); + border-radius: 22px; + background: var(--obs-card); + box-shadow: 0 14px 34px rgba(70, 46, 28, 0.07); + overflow: hidden; +} + +.momo-observability-mode .card-header { + border-bottom: 1px solid var(--obs-line); + background: linear-gradient(90deg, rgba(255, 248, 239, 0.92), rgba(255, 255, 255, 0.72)) !important; + color: var(--obs-ink); + font-weight: 800; +} + +.momo-observability-mode .card-body { + color: var(--obs-ink); +} + +.momo-observability-mode .card-footer { + border-top: 1px solid var(--obs-line); + background: rgba(255, 248, 239, 0.56); +} + +.momo-observability-mode .text-muted { + color: var(--obs-muted) !important; +} + +.momo-observability-mode h3, +.momo-observability-mode h4, +.momo-observability-mode h5 { + letter-spacing: -0.035em; +} + +.momo-observability-mode code, +.momo-observability-mode .momo-mono, +.momo-observability-mode table td, +.momo-observability-mode table th { + font-variant-numeric: tabular-nums; +} + +.momo-observability-mode .table { + --bs-table-bg: transparent; + --bs-table-striped-bg: rgba(255, 248, 239, 0.46); + color: var(--obs-ink); + margin-bottom: 0; +} + +.momo-observability-mode .table thead, +.momo-observability-mode .table-light { + --bs-table-bg: rgba(255, 248, 239, 0.9); + color: var(--obs-muted); + font-size: 0.74rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.momo-observability-mode .table td, +.momo-observability-mode .table th { + border-color: rgba(86, 64, 48, 0.1); + vertical-align: middle; +} + +.momo-observability-mode .btn { + border-radius: 999px; + font-weight: 760; + letter-spacing: -0.01em; +} + +.momo-observability-mode .btn-primary, +.momo-observability-mode .btn-warning, +.momo-observability-mode .btn-success { + border-color: var(--obs-accent) !important; + background: var(--obs-accent) !important; + color: #fff8ef !important; +} + +.momo-observability-mode .btn-outline-primary, +.momo-observability-mode .btn-outline-secondary, +.momo-observability-mode .btn-outline-info, +.momo-observability-mode .btn-outline-warning, +.momo-observability-mode .btn-outline-danger, +.momo-observability-mode .btn-outline-success { + border-color: rgba(201, 100, 66, 0.34) !important; + color: var(--obs-accent) !important; + background: rgba(255, 255, 255, 0.5); +} + +.momo-observability-mode .btn:hover { + transform: translateY(-1px); + box-shadow: 0 10px 22px rgba(201, 100, 66, 0.14); +} + +.momo-observability-mode .badge.bg-secondary, +.momo-observability-mode .badge.bg-primary, +.momo-observability-mode .badge.bg-info { + background: rgba(79, 111, 143, 0.14) !important; + color: var(--obs-blue) !important; +} + +.momo-observability-mode .badge.bg-warning { + background: rgba(184, 121, 47, 0.14) !important; + color: var(--obs-amber) !important; +} + +.momo-observability-mode .badge.bg-danger { + background: rgba(185, 75, 69, 0.14) !important; + color: var(--obs-red) !important; +} + +.momo-observability-mode .badge.bg-success { + background: rgba(79, 138, 91, 0.14) !important; + color: var(--obs-green) !important; +} + +.momo-observability-mode .alert { + border-radius: 18px; + border-color: rgba(184, 121, 47, 0.24); + background: rgba(184, 121, 47, 0.08); + color: var(--obs-amber); +} + +.momo-observability-mode canvas { + max-width: 100%; +} + +.momo-observability-mode .modal-content { + border: 1px solid var(--obs-line); + border-radius: 24px; + background: var(--obs-paper); + box-shadow: 0 24px 70px rgba(70, 46, 28, 0.18); +} + +.momo-observability-mode .modal-header, +.momo-observability-mode .modal-footer { + border-color: var(--obs-line); +} + +@media (max-width: 768px) { + .momo-observability-mode .container-fluid { + padding-left: 0.7rem; + padding-right: 0.7rem; + } + + .momo-observability-mode .table-responsive, + .momo-observability-mode .card-body.p-0 { + overflow-x: auto; + } +} diff --git a/templates/admin/ai_calls_dashboard.html b/templates/admin/ai_calls_dashboard.html index 9fed29c..9a59319 100644 --- a/templates/admin/ai_calls_dashboard.html +++ b/templates/admin/ai_calls_dashboard.html @@ -1,375 +1,148 @@ {% extends "ewoooc_base.html" %} -{% block title %}AI 呼叫總覽{% endblock %} +{% block title %}AI 流量控制塔{% endblock %} {% block ewooo_content %} + + +{% set total = summary.total_calls or 0 %} +{% set errors = summary.error_calls or 0 %} +{% set error_rate = (errors / total * 100) if total > 0 else 0 %} +{% set rag_rate = ((summary.rag_hits or 0) / total * 100) if total > 0 else 0 %} +{% set avg_tokens = ((summary.total_tokens or 0) // (total or 1)) %} +
-

AI 呼叫總覽 - 過去 {{ hours }} 小時 -

+
+
AI Traffic Control · {{ hours }}h Window
+

AI 流量控制塔

+

這裡不是流水帳,而是 AI 中樞的飛航管制台:看呼叫量、Token、成本、錯誤率、RAG 命中與 MCP 編排,並在異常時一鍵派出 Code Review Pipeline。

+
+ +
+ + + + +
+
+
- {% if error %} -
{{ error }}
- {% endif %} + {% if error %}
{{ error }}
{% endif %} - -
- - - 看到錯誤率飆高?一鍵觸發 Hermes→OpenClaw→EA→NemoTron 5 步審查最新 commit, - 在背景執行,完成後 Telegram 通知。 - -
+
+
Calls
{{ "{:,}".format(total) }}{% if hourly_trend %}{% endif %}
+
Tokens
{{ "{:,}".format(summary.total_tokens or 0) }}
{{ avg_tokens }} tk/call
+
Cost
${{ "%.2f"|format(summary.total_cost or 0) }}{% if hourly_trend %}{% endif %}
+
Latency
{{ summary.avg_duration or 0 }}ms
{{ summary.cache_hits or 0 }} cache hits
+
RAG Hit
{{ "%.1f"|format(rag_rate) }}%
{{ summary.rag_hits or 0 }} hits
+
Errors
{{ errors }}{% if hourly_trend %}{% endif %}
+
- -
-
- -
-
- -
-
- -
-
- -
-
+
+
+ {% if hourly_trend %} +
+
Hourly Traffic

每小時呼叫趨勢

+
+
+ {% endif %} - -
-
-
- 呼叫次數 -

{{ "{:,}".format(summary.total_calls or 0) }}

- {% if hourly_trend %}{% endif %} -
+ {% if caller_richness %} +
+
Caller Orchestration

呼叫端 × RAG × MCP 編排矩陣

+
{% for c in caller_richness %}{% endfor %}
呼叫端總呼叫RAG 命中MCP 編排RAG 反饋筆數
{{ c.caller }}{{ "{:,}".format(c.total_calls) }}{{ "%.1f"|format(c.rag_hit_rate) }}% ({{ c.rag_hits }}){{ "%.1f"|format(c.mcp_rate) }}%{% if c.feedback_count > 0 %}{{ "%.2f"|format(c.avg_rag_feedback) }}/5{% else %}{% endif %}{{ c.feedback_count }}
+
+ {% endif %}
-
-
- Token 用量 -

{{ "{:,}".format(summary.total_tokens or 0) }}

- {{ (summary.total_tokens or 0) // (summary.total_calls or 1) }} tk/call 平均 -
-
-
-
- 成本 (USD) -

${{ "%.2f"|format(summary.total_cost or 0) }}

- {% if hourly_trend %}{% endif %} -
-
-
-
- 平均耗時 -

{{ summary.avg_duration or 0 }} ms

- {{ summary.cache_hits or 0 }} 次 cache 命中 -
-
-
-
- RAG 命中 -

{{ summary.rag_hits or 0 }}

- - {% if (summary.total_calls or 0) > 0 %} - {{ "%.1f"|format((summary.rag_hits or 0) / summary.total_calls * 100) }}% - {% else %}—{% endif %} - -
-
-
-
- 錯誤次數 -

{{ summary.error_calls or 0 }}

- {% if hourly_trend %}{% endif %} -
-
-
- - {% if caller_richness %} -
-
呼叫端 × RAG × MCP 編排矩陣 - 資料來源:ai_calls × mcp_calls × rag_query_log({{ hours }}h 內呼叫 ≥ 5 次的 caller) -
-
- - - - - - - - - - - - - {% for c in caller_richness %} - - - - - - - - - {% endfor %} - -
呼叫端總呼叫RAG 命中率MCP 編排率RAG 反饋分數反饋筆數
{{ c.caller }}{{ "{:,}".format(c.total_calls) }} - - {{ "%.1f"|format(c.rag_hit_rate) }}% - - ({{ c.rag_hits }}) - - - {{ "%.1f"|format(c.mcp_rate) }}% - - ({{ c.mcp_orchestrated }}) - - {% if c.feedback_count > 0 %} - - {{ "%.2f"|format(c.avg_rag_feedback) }}/5 - - {% else %} - - {% endif %} - {{ c.feedback_count }}
-
- -
- {% endif %} + +
- {% if by_model %} -
-
依模型細分 - 資料來源:ai_calls.model(細到模型版本)— Top 15 -
-
- - - - - - - - - - - {% for m in by_model %} - - - - - - - - - - {% endfor %} - -
模型供應商呼叫Token成本 USD平均耗時錯誤
{{ m.model[:35] }}{{ m.provider }}{{ "{:,}".format(m.calls) }}{{ "{:,}".format(m.tokens) }}${{ "%.4f"|format(m.cost) }}{{ m.avg_ms }} ms - {% if m.errors > 0 %}{{ m.errors }} - {% else %}0{% endif %} -
-
-
+
+
Model Economics

依模型細分

+
{% for m in by_model %}{% endfor %}
模型供應商呼叫Token成本耗時錯誤
{{ m.model[:35] }}{{ m.provider }}{{ "{:,}".format(m.calls) }}{{ "{:,}".format(m.tokens) }}${{ "%.4f"|format(m.cost) }}{{ m.avg_ms }} ms{% if m.errors > 0 %}{{ m.errors }}{% else %}0{% endif %}
+
{% endif %} - - {% if recent_contexts %} -
-
Agent 對話上下文(最近 10 筆) - 資料來源:agent_context(Hermes/OpenClaw/NemoTron 工作 session) -
-
- - - - - - - - - {% for c in recent_contexts %} - - - - - - - - {% endfor %} - -
時間AgentKeyTTL min預覽
{{ c.created_at }}{{ c.agent_name }}{{ c.context_key }}{{ c.ttl_minutes }}{{ c.preview }}{% if c.preview|length >= 120 %}…{% endif %}
-
-
- {% endif %} +
+
Recent Calls

最近呼叫 100 筆

+
{% for r in recent %}{% endfor %}
編號時間呼叫端供應商模型輸入輸出耗時狀態成本標記
{{ r.id }}{{ r.called_at }}{{ r.caller }}{{ r.provider }}{{ r.model[:25] }}{{ r.in_tokens }}{{ r.out_tokens }}{{ r.duration_ms }}{{ r.status }}${{ "%.4f"|format(r.cost) }}{% if r.cache_hit %}快取{% endif %}{% if r.rag_hit %}RAG{% endif %}
+
- -
-
依供應商分組
-
- - - - - - {% for row in by_provider %} - - - - - - - {% endfor %} - -
供應商呼叫數Token 用量成本 (USD)
{{ row.provider }}{{ "{:,}".format(row.calls) }}{{ "{:,}".format(row.tokens) }}${{ "%.2f"|format(row.cost) }}
-
-
- - -
-
最近呼叫(最新 100 筆)
-
- - - - - - - - - - {% for r in recent %} - - - - - - - - - - - - - - {% endfor %} - -
編號時間呼叫端供應商模型輸入輸出耗時 ms狀態成本 $標記
{{ r.id }}{{ r.called_at }}{{ r.caller }}{{ r.provider }}{{ r.model[:25] }}{{ r.in_tokens }}{{ r.out_tokens }}{{ r.duration_ms }}{{ r.status }}${{ "%.4f"|format(r.cost) }} - {% if r.cache_hit %}快取{% endif %} - {% if r.rag_hit %}RAG{% endif %} -
-
-
- -

- Operation Ollama-First v5.0 / Phase 40 — AI 呼叫總覽(含 RAG/MCP 編排矩陣) -

+

Operation Ollama-First v5.0 — AI 流量控制塔

{% endblock %} diff --git a/templates/admin/budget.html b/templates/admin/budget.html index 6262fb4..2569fef 100644 --- a/templates/admin/budget.html +++ b/templates/admin/budget.html @@ -1,329 +1,95 @@ {% extends "ewoooc_base.html" %} -{% block title %}預算控管{% endblock %} +{% block title %}AI 成本治理艙{% endblock %} {% block ewooo_content %} + + +{% set total_budget = namespace(value=0) %}{% set total_spent = namespace(value=0) %}{% set warn_count = namespace(value=0) %}{% set throttled_count = namespace(value=0) %} +{% for r in rows %}{% set total_budget.value = total_budget.value + (r.budget_usd or 0) %}{% set total_spent.value = total_spent.value + (r.spent or 0) %}{% if r.ratio >= 0.8 %}{% set warn_count.value = warn_count.value + 1 %}{% endif %}{% if r.throttled %}{% set throttled_count.value = throttled_count.value + 1 %}{% endif %}{% endfor %} +{% set total_ratio = (total_spent.value / total_budget.value * 100) if total_budget.value > 0 else 0 %} +
-

預算控管 - ai_call_budgets × 當月實際支出即時對比 -

+
+
AI Cost Governance · Budget / Throttle / RAG Strategy
+

AI 成本治理艙

+

這頁回答一個問題:AI 中樞花錢是否仍在治理邊界內?預算、實際支出、月底推估、節流狀態與 RAG 策略建議集中在同一個 cockpit。

+
超過 110% 時不用等 cron,直接 evaluate provider throttle。
+
+
Month Spend
${{ "%.2f"|format(total_spent.value) }}
預算 ${{ "%.2f"|format(total_budget.value) }}
+
Budget Ratio
{{ "%.0f"|format(total_ratio) }}%
全 provider 加總
+
Warnings
{{ warn_count.value }}
使用率 ≥ 80%
+
Throttled
{{ throttled_count.value }}
已啟動成本節流
+
+
- {% if error %}
{{ error }}
{% endif %} + {% if error %}
{{ error }}
{% endif %} -

- 依 ADR-028 預算 + Phase 20 成本節流:每小時 cron 檢查當月支出, - 線性外推月底成本超 110% → 自動節流(claude→gemini fallback)。 - 手動編輯預算後立即生效(不需重啟)。 -

+
+
+
+
Budget Lines

預算線與節流狀態

+
{% for r in rows %}= 0.8 %}class="table-warning"{% endif %}>{% else %}{% endfor %}
週期供應商已花費預算閾值使用率狀態動作
{{ r.period }}{{ r.provider }}${{ "%.2f"|format(r.spent) }}{{ "%.0f"|format(r.ratio * 100) }}%{% if r.throttled %}已節流{% elif r.ratio >= 0.8 %}接近上限{% else %}正常{% endif %}
無預算資料(需先跑 migrations/025)
+
- -
- - 用途:發現某 provider 飆超 110% 時立即 evaluate,毋需等下次每小時 cron。 -
+ {% if cost_trend_30d %} +
30d Cost Trend

每日成本堆疊趨勢

+ {% endif %} +
+ + +
- {% if budget_strategies %} -
-
- RAG 自動策略建議 - — 從知識庫 ai_insights 召回過去類似超支情境的應對策略 -
-
-
    - {% for s in budget_strategies %} -
  • - {{ s.insight_type }} - 相似度 {{ "%.2f"|format(s.similarity) }} - {{ s.content }}{% if s.content|length >= 240 %}…{% endif %} -
  • - {% endfor %} -
-
-
+
RAG Strategy

RAG 自動策略建議

{% for s in budget_strategies %}
{{ s.insight_type }}相似度 {{ "%.2f"|format(s.similarity) }}{{ s.content }}{% if s.content|length >= 240 %}…{% endif %}
{% endfor %}
{% endif %} - - - - - - - - - - - - {% for r in rows %} - = 0.8 %}class="table-warning"{% endif %}> - - - - - - - - - - - {% else %} - - {% endfor %} - -
週期供應商已花費 (USD)預算 (USD)警示閾值 %使用率狀態更新時間動作
{{ r.period }}{{ r.provider }}${{ "%.2f"|format(r.spent) }} - - - - - - {{ "%.0f"|format(r.ratio * 100) }}% - - - {% if r.throttled %} - 已節流 - {% elif r.ratio >= 0.8 %} - 接近上限 - {% else %} - 正常 - {% endif %} - {{ r.updated_at }} - -
無預算資料(需先跑 migrations/025)
- - - {% if provider_cost_month %} -
-
當月各供應商成本分布 - 資料來源:ai_calls.cost_usd GROUP BY provider · 月初至今 -
-
-
-
- -
-
- - - - - - {% set total_pc = (provider_cost_month | sum(attribute='cost')) or 0.0001 %} - {% for p in provider_cost_month %} - - - - - - {% endfor %} - - - - - - -
供應商成本佔比
{{ p.provider }}${{ "%.4f"|format(p.cost) }}{{ "%.1f"|format(p.cost / total_pc * 100) }}%
總計${{ "%.2f"|format(total_pc) }}100%
-
-
-
-
- {% endif %} - - - {% if top_cost_callers %} -
-
當月 Top 5 燒錢呼叫端 - 資料來源:ai_calls.cost_usd(caller 級彙總) -
-
- - - - - - {% set max_cost = (top_cost_callers | map(attribute='cost') | max) or 1 %} - {% for c in top_cost_callers %} - - - - - - - - {% endfor %} - -
呼叫端呼叫Token成本佔比
{{ c.caller }}{{ "{:,}".format(c.calls) }}{{ "{:,}".format(c.tokens) }}${{ "%.2f"|format(c.cost) }} -
-
-
-
-
-
- {% endif %} - - - {% if cost_trend_30d %} -
-
過去 30 日每日成本(依 provider) - 堆疊柱圖:依 provider 分色 · 資料來源 ai_calls 每日 SUM(cost_usd) -
-
- -
-
- {% endif %} - - {% if price_rec_7d %} -
-
AI 價格決策 7 日(ai_price_recommendations) - 展現 AI 編排的商業面產出 — 不只 cost,看實際決策數量 -
-
-
- {% for p in price_rec_7d %} -
-
- {{ p.strategy }} - {{ p.count }} - 信心 {{ "%.2f"|format(p.avg_confidence) }} -
-
- {% endfor %} -
-
-
+
Business Output

AI 價格決策 7 日

{% for p in price_rec_7d %}
{{ p.strategy }}{{ p.count }}信心 {{ "%.2f"|format(p.avg_confidence) }}
{% endfor %}
{% endif %} -

- Operation Ollama-First v5.0 / Phase 47 — 預算控管 - (5 表深挖:ai_call_budgets / ai_calls / ai_price_recommendations / ai_insights / cost_throttle_state) -

+

Operation Ollama-First v5.0 — AI 成本治理艙

{% endblock %} diff --git a/templates/admin/host_health.html b/templates/admin/host_health.html index f941fe9..f711f5a 100644 --- a/templates/admin/host_health.html +++ b/templates/admin/host_health.html @@ -1,556 +1,184 @@ {% extends "ewoooc_base.html" %} -{% block title %}主機健康監控{% endblock %} +{% block title %}基礎設施生命線{% endblock %} {% block ewooo_content %} + + +{% set down = namespace(count=0) %} +{% for h in ollama_hosts %}{% if not h.healthy %}{% set down.count = down.count + 1 %}{% endif %}{% endfor %} +{% set active_playbooks = namespace(count=0) %} +{% for p in playbook_ranking %}{% if p.is_active %}{% set active_playbooks.count = active_playbooks.count + 1 %}{% endif %}{% endfor %} +{% set throttled = namespace(count=0) %} +{% for provider, info in throttle_state.items() %}{% if info.throttled %}{% set throttled.count = throttled.count + 1 %}{% endif %}{% endfor %} +
-

主機健康監控 - 三主機 Ollama + MCP + 成本節流即時狀態 -

- - -
-
Ollama 三主機(HTTP /api/tags 即時 probe)
-
- - - - - - {% for h in ollama_hosts %} - - - - - - - - - {% endfor %} - -
角色主機HTTP 健康異常標記已載入模型動作
{{ h.label }}{{ h.host }} - {% if h.healthy %} - HTTP 正常 - {% else %} - 離線 - {% endif %} - - {% if h.unhealthy_mark %} - 已標記異常(30 秒) - {% else %} - - {% endif %} - - {% for m in h.models %} - {{ m }} - {% endfor %} - {% if not h.models %}無 / 未連線{% endif %} - - {% if h.unhealthy_mark or not h.healthy %} - - {% else %} - - {% endif %} -
+
+
Infrastructure Lifeline · Ollama / MCP / AIOps
+

基礎設施生命線

+

這頁是 AI 中樞的底盤監控:三主機 Ollama 級聯、MCP 工具層、成本節流與 ADR-013 AutoHeal 閉環。先看能不能活,再看要不要修。

+
+
Ollama Down
{{ down.count }}{{ ollama_hosts|length }} 台即時 probe
+
AIOps Open
{{ aiops_summary.incidents_open if aiops_summary else '—' }}7 日 incident 未解決
+
Heal Rate
{{ "%.0f"|format(aiops_summary.heal_success_rate) if aiops_summary else '—' }}{% if aiops_summary %}%{% endif %}ADR-013 自癒成功率
+
Throttled
{{ throttled.count }}成本節流供應商
-
+ - -
-
MCP 服務(Phase 10/10.5)
-
- - - - - - {% for server, healthy in mcp_status.items() %} - - - - - {% else %} - - {% endfor %} - -
服務名稱狀態
{{ server }} - {% if healthy %} - 正常 - {% else %} - — 未啟用 / 離線 - {% endif %} -
MCP_ROUTER_ENABLED=false 或 mcp-stack 未部署
-
-
- - -
-
成本節流狀態(Phase 20)
-
- {% if throttle_state %} - - - - - - {% for provider, info in throttle_state.items() %} - - - - - - - - +
+
+
+
+
Host Cascade

Ollama 三主機

+ {{ '需要處理' if down.count > 0 else '全部在線' }} +
+
+
+ {% for h in ollama_hosts %} +
+
+
+ {{ h.label }} + {% if h.healthy %}HTTP 正常{% else %}離線{% endif %} +
+
{{ h.host }}
+
+ {% for m in h.models %}{{ m }}{% endfor %} + {% if not h.models %}無模型資料 / 未連線{% endif %} +
+
+
+ {% if h.unhealthy_mark %}30 秒異常標記{% endif %} + {% if h.unhealthy_mark or not h.healthy %}{% endif %} +
+
{% endfor %} -
-
供應商已花費預算月底推估使用率狀態
{{ provider }}${{ "%.2f"|format(info.spent) }}${{ "%.2f"|format(info.budget) }}${{ "%.2f"|format(info.projected) }}{{ "%.0f"|format(info.ratio * 100) }}% - {% if info.throttled %} - 已節流 - {% else %} - 正常 - {% endif %} -
- {% else %} -

- COST_THROTTLE_ENABLED=false 或尚未首次評估(每小時 cron 執行) -

- {% endif %} -
-
+
+
+ - - {% if aiops_summary %} -
-
- AIOps 自癒系統 7 日摘要 - 資料來源:incidents + heal_logs(ADR-013 AutoHeal 閉環) -
-
-
-
-
- 總事件 - {{ aiops_summary.incidents_total }} -
-
-
-
- 未解決 - - {{ aiops_summary.incidents_open }} - -
-
-
-
- 已解決 - {{ aiops_summary.incidents_resolved }} -
-
-
-
- P0/P1 - - {{ aiops_summary.incidents_p0 + aiops_summary.incidents_p1 }} - -
-
-
-
- 自癒成功率 - - {{ "%.0f"|format(aiops_summary.heal_success_rate) }}% - -
-
-
-
- 平均自癒耗時 - {{ aiops_summary.heals_avg_ms }} ms -
-
-
-
- - 7d 共 {{ aiops_summary.heals_total }} 次自癒嘗試 - (成功 {{ aiops_summary.heals_success }} · 失敗 {{ aiops_summary.heals_failed }}) -
- {% if aiops_summary.heal_sparkline %} -
- -
-
- 7 日每日自癒成功率趨勢 -
+ {% if health_history %} +
+
24h Probe History

健康趨勢摘要

+
{% for h in health_history %}{% endfor %}
角色總探針正常離線在線率平均 ms
{{ h.host_label }}{{ h.total }}{{ h.up_count }}{{ h.down_count }}{{ "%.1f"|format(h.uptime_pct) }}%{{ h.avg_ms }}
+
{% endif %}
-
+ + + + + {% if throttle_state %} +
+
Cost Throttle

成本節流狀態

+
{% for provider, info in throttle_state.items() %}{% endfor %}
供應商已花費預算月底推估使用率狀態
{{ provider }}${{ "%.2f"|format(info.spent) }}${{ "%.2f"|format(info.budget) }}${{ "%.2f"|format(info.projected) }}{{ "%.0f"|format(info.ratio * 100) }}%{% if info.throttled %}已節流{% else %}正常{% endif %}
+
{% endif %} - {% if mcp_24h %} -
-
MCP 服務 24h 工作量 - 資料來源:mcp_calls 表 — 展現 AI×MCP 編排規模 -
-
- - - - - - - - - - - - - - {% for s in mcp_24h %} - - - - - - - - - - {% endfor %} - -
服務呼叫次數成功率快取命中率使用 Tool 數平均耗時成本 (USD)
{{ s.server }}{{ "{:,}".format(s.total_calls) }} - - {{ "%.1f"|format(s.success_rate) }}% - - - - {{ "%.1f"|format(s.cache_rate) }}% - - {{ s.tools_used }}{{ s.avg_ms }} ms${{ "%.4f"|format(s.total_cost) }}
-
-
+
+
MCP Workload

MCP 服務 24h 工作量

+
{% for s in mcp_24h %}{% endfor %}
服務呼叫成功率快取Tools平均成本
{{ s.server }}{{ "{:,}".format(s.total_calls) }}{{ "%.1f"|format(s.success_rate) }}%{{ "%.1f"|format(s.cache_rate) }}%{{ s.tools_used }}{{ s.avg_ms }} ms${{ "%.4f"|format(s.total_cost) }}
+
{% endif %} - - {% if health_history %} -
-
過去 24 小時健康趨勢 - 資料來源:host_health_probes(每次刷新自動寫入) +
+
+
Incidents

最近 10 筆事件

{% if recent_incidents %}{% for i in recent_incidents %}{% endfor %}
時間任務錯誤等級狀態訊息
{{ i.created_at }}{{ i.task_name }}{{ i.error_type }}{{ i.severity }}{{ i.status }}{{ i.error_message }}
{% else %}
尚無 incident 紀錄
{% endif %}
+
Heal Logs

最近 10 筆自癒

{% if recent_heals %}{% for h in recent_heals %}{% endfor %}
時間動作結果耗時細節
{{ h.created_at }}{{ h.action_type or '—' }}{% if h.result == 'success' %}成功{% elif h.result == 'failed' %}失敗{% else %}{{ h.result }}{% endif %}{{ h.duration_ms }} ms{{ h.action_detail }}
{% else %}
尚無 heal log
{% endif %}
-
- - - - - - - - - - - - - {% for h in health_history %} - - - - - - - - - {% endfor %} - -
角色總探針次數正常次數離線次數在線率平均回應 ms
{{ h.host_label }}{{ h.total }}{{ h.up_count }}{{ h.down_count }} - - {{ "%.1f"|format(h.uptime_pct) }}% - - {{ h.avg_ms }}
-
-
- {% endif %} + + - -
-
- 最近 10 筆 Incidents - 資料來源:incidents(ADR-013 AutoHeal 觸發紀錄) -
-
- {% if recent_incidents %} - - - - - - - - - - {% for i in recent_incidents %} - - - - - - - - - - - {% endfor %} - -
建立時間任務錯誤類型嚴重度狀態重試錯誤訊息解決時間
{{ i.created_at }}{{ i.task_name }}{{ i.error_type }} - {% if i.severity in ('P0', 'P1') %}{{ i.severity }} - {% elif i.severity == 'P2' %}{{ i.severity }} - {% else %}{{ i.severity }}{% endif %} - - {% if i.status == 'open' %}未解決 - {% elif i.status == 'resolved' %}已解決 - {% else %}{{ i.status }}{% endif %} - {{ i.retry_count }}{{ i.error_message }}{{ i.resolved_at or '—' }}
- {% else %} -
- 尚無 incident 紀錄(即系統尚未觸發過 AutoHeal) -
- {% endif %} -
-
+ {% if embed_queue_pending > 0 or embed_queue_failed > 0 %}
Embedding 重試佇列:待處理 {{ embed_queue_pending }} 筆 · 失敗 {{ embed_queue_failed }} 筆
{% endif %} - -
-
- 最近 10 筆 Heal Logs - 資料來源:heal_logs(自癒 playbook 執行歷史) -
-
- {% if recent_heals %} - - - - - - - - - {% for h in recent_heals %} - - - - - - - - - {% endfor %} - -
時間動作結果耗時 ms關聯 Incident細節
{{ h.created_at }}{{ h.action_type or '—' }} - {% if h.result == 'success' %}成功 - {% elif h.result == 'failed' %}失敗 - {% elif h.result == 'skipped' %}跳過 - {% else %}{{ h.result }}{% endif %} - {{ h.duration_ms }}#{{ h.incident_id }} · {{ h.error_type or '—' }}{{ h.action_detail }}
- {% else %} -
- 尚無 heal log(一鍵 AutoHeal 觸發後將累積) -
- {% endif %} -
-
- - -
-
- AutoHeal Playbook 庫排行 - 資料來源:playbooks(success/fail 累計,按執行次數排序) -
-
- {% if playbook_ranking %} - - - - - - - - - - - - {% for p in playbook_ranking %} - - - - - - - - - - - - - {% endfor %} - -
Playbook錯誤類型動作嚴重度成功失敗成功率狀態冷卻 min切換
{{ p.name }}{{ p.error_type }}{{ p.action_type }}{{ p.severity }}{{ p.success }}{{ p.fail }} - {% if (p.success + p.fail) > 0 %} - - {{ "%.0f"|format(p.success_rate) }}% - - {% else %}{% endif %} - - {% if p.is_active %} - 啟用 - {% else %} - 停用 - {% endif %} - {{ p.cooldown_min }} - -
- {% else %} -
- 尚無 playbook 資料(migration 013 + 020 是否已跑?) -
- {% endif %} -
-
- - -
-
- 備份歷史(過去 7 日) - 資料來源:backup_log -
-
- {% if backup_history %} - - - - - - - - - - {% for b in backup_history %} - - - - - - - - - {% endfor %} - -
時間類型狀態大小 (MB)耗時 s錯誤
{{ b.created_at }}{{ b.backup_type }} - {% if b.status == 'success' %}成功 - {% elif b.status == 'failed' %}失敗 - {% else %}{{ b.status }}{% endif %} - {{ "{:,}".format(b.size_mb) }}{{ b.duration_s }}{{ b.error }}
- {% else %} -
- 過去 7 日無備份紀錄(每日 03:00 cron 執行) -
- {% endif %} -
-
- - - {% if embed_queue_pending > 0 or embed_queue_failed > 0 %} -
- Embedding 重試佇列: - 待處理 {{ embed_queue_pending }} 筆 · - 失敗 {{ embed_queue_failed }} 筆 - - 資料來源:embedding_retry_queue — 卡住的 embedding 工作,需檢查 Ollama 健康 - -
- {% endif %} - -

- Operation Ollama-First v5.0 / Phase 47 — 主機健康監控 - (8 表深挖:host_health_probes / mcp_calls / incidents / heal_logs / playbooks / backup_log / embedding_retry_queue / ai_call_budgets) -

+

Operation Ollama-First v5.0 — 基礎設施生命線

{% endblock %} diff --git a/templates/admin/observability_overview.html b/templates/admin/observability_overview.html index 9e0f89d..7ebe256 100644 --- a/templates/admin/observability_overview.html +++ b/templates/admin/observability_overview.html @@ -1,302 +1,614 @@ {% extends "ewoooc_base.html" %} -{% block title %}觀測台總覽{% endblock %} +{% block title %}AI 觀測戰情室{% endblock %} {% block ewooo_content %} -
-

AI 觀測台總覽 - {{ today }} · 全景一頁看(資料來源 8 表跨 JOIN) -

+ + +{% set ai = summary.ai_calls if summary.ai_calls else none %} +{% set host_count = summary.hosts|length if summary.hosts else 0 %} +{% set host_bad = namespace(value=0) %} +{% if summary.hosts %} + {% for h in summary.hosts %} + {% if h.uptime_pct < 90 %}{% set host_bad.value = host_bad.value + 1 %}{% endif %} + {% endfor %} +{% endif %} +{% set risk_count = (summary.budget_alerts|length if summary.budget_alerts else 0) + host_bad.value + (1 if ai and ai.error_rate >= 15 else 0) + (1 if summary.episodes and summary.episodes.pending > 0 else 0) %} + +
+
+ AI Observability Command Room · {{ today }} +

AI 觀測戰情室

+

+ 這裡是 momo-pro 私有 AI 中樞的第一入口:三主機、AI 呼叫、RAG 學習、MCP、AIOps、預算與 PPT 視覺審核全部收斂到同一張作戰圖。數字只讀正式資料來源;沒有資料時顯示診斷空狀態,不用假 KPI 撐場面。 +

+ +
+
+
Risk Signals
+
{{ risk_count }}
+
主機、預算、錯誤率、待審核的即時風險數
+
+
+
AI Calls / 24h
+
{{ "{:,}".format(ai.total) if ai else '—' }}
+
Token {{ "{:,}".format(ai.tokens) if ai else '—' }}
+
+
+
Cost
+
${{ "%.2f"|format(ai.cost_24h) if ai else '0.00' }}
+
當月累計 ${{ "%.2f"|format(summary.month_cost|default(0)) }}
+
+
+
RAG Hit Rate
+
{{ "%.1f"|format(ai.rag_rate) if ai else '—' }}{% if ai %}%{% endif %}
+
Cache {{ "%.0f"|format(ai.cache_rate) if ai else '—' }}{% if ai %}%{% endif %}
+
+
+
+ +
+
+
+
+
+
Host Cascade
+

三主機生命線

+
+ 主機健康 +
+
+ {% if summary.hosts %} +
+ {% for h in summary.hosts %} +
- {{ h.label }} -

{{ "%.1f"|format(h.uptime_pct) }}%

- 24h 在線率({{ h.up }}/{{ h.total }} probe) +
+ {{ h.label }} + {{ "%.1f"|format(h.uptime_pct) }}% +
+
{{ h.up }}/{{ h.total }} probes · 平均 {{ h.avg_ms }} ms · 24h 視窗
-
- -
{{ h.avg_ms }} ms
+
+ {% if host_sparkline.get(h.label) %} + + {% else %} +
尚無趨勢資料
+ {% endif %}
- {% if host_sparkline.get(h.label) %} -
- -
- {% endif %} + {% endfor %}
-
-
- {% endfor %} - {% else %} -
-
- - host_health_probes 表無資料(migration 029 是否已跑?scheduler probe job 是否已啟動?) -
-
- {% endif %} -
- - -
- {% if summary.ai_calls %} -
-
-
- 24h AI 呼叫 -

{{ "{:,}".format(summary.ai_calls.total) }}

- Token:{{ "{:,}".format(summary.ai_calls.tokens) }} -
-
-
-
-
-
- 成本 -

${{ "%.2f"|format(summary.ai_calls.cost_24h) }}/24h

- 當月累計 ${{ "%.2f"|format(summary.month_cost) }} -
-
-
-
-
-
- 錯誤率 -

{{ "%.1f"|format(summary.ai_calls.error_rate) }}%

- {{ summary.ai_calls.errors }} 次失敗 -
-
-
-
-
-
- RAG 命中率 -

{{ "%.1f"|format(summary.ai_calls.rag_rate) }}%

- {{ summary.ai_calls.rag_hits }} hits · cache {{ "%.0f"|format(summary.ai_calls.cache_rate) }}% -
-
-
- {% endif %} -
- - - {% if summary.budget_alerts %} -
-
- 預算告警 — 共 {{ summary.budget_alerts|length }} 項超出閾值 -
-
- - - - - - {% for b in summary.budget_alerts %} - - - - - - - - {% endfor %} - -
週期供應商已花費預算使用率
{{ b.period }}{{ b.provider }}${{ "%.2f"|format(b.spent) }}${{ "%.2f"|format(b.budget) }} - - {{ "%.0f"|format(b.ratio * 100) }}% - -
-
- -
- {% endif %} - - -
- {% if summary.aiops %} -
-
-
AIOps 自癒 7d
-
-
-
事件總數

{{ summary.aiops.incidents_total }}

-
未解決

{{ summary.aiops.incidents_open }}

-
P0/P1

{{ summary.aiops.incidents_p0_p1 }}

-
自癒成功率

{{ "%.0f"|format(summary.aiops.heal_rate) }}%

-
-
- -
-
- {% endif %} - - {% if summary.mcp %} -
-
-
MCP 24h
-
-
-
tool 呼叫

{{ "{:,}".format(summary.mcp.total) }}

-
使用 server

{{ summary.mcp.servers }}

-
cache 命中

{{ summary.mcp.cache_hits }}

-
成本

${{ "%.4f"|format(summary.mcp.cost) }}

-
-
- -
-
- {% endif %} - - {% if summary.episodes %} -
-
-
RAG 學習鏈路 30d
-
-
-
待審核

{{ summary.episodes.pending }}

-
總 episodes

{{ summary.episodes.total_30d }}

-
已晉升

{{ summary.episodes.approved_30d }}

-
晉升率

{{ "%.0f"|format(summary.episodes.approval_rate) }}%

-
-
- + + + {% if summary.budget_alerts %} +
+
+
+
Budget Guard
+

預算告警

+
+ 處理預算 +
+
+ + + + + + {% for b in summary.budget_alerts %} + + + + + + + + {% endfor %} + +
週期供應商已花費預算使用率
{{ b.period }}{{ b.provider }}${{ "%.2f"|format(b.spent) }}${{ "%.2f"|format(b.budget) }}{{ "%.0f"|format(b.ratio * 100) }}%
+
+
+ {% endif %} +
+ + +
+ +
+ - {% endif %} -
- - {% if summary.ppt and summary.ppt.total > 0 %} -
-
PPT 視覺審核 7d
-
-
-
總筆數

{{ summary.ppt.total }}

-
通過

{{ summary.ppt.passed }}

-
失敗

{{ summary.ppt.failed }}

-
通過率

{{ "%.0f"|format(summary.ppt.pass_rate) }}%

+ - -
- {% endif %} - -
-
9 大子頁入口
- - {% endblock %} diff --git a/templates/admin/promotion_review.html b/templates/admin/promotion_review.html index 113ec96..bf33959 100644 --- a/templates/admin/promotion_review.html +++ b/templates/admin/promotion_review.html @@ -1,283 +1,107 @@ {% extends "ewoooc_base.html" %} -{% block title %}RAG 學習晉升審核{% endblock %} +{% block title %}RAG Promotion Gate{% endblock %} {% block ewooo_content %} + + +{% set total_dist = (episode_distribution_30d.values() | sum) if episode_distribution_30d else 0 %} +{% set approved_30d = episode_distribution_30d.get('approved', 0) if episode_distribution_30d else 0 %} +{% set rejected_30d = namespace(value=0) %} +{% if episode_distribution_30d %}{% for status, cnt in episode_distribution_30d.items() %}{% if status.startswith('rejected') %}{% set rejected_30d.value = rejected_30d.value + cnt %}{% endif %}{% endfor %}{% endif %} +{% set approval_rate = (approved_30d / total_dist * 100) if total_dist > 0 else 0 %} +
-

RAG 學習晉升審核 - 待審核 × {{ episodes|length }} 筆 · 知識庫 ai_insights × {{ kb_size or 0 }} 筆 -

+
+
RAG Promotion Gate · Human Review / Dedup / Anti-pollution
+

RAG 知識晉升閘

+

這頁是 RAG 不被污染的最後關卡。高權重 learning episode 不能直接進知識庫,必須先看品質、相似知識、人工拒絕與晉升分布,再決定是否寫入 ai_insights。

+
+
Awaiting Review
{{ episodes|length }}高權重待審片段
+
Knowledge Base
{{ kb_size or 0 }}ai_insights 已晉升
+
30d Approval
{{ "%.0f"|format(approval_rate) }}%{{ approved_30d }}/{{ total_dist }} episodes
+
Rejected 30d
{{ rejected_30d.value }}品質 / 幻覺 / 重複 / 人工拒
+
+
- {% if error %} -
{{ error }}
- {% endif %} + {% if error %}
{{ error }}
{% endif %} - {% if episodes %} -

- Phase 11 PromotionGate Stage 4 強制門檻:weight ≥ 0.8 的 episode 必經統帥審核, - 24 小時無回應自動過期(weight 降為 0.5 不晉升)。 - 點「通過晉升」→ 寫入 ai_insights 供 RAG 檢索;點「拒絕」→ 永不晉升(learning_episodes 留存)。 -

+
+
+
+
Review Queue

待審核片段

{{ episodes|length }} 筆
+
+

PromotionGate Stage 4:weight ≥ 0.8 必經統帥審核;24 小時無回應自動降權,不直接污染知識庫。

+
+
- {% for ep in episodes %} -
-
-
- 學習片段 #{{ ep.id }} - {{ ep.episode_type }} - {% if ep.source_table %} - {{ ep.source_table }}#{{ ep.source_id }}{% endif %} - 權重:{{ "%.2f"|format(ep.weight) }} - 品質:{{ "%.2f"|format(ep.quality_score) }} + {% if episodes %} + {% for ep in episodes %} +
+
+
學習片段 #{{ ep.id }} {{ ep.episode_type }}{% if ep.source_table %}{{ ep.source_table }}#{{ ep.source_id }}{% endif %}權重 {{ "%.2f"|format(ep.weight) }}品質 {{ "%.2f"|format(ep.quality_score) }}
+ {{ ep.created_at }}
- {{ ep.created_at }} -
-
-
{{ ep.distilled_text }}
- - {% if ep.similar_insights %} -
- - RAG 自動檢索:知識庫中 Top 3 相似已晉升內容 - (cosine ≥ 0.7)— 輔助判斷此次晉升是否冗餘 - -
    - {% for sim in ep.similar_insights %} -
  • - #{{ sim.id }} - {{ sim.insight_type }} - 相似度 {{ "%.2f"|format(sim.similarity) }} - {% if sim.created_at %}{{ sim.created_at }}{% endif %} - {{ sim.content }}{% if sim.content|length >= 180 %}…{% endif %} -
  • - {% endfor %} -
+
+
{{ ep.distilled_text }}
+ {% if ep.similar_insights %}
Top 3 相似已晉升知識(用來判斷是否重複)
    {% for sim in ep.similar_insights %}
  • #{{ sim.id }}{{ sim.insight_type }}相似度 {{ "%.2f"|format(sim.similarity) }}{{ sim.content }}{% if sim.content|length >= 180 %}…{% endif %}
  • {% endfor %}
{% else %}
知識庫無 cosine ≥ 0.7 相似內容,可能是新領域知識。
{% endif %}
- {% else %} -
- RAG 檢索:知識庫中無 cosine ≥ 0.7 的相似內容(屬新領域知識,建議晉升) -
- {% endif %} -
- -
- {% endfor %} - {% else %} -
- 目前無待審核片段。 - (RAG 未啟用 / 無高權重片段 / 全部已 24 小時過期) + + + {% endfor %} + {% else %} +
目前無待審核片段。
+ {% endif %}
- {% endif %} - - {% if episode_distribution_30d %} -
-
蒸餾池 30 日狀態分布 - 資料來源:learning_episodes(PromotionGate 4 階段流動全景) -
-
-
-
- -
-
- - - - - - {% set total_ep = (episode_distribution_30d.values() | sum) or 1 %} - {% for status, cnt in episode_distribution_30d.items() %} - - - - - - {% endfor %} - -
狀態筆數佔比
- {% if status == 'pending' %}待處理 - {% elif status == 'awaiting_review' %}待審核 - {% elif status == 'approved' %}已晉升 - {% elif status == 'rejected_quality' %}品質拒 - {% elif status == 'rejected_hallucination' %}幻覺拒 - {% elif status == 'rejected_duplicate' %}重複拒 - {% elif status == 'rejected_human' %}人工拒 - {% elif status == 'expired' %}已過期 - {% else %}{{ status }}{% endif %} - {{ cnt }}{{ "%.1f"|format(cnt / total_ep * 100) }}%
-
-
-
-
- {% endif %} + +
- {% if latest_insights %} -
-
知識庫最近 10 筆 ai_insights - 資料來源:ai_insights — 已晉升內容預覽 -
-
- - - - - - {% for i in latest_insights %} - - - - - - - - - {% endfor %} - -
#類型期間SKU建立時間預覽
#{{ i.id }}{{ i.insight_type }}{{ i.period or '—' }}{{ i.product_sku or '—' }}{{ i.created_at }}{{ i.preview }}{% if i.preview|length >= 160 %}…{% endif %}
-
-
+
Knowledge Base

最近 10 筆 ai_insights

{% for i in latest_insights %}{% endfor %}
#類型期間SKU建立時間預覽
#{{ i.id }}{{ i.insight_type }}{{ i.period or '—' }}{{ i.product_sku or '—' }}{{ i.created_at }}{{ i.preview }}{% if i.preview|length >= 160 %}…{% endif %}
{% endif %} - - {% if strategy_weights %} -
-
OpenClaw 學習策略權重 TOP 12 - 資料來源:agent_strategy_weights(ADR-012 closed-loop learning) -
-
- - - - - - - - - - - - - {% for s in strategy_weights %} - - - - - - - - - {% endfor %} - -
策略 Key權重成功失敗成功率更新時間
{{ s.strategy_key }}{{ "%.2f"|format(s.weight) }}{{ s.success }}{{ s.fail }} - {% if (s.success + s.fail) > 0 %} - - {{ "%.0f"|format(s.success_rate) }}% - - {% else %}{% endif %} - {{ s.updated_at }}
-
-
- {% endif %} - -

- Operation Ollama-First v5.0 / Phase 47 — RAG 學習晉升審核 - (4 表深挖:learning_episodes / ai_insights / agent_strategy_weights / rag_query_log) -

+

Operation Ollama-First v5.0 — RAG 知識晉升閘

{% endblock %} diff --git a/templates/admin/quality_trend.html b/templates/admin/quality_trend.html index 00c15a6..140268f 100644 --- a/templates/admin/quality_trend.html +++ b/templates/admin/quality_trend.html @@ -1,289 +1,41 @@ {% extends "ewoooc_base.html" %} -{% block title %}Caller 反饋趨勢{% endblock %} +{% block title %}AI 品質診斷台{% endblock %} {% block ewooo_content %} + + +{% set total_feedback = namespace(value=0) %}{% set worst_avg = namespace(value=5) %}{% for caller, info in trends %}{% set total_feedback.value = total_feedback.value + (info.total_feedback or 0) %}{% if info.avg_score < worst_avg.value %}{% set worst_avg.value = info.avg_score %}{% endif %}{% endfor %} +{% set episode_total = (episode_distribution.values() | sum) if episode_distribution else 0 %} +{% set rag_total = (rag_overall_dist | sum(attribute='count')) if rag_overall_dist else 0 %} +
-

Caller 反饋趨勢 - 過去 {{ days }} 日 -

+
Quality Diagnostics · {{ days }}d Window

AI 品質診斷台

這裡看 AI 的回答到底有沒有變好:caller 反饋、RAG 分數、learning episode 流量、action plan 與 outcome 閉環全部聚合到同一張品質雷達。

Feedback
{{ total_feedback.value }}caller feedback 總量
Worst Avg
{{ "%.2f"|format(worst_avg.value) }}最差 caller 平均分
Episodes
{{ episode_total }}蒸餾池 {{ days }} 日
RAG Scores
{{ rag_total }}已回饋 RAG query
+ {% if error %}
{{ error }}
{% endif %} - {% if error %} -
{{ error }}
- {% endif %} +
+
+
Caller Feedback

呼叫端 × 反饋分佈

{% for caller, info in trends %}{% else %}{% endfor %}
呼叫端平均倒讚總數趨勢分布
{{ caller }}{{ "%.2f"|format(info.avg_score) }}/5{{ info.thumbs_up }}{{ info.thumbs_down }}{{ info.total_feedback }}{% if info.trend == 'positive' %}正向{% elif info.trend == 'negative' %}負向{% elif info.trend == 'neutral' %}中性{% else %}無資料{% endif %}
無反饋資料
+ {% if action_plans_status %}
Action Plans

Action Plans 狀態分布

{% for a in action_plans_status %}{% endfor %}
狀態計畫類型數量
{{ a.status }}{{ a.plan_type }}{{ a.count }}
{% endif %} +
+ +
-
-
- -
-
-
- - {% if episode_distribution %} -
-
蒸餾池狀態(learning_episodes 過去 {{ days }} 日) - 資料來源:learning_episodes — 展現 RAG 學習鏈路飽和度 -
-
-
- {% for status, cnt in episode_distribution.items() %} -
-
- - {% if status == 'pending' %} 待處理 - {% elif status == 'awaiting_review' %} 待審核 - {% elif status == 'approved' %} 已晉升 - {% elif status == 'rejected_quality' %} 品質拒 - {% elif status == 'rejected_hallucination' %} 幻覺拒 - {% elif status == 'rejected_duplicate' %} 重複拒 - {% elif status == 'rejected_human' %} 人工拒 - {% elif status == 'expired' %} 已過期 - {% else %}{{ status }}{% endif %} - - {{ cnt }} -
-
- {% endfor %} -
-
-
- {% endif %} - - {% if rag_root_causes %} -
-
- RAG 自動根因建議 - — 對最差 3 名 caller 自動從 ai_insights 召回相似案例 -
-
- {% for rc in rag_root_causes %} -
- {{ rc.caller }} - {{ "%.2f"|format(rc.avg_score) }}/5 - {{ rc.feedback_n }} 筆反饋 -
    - {% for h in rc.hits %} -
  • - {{ h.insight_type }} - 相似度 {{ "%.2f"|format(h.similarity) }} - {{ h.content }}{% if h.content|length >= 200 %}…{% endif %} -
  • - {% endfor %} -
-
- {% endfor %} -
-
- {% endif %} - - {% if recommendations %} -
-
智能建議
-
-
    - {% for rec in recommendations %} -
  • - {% if rec.action == 'review' %}{% else %}{% endif %} - {{ rec.caller }}:{{ rec.reason }} -
  • - {% endfor %} -
-
-
- {% endif %} - -
-
呼叫端 × 反饋分佈 - (平均分數升序排列,最差先看) -
-
- - - - - - - - - - - {% for caller, info in trends %} - - - - - - - - - - {% else %} - - {% endfor %} - -
呼叫端平均總數趨勢分布
{{ caller }} - {{ "%.2f"|format(info.avg_score) }}/5 - {{ info.thumbs_up }}{{ info.thumbs_down }}{{ info.total_feedback }} - {% if info.trend == 'positive' %} - 正向 - {% elif info.trend == 'negative' %} - 負向 - {% elif info.trend == 'neutral' %} - 中性 - {% else %} - 無資料 - {% endif %} - -
- {% set pct = (info.avg_score / 5 * 100)|int %} -
-
-
無反饋資料
-
-
- - - {% if rag_overall_dist %} -
-
RAG 整體反饋分布(過去 {{ days }} 日) - 資料來源:rag_query_log.feedback_score(含全 caller,1-5 分) -
-
-
-
- -
-
- - - - - - {% set total_fb = (rag_overall_dist | sum(attribute='count')) or 1 %} - {% for r in rag_overall_dist %} - - - - - - {% endfor %} - -
星等筆數佔比
- {% for _ in range(r.score) %}{% endfor %} - {% for _ in range(5 - r.score) %}{% endfor %} - {{ r.score }} 分 - {{ r.count }}{{ "%.1f"|format(r.count / total_fb * 100) }}%
-
-
-
-
- {% endif %} - - - {% if action_plans_status %} -
-
Action Plans 狀態分布(過去 {{ days }} 日) - 資料來源:action_plans(NemoTron/OpenClaw 的計畫產出 + 審核狀態) -
-
- - - - - - {% for a in action_plans_status %} - - - - - - {% endfor %} - -
狀態計畫類型數量
- {% if a.status == 'pending' %}{{ a.status }} - {% elif a.status == 'approved' %}{{ a.status }} - {% elif a.status == 'executed' %}{{ a.status }} - {% elif a.status == 'rejected' %}{{ a.status }} - {% else %}{{ a.status }}{% endif %} - {{ a.plan_type }}{{ a.count }}
-
-
- {% endif %} - - - {% if action_outcomes_stats %} -
-
Action Outcomes 成效(過去 {{ days }} 日) - 資料來源:action_outcomes(ADR-012 閉環學習:實際動作有效嗎?) -
-
-
- {% set total_ao = (action_outcomes_stats | sum(attribute='count')) or 1 %} - {% for r in action_outcomes_stats %} -
-
- - {% if r.verdict == 'effective' %} 有效 - {% elif r.verdict == 'backfired' %} 適得其反 - {% elif r.verdict == 'neutral' %} 無顯著效果 - {% else %}{{ r.verdict }}{% endif %} - - {{ r.count }} - {{ "%.1f"|format(r.count / total_ao * 100) }}% -
-
- {% endfor %} -
-
-
- {% endif %} - -

- Operation Ollama-First v5.0 / Phase 52 — Caller 反饋趨勢(含 RAG 圓餅圖) - (6 表深挖:rag_query_log / learning_episodes / ai_insights / action_plans / action_outcomes / agent_strategy_weights) -

+ {% if rag_root_causes %}
Root Cause

RAG 自動根因建議

{% for rc in rag_root_causes %}
{{ rc.caller }}{{ "%.2f"|format(rc.avg_score) }}/5{{ rc.feedback_n }} 筆
    {% for h in rc.hits %}
  • {{ h.insight_type }}相似度 {{ "%.2f"|format(h.similarity) }}{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}
  • {% endfor %}
{% endfor %}
{% endif %} + {% if recommendations %}
Recommendations

智能建議

    {% for rec in recommendations %}
  • {% if rec.action == 'review' %}{% else %}{% endif %}{{ rec.caller }}:{{ rec.reason }}
  • {% endfor %}
{% endif %} + {% if action_outcomes_stats %}
Action Outcomes

實際動作成效

{% set total_ao = (action_outcomes_stats | sum(attribute='count')) or 1 %}{% for r in action_outcomes_stats %}
{{ r.verdict }}{{ r.count }}{{ "%.1f"|format(r.count / total_ao * 100) }}%
{% endfor %}
{% endif %} +

Operation Ollama-First v5.0 — AI 品質診斷台

-{% if rag_overall_dist %} - - -{% endif %} +{% if rag_overall_dist %}{% endif %} {% endblock %} diff --git a/templates/admin/rag_queries.html b/templates/admin/rag_queries.html index 901fdb8..1d7e922 100644 --- a/templates/admin/rag_queries.html +++ b/templates/admin/rag_queries.html @@ -1,267 +1,46 @@ {% extends "ewoooc_base.html" %} -{% block title %}RAG 召回詳情{% endblock %} +{% block title %}RAG 召回雷達{% endblock %} {% block ewooo_content %} + + +{% set total = summary.total if summary else 0 %} +
-

RAG 召回詳情 - 過去 {{ hours }} 小時 · 每筆 query 的 hits / saved_call / 反饋 -

+
+
RAG Recall Radar · {{ hours }}h Window
+

RAG 召回雷達

+

這裡追蹤每次 RAG 查詢是否真的命中、是否省下 LLM call、哪些 caller 用得好、哪些 query 沒找到知識。RAG 如果要成為武器,這裡就是雷達螢幕。

+
+ {% if summary and summary.total > 0 %}
Queries
{{ "{:,}".format(summary.total) }}
{{ summary.distinct_callers }} callers
Hit Rate
{{ "%.1f"|format(summary.hit_rate) }}%
{{ summary.with_hits }} hit · {{ summary.no_hits }} miss
Saved Call
{{ "%.1f"|format(summary.saved_rate) }}%
{{ summary.saved }} 次省下 LLM
Feedback
{{ "%.2f"|format(summary.avg_score) }}
{{ summary.feedback_count }} 筆 · 平均 {{ summary.avg_hits }} hits
{% endif %} +
+ {% if error %}
{{ error }}
{% endif %} - {% if error %} -
{{ error }}
- {% endif %} +
+
+
Query Stream

最近 50 筆查詢詳情

{% if queries %}{% for q in queries %}{% endfor %}
時間呼叫端查詢top_k門檻命中saved反饋動作
{{ q.queried_at }}{{ q.caller }}{{ q.query_text }}{% if q.query_text|length >= 200 %}…{% endif %}{{ q.top_k }}{{ q.threshold }}{% if q.hit_count > 0 %}{{ q.hit_count }}{% else %}0{% endif %}{% if q.saved_call %}saved{% else %}{% endif %}{% if q.feedback_score is not none %}{{ q.feedback_score }}/5{% else %}{% endif %}{% if q.hit_count > 0 %}{% endif %}
{% else %}
過去 {{ hours }} 小時無符合條件的 RAG 查詢紀錄。
{% endif %}
+
+ +
- -
-
- -
-
- -
-
-
- - -
-
-
- - - {% if summary and summary.total > 0 %} -
-
-
-
- 總查詢 -

{{ "{:,}".format(summary.total) }}

- {{ summary.distinct_callers }} 個呼叫端 -
-
-
-
-
-
- 命中率 -

{{ "%.1f"|format(summary.hit_rate) }}%

- {{ summary.with_hits }} hit · {{ summary.no_hits }} 未命中 -
-
-
-
-
-
- saved_call 率 -

{{ "%.1f"|format(summary.saved_rate) }}%

- {{ summary.saved }} 次省下 LLM -
-
-
-
-
-
- 反饋平均分 -

{{ "%.2f"|format(summary.avg_score) }}/5

- {{ summary.feedback_count }} 筆反饋 · 平均 {{ summary.avg_hits }} hits -
-
-
-
- {% endif %} - - - {% if by_caller %} -
-
各呼叫端 RAG 表現 - 資料來源:rag_query_log GROUP BY caller -
-
- - - - - - - - - - - - - - - {% for c in by_caller %} - - - - - - - - - - - {% endfor %} - -
呼叫端查詢命中命中率savedsaved 率反饋平均分
{{ c.caller }}{{ "{:,}".format(c.total) }}{{ c.with_hits }} - - {{ "%.1f"|format(c.hit_rate) }}% - - {{ c.saved }} - - {{ "%.1f"|format(c.saved_rate) }}% - - {{ c.fb_count }} - {% if c.fb_count > 0 %} - - {{ "%.2f"|format(c.avg_score) }} - - {% else %}{% endif %} -
-
-
- {% endif %} - - - {% if queries %} -
-
最近 50 筆查詢詳情 - 點「查 hits」展開命中的 ai_insights 內容 -
-
- - - - - - - - - - - - - {% for q in queries %} - - - - - - - - - - - - {% endfor %} - -
時間呼叫端查詢top_k門檻命中saved反饋動作
{{ q.queried_at }}{{ q.caller }}{{ q.query_text }}{% if q.query_text|length >= 200 %}…{% endif %}{{ q.top_k }}{{ q.threshold }} - {% if q.hit_count > 0 %}{{ q.hit_count }} - {% else %}0{% endif %} - - {% if q.saved_call %}saved - {% else %}{% endif %} - - {% if q.feedback_score is not none %} - {% if q.feedback_score >= 4 %}{{ q.feedback_score }}/5 - {% elif q.feedback_score >= 3 %}{{ q.feedback_score }}/5 - {% else %}{{ q.feedback_score }}/5{% endif %} - {% else %}{% endif %} - - {% if q.hit_count > 0 %} - - {% endif %} -
-
-
- {% else %} -
- 過去 {{ hours }} 小時無符合條件的 RAG 查詢紀錄。 -
- {% endif %} - -

- Operation Ollama-First v5.0 / Phase 51 — RAG 召回詳情 - (3 表跨 JOIN:rag_query_log × ai_insights × ai_calls.request_id) -

-
- - - + {% endblock %} diff --git a/templates/components/_ewoooc_shell.html b/templates/components/_ewoooc_shell.html index 230ede4..a2561ed 100644 --- a/templates/components/_ewoooc_shell.html +++ b/templates/components/_ewoooc_shell.html @@ -8,6 +8,73 @@ #} {% set _active_page = active_page|default('') %} +{% set _obs_pages = [ + 'obs_overview', 'obs_agent_orchestration', 'obs_business_intel', + 'obs_host_health', 'obs_ai_calls', 'obs_budget', + 'obs_promotion_review', 'obs_rag_queries', 'obs_quality_trend', + 'obs_ppt_audit' +] %} + + {% set _scheduler = scheduler_stats|default({}) %} {% set _momo_runs = _scheduler.get('momo_task', []) if _scheduler is mapping else [] %} {% set _latest_run = _momo_runs[0] if _momo_runs else {} %} @@ -75,57 +142,51 @@