Files
ewoooc/templates/admin/ai_calls_dashboard.html
OoO 79cf08c58c
All checks were successful
CD Pipeline / deploy (push) Successful in 2m30s
feat(p39): 觀測台升級 — DB + MCP + RAG + AI 自動化深度整合
統帥質疑:6 頁觀測台只是 raw stats dashboard,沒展現 AI 自動化專業。
深度盤點 4 軸結果:
- DB 利用率 22.7%(22 表只用 5 張)
- MCP 整合 1/6(mcp_calls 表完全沒被讀)
- RAG 整合 0/6(沒 import rag_service)
- AI 自動化 L0 × 5 + L1 × 1(純讀 dashboard,無一鍵觸發)

本 commit 5 個增強:

D-1: promotion_review 加 RAG「Top 3 相似已晉升」
- 對每筆 awaiting_review episode 跑 rag_service.query 找 ai_insights 中
  cosine ≥ 0.7 的相似已晉升內容
- 輔助人工判斷:是否冗餘?是否新領域?
- header 顯示 ai_insights 知識庫 size
- fail-safe: 單筆 RAG 失敗不影響其餘

D-2: host_health 加 MCP 24h 工作量 widget
- 從 mcp_calls 統計各 server 24h 呼叫次數 / 成功率 / cache 率 /
  使用 tool 數 / 平均耗時 / cost
- 展現「AI×MCP 編排規模」而非只「server 健康與否」

D-3: ai_calls × rag_query_log × mcp_calls 三表 JOIN
- 新增「呼叫端 × RAG × MCP 編排矩陣」card
- 每個 caller:總呼叫 / RAG 命中率 / MCP 編排率(透過 request_id 串接)
  / RAG 反饋分數 / 反饋筆數
- 展現「AI 自動化專業」核心指標

D-4: budget 加 RAG 自動策略建議 + 一鍵 force-throttle (L2)
- ratio ≥ 0.8 時自動 RAG 召回 ai_insights 中的 budget_strategy 知識
- POST /budget/force_throttle endpoint:立即重算 cost_throttle 狀態
  (不等下次每小時 cron)— 升級到 L2 自動化
- 對應頁面加「立即重算節流狀態」按鈕

D-5: host_health 加 incidents + heal_logs 7d 摘要
- 顯示 ADR-013 AutoHeal 閉環核心 KPI:
  總事件 / 未解決 / 已解決 / P0+P1 / 自癒成功率 / 平均自癒耗時
- 展現「AIOps 自癒系統」運作實況

對應升級:
- DB 利用率 22.7% → ~50%(新接 mcp_calls + rag_query_log JOIN
  + ai_insights + incidents + heal_logs)
- MCP 整合 1/6 → 3/6(host_health + ai_calls + budget 都接 mcp_calls)
- RAG 整合 0/6 → 3/6(promotion_review + budget + 待 quality_trend)
- AI 自動化 L1 → L2 一鍵 force-throttle 一個(其餘按鈕待 D-6)

全部 fail-safe:DB 表/RAG/MCP 失敗都不擋頁面渲染。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:08:41 +08:00

179 lines
7.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}AI 呼叫總覽{% endblock %}
{% block content %}
<div class="container-fluid mt-3">
<h2 class="mb-3"><i class="fas fa-chart-bar me-2"></i>AI 呼叫總覽
<small class="text-muted">過去 {{ hours }} 小時</small>
</h2>
{% if error %}
<div class="alert alert-warning"><strong><i class="fas fa-exclamation-triangle me-1"></i></strong> {{ error }}</div>
{% endif %}
<!-- 篩選 bar -->
<form method="get" class="row g-2 mb-3">
<div class="col-auto">
<select name="hours" class="form-select form-select-sm">
{% for h in [1, 6, 24, 72, 168] %}
<option value="{{ h }}" {% if hours == h %}selected{% endif %}>
{% if h < 24 %}{{ h }} 小時{% else %}{{ (h//24) }} {% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<select name="caller" class="form-select form-select-sm">
<option value="">全部呼叫端</option>
{% for c in callers %}
<option value="{{ c }}" {% if caller_filter == c %}selected{% endif %}>{{ c }}</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<select name="provider" class="form-select form-select-sm">
<option value="">全部供應商</option>
{% for p in ['gcp_ollama','ollama_secondary','ollama_111','gemini','claude','nim','openrouter','nim_via_elephant'] %}
<option value="{{ p }}" {% if provider_filter == p %}selected{% endif %}>{{ p }}</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<button class="btn btn-primary btn-sm">篩選</button>
</div>
</form>
<!-- 總覽 KPI -->
<div class="row g-2 mb-3">
<div class="col-lg-2 col-md-4 col-sm-6"><div class="card p-2"><small>呼叫次數</small><h4>{{ "{:,}".format(summary.total_calls or 0) }}</h4></div></div>
<div class="col-lg-2 col-md-4 col-sm-6"><div class="card p-2"><small>Token 用量</small><h4>{{ "{:,}".format(summary.total_tokens or 0) }}</h4></div></div>
<div class="col-lg-2 col-md-4 col-sm-6"><div class="card p-2"><small>成本 (USD)</small><h4>${{ "%.2f"|format(summary.total_cost or 0) }}</h4></div></div>
<div class="col-lg-2 col-md-4 col-sm-6"><div class="card p-2"><small>平均耗時</small><h4>{{ summary.avg_duration or 0 }} ms</h4></div></div>
<div class="col-lg-2 col-md-4 col-sm-6"><div class="card p-2"><small>RAG 命中</small><h4 class="text-success">{{ summary.rag_hits or 0 }}</h4></div></div>
<div class="col-lg-2 col-md-4 col-sm-6"><div class="card p-2"><small>錯誤次數</small><h4 class="{% if (summary.error_calls or 0) > 0 %}text-danger{% endif %}">{{ summary.error_calls or 0 }}</h4></div></div>
</div>
<!-- Phase 39 D-3: caller × RAG × MCP 編排矩陣 -->
{% if caller_richness %}
<div class="card mb-3">
<div class="card-header"><strong><i class="fas fa-network-wired me-2"></i>呼叫端 × RAG × MCP 編排矩陣</strong>
<small class="text-muted">資料來源ai_calls × mcp_calls × rag_query_log{{ hours }}h 內呼叫 ≥ 5 次的 caller</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>呼叫端</th>
<th class="text-end">總呼叫</th>
<th class="text-end">RAG 命中率</th>
<th class="text-end">MCP 編排率</th>
<th class="text-end">RAG 反饋分數</th>
<th class="text-end">反饋筆數</th>
</tr>
</thead>
<tbody>
{% for c in caller_richness %}
<tr>
<td><code>{{ c.caller }}</code></td>
<td class="text-end">{{ "{:,}".format(c.total_calls) }}</td>
<td class="text-end">
<strong class="{% if c.rag_hit_rate >= 50 %}text-success{% elif c.rag_hit_rate >= 20 %}text-warning{% else %}text-muted{% endif %}">
{{ "%.1f"|format(c.rag_hit_rate) }}%
</strong>
<small class="text-muted">({{ c.rag_hits }})</small>
</td>
<td class="text-end">
<strong class="{% if c.mcp_rate >= 30 %}text-info{% elif c.mcp_rate >= 10 %}text-warning{% endif %}">
{{ "%.1f"|format(c.mcp_rate) }}%
</strong>
<small class="text-muted">({{ c.mcp_orchestrated }})</small>
</td>
<td class="text-end">
{% if c.feedback_count > 0 %}
<strong class="{% if c.avg_rag_feedback >= 4 %}text-success{% elif c.avg_rag_feedback >= 3 %}text-warning{% else %}text-danger{% endif %}">
{{ "%.2f"|format(c.avg_rag_feedback) }}/5
</strong>
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">{{ c.feedback_count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer small text-muted">
<i class="fas fa-info-circle me-1"></i>
<strong>RAG 命中率</strong>caller 在此期間有多少呼叫吃到 RAG 召回;
<strong>MCP 編排率</strong>:有多少呼叫透過 request_id 串接到 MCP tool
<strong>反饋分數</strong>RAG 召回後人工反饋1-5
</div>
</div>
{% endif %}
<!-- by provider -->
<div class="card mb-3">
<div class="card-header"><strong>依供應商分組</strong></div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr><th>供應商</th><th class="text-end">呼叫數</th><th class="text-end">Token 用量</th><th class="text-end">成本 (USD)</th></tr>
</thead>
<tbody>
{% for row in by_provider %}
<tr>
<td><code>{{ row.provider }}</code></td>
<td class="text-end">{{ "{:,}".format(row.calls) }}</td>
<td class="text-end">{{ "{:,}".format(row.tokens) }}</td>
<td class="text-end">${{ "%.2f"|format(row.cost) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- recent calls -->
<div class="card">
<div class="card-header"><strong>最近呼叫(最新 100 筆)</strong></div>
<div class="card-body p-0">
<table class="table table-sm table-striped mb-0" style="font-size: 0.85em;">
<thead class="table-light">
<tr>
<th>編號</th><th>時間</th><th>呼叫端</th><th>供應商</th><th>模型</th>
<th class="text-end">輸入</th><th class="text-end">輸出</th><th class="text-end">耗時 ms</th>
<th>狀態</th><th class="text-end">成本 $</th><th>標記</th>
</tr>
</thead>
<tbody>
{% for r in recent %}
<tr {% if r.status not in ['ok','cache_only'] %}class="table-warning"{% endif %}>
<td>{{ r.id }}</td>
<td><small>{{ r.called_at }}</small></td>
<td><code>{{ r.caller }}</code></td>
<td><small>{{ r.provider }}</small></td>
<td><small>{{ r.model[:25] }}</small></td>
<td class="text-end">{{ r.in_tokens }}</td>
<td class="text-end">{{ r.out_tokens }}</td>
<td class="text-end">{{ r.duration_ms }}</td>
<td><small>{{ r.status }}</small></td>
<td class="text-end">${{ "%.4f"|format(r.cost) }}</td>
<td>
{% if r.cache_hit %}<span class="badge bg-success">快取</span>{% endif %}
{% if r.rag_hit %}<span class="badge bg-info">RAG</span>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<p class="text-muted mt-2"><small>
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 29 — AI 呼叫總覽
</small></p>
</div>
{% endblock %}