All checks were successful
CD Pipeline / deploy (push) Successful in 2m36s
統帥質疑:「6 頁內容太空洞,要更貼近資料庫裡所有數據」
盤點:DB 有 22 表,6 頁原本只用 5 表(22.7% 利用率)。
本 commit 新接 12 張既有但未用的表。
K-1 host_health(接 +5 表 → 8 表)
- 加 incidents 最近 10 筆詳細(task_name/error_type/嚴重度/狀態/重試/錯誤訊息)
- 加 heal_logs 最近 10 筆(action_type/result/耗時/incident 關聯)
- 加 playbooks 庫排行 TOP 12(success_count/fail_count/成功率/啟用狀態)
- 加 backup_log 7 日歷史(type/status/size/duration/error)
- 加 embedding_retry_queue pending/failed 警示
K-2 ai_calls(接 +3 表)
- 加 24h 每小時呼叫趨勢 bucket(含成本+錯誤+流量分布條)
- 加 by model 細分(不只 provider,到 model 版本級別)TOP 15
- 加 agent_context 最近 10 筆(OpenClaw/Hermes 對話 session preview)
K-3 budget(接 +3 表)
- 加當月 Top 5 燒錢呼叫端(caller × cost ranking)
- 加過去 30 日每日成本 by provider 趨勢表
- 加 ai_price_recommendations 7 日統計(strategy 分布 + 平均信心度)
K-4 promotion_review(接 +2 表)
- 加蒸餾池 30 日 status 分布(不只 awaiting,看 8 種完整流動)
- 加 ai_insights 最近 10 筆已晉升內容預覽
- 加 agent_strategy_weights TOP 12(OpenClaw 學習權重 + 成功率)
K-5 quality_trend(接 +3 表)
- 加 RAG 整體 feedback 1-5 分分布(過去 N 日,星等視覺)
- 加 action_plans status 分布(pending/approved/executed/rejected)
- 加 action_outcomes verdict 分布(effective/neutral/backfired
— ADR-012 閉環學習核心 KPI)
K-6 ppt_audit_history(接 +0 表,但 deeper SQL)
- 加 30 日統計卡(total/passed/failed/error/通過率/總 issue 數/平均信心度)
- 加 Top 10 反覆失敗檔案(30d 失敗次數 + total issues)
- 加 empty state 說明(PPT_VISION_ENABLED=false 時顯示啟用步驟)
DB 利用率對應:
- Phase 38 起點:5 表(22.7%)
- Phase 39-46 累計:12 表(54.5%)
- Phase 47 收官:17 表(77.3%)
新接:playbooks / backup_log / embedding_retry_queue / agent_context /
ai_price_recommendations / agent_strategy_weights / action_plans /
action_outcomes(之前已接:incidents/heal_logs/ai_insights/learning_episodes/
rag_query_log/mcp_calls/ai_calls/host_health_probes/ppt_audit_results)
每頁 widget 數對應:
- host_health: 5 卡 → 11 卡
- ai_calls: 5 卡 → 8 卡
- budget: 4 卡 → 7 卡
- promotion_review: 2 卡 → 5 卡
- quality_trend: 4 卡 → 7 卡
- ppt_audit: 3 卡 → 6 卡
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
312 lines
13 KiB
HTML
312 lines
13 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}AI 呼叫總覽{% endblock %}
|
||
|
||
{% block ewooo_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 %}
|
||
|
||
<!-- Phase 40 D-7: 一鍵觸發 Code Review (L2 自動化) -->
|
||
<div class="mb-3">
|
||
<button class="btn btn-warning btn-sm" onclick="triggerCodeReview()">
|
||
<i class="fas fa-microscope me-1"></i>觸發 Code Review Pipeline (5 step)
|
||
</button>
|
||
<small class="text-muted ms-2">
|
||
看到錯誤率飆高?一鍵觸發 Hermes→OpenClaw→EA→NemoTron 5 步審查最新 commit,
|
||
在背景執行,完成後 Telegram 通知。
|
||
</small>
|
||
</div>
|
||
|
||
<!-- 篩選 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 %}
|
||
|
||
<!-- Phase 47 K-2: 24h 每小時呼叫趨勢 -->
|
||
{% if hourly_trend %}
|
||
<div class="card mb-3">
|
||
<div class="card-header"><strong><i class="fas fa-chart-area me-2"></i>過去 24h 每小時呼叫趨勢</strong>
|
||
<small class="text-muted">每小時 bucket:呼叫數 · 成本 · 錯誤</small>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0" style="font-size: 0.85em;">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>時段</th><th>呼叫數</th><th>成本 USD</th><th>錯誤</th><th style="width: 50%;">流量分布</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% set max_calls = (hourly_trend | map(attribute='calls') | max) or 1 %}
|
||
{% for h in hourly_trend %}
|
||
<tr>
|
||
<td><code>{{ h.hour }}</code></td>
|
||
<td><strong>{{ "{:,}".format(h.calls) }}</strong></td>
|
||
<td>${{ "%.3f"|format(h.cost) }}</td>
|
||
<td>
|
||
{% if h.errors > 0 %}<span class="text-danger">{{ h.errors }}</span>
|
||
{% else %}<small class="text-muted">0</small>{% endif %}
|
||
</td>
|
||
<td>
|
||
<div class="progress" style="height: 8px;">
|
||
<div class="progress-bar {% if h.errors > 0 %}bg-warning{% else %}bg-info{% endif %}"
|
||
style="width: {{ (h.calls / max_calls * 100) | round | int }}%"></div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Phase 47 K-2: by model 細分 -->
|
||
{% if by_model %}
|
||
<div class="card mb-3">
|
||
<div class="card-header"><strong><i class="fas fa-microchip me-2"></i>依模型細分</strong>
|
||
<small class="text-muted">資料來源:ai_calls.model(細到模型版本)— Top 15</small>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0" style="font-size: 0.9em;">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>模型</th><th>供應商</th>
|
||
<th class="text-end">呼叫</th><th class="text-end">Token</th>
|
||
<th class="text-end">成本 USD</th><th class="text-end">平均耗時</th>
|
||
<th class="text-end">錯誤</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for m in by_model %}
|
||
<tr>
|
||
<td><code>{{ m.model[:35] }}</code></td>
|
||
<td><span class="badge bg-secondary">{{ m.provider }}</span></td>
|
||
<td class="text-end">{{ "{:,}".format(m.calls) }}</td>
|
||
<td class="text-end">{{ "{:,}".format(m.tokens) }}</td>
|
||
<td class="text-end">${{ "%.4f"|format(m.cost) }}</td>
|
||
<td class="text-end">{{ m.avg_ms }} ms</td>
|
||
<td class="text-end">
|
||
{% if m.errors > 0 %}<span class="text-danger">{{ m.errors }}</span>
|
||
{% else %}<small class="text-muted">0</small>{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Phase 47 K-2: agent_context 最近 10 筆 -->
|
||
{% if recent_contexts %}
|
||
<div class="card mb-3">
|
||
<div class="card-header"><strong><i class="fas fa-comments-dollar me-2"></i>Agent 對話上下文(最近 10 筆)</strong>
|
||
<small class="text-muted">資料來源:agent_context(Hermes/OpenClaw/NemoTron 工作 session)</small>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0" style="font-size: 0.85em;">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>時間</th><th>Agent</th><th>Key</th>
|
||
<th class="text-end">TTL min</th><th>預覽</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for c in recent_contexts %}
|
||
<tr>
|
||
<td><small>{{ c.created_at }}</small></td>
|
||
<td><span class="badge bg-info text-dark">{{ c.agent_name }}</span></td>
|
||
<td><code>{{ c.context_key }}</code></td>
|
||
<td class="text-end">{{ c.ttl_minutes }}</td>
|
||
<td><small class="text-muted">{{ c.preview }}{% if c.preview|length >= 120 %}…{% endif %}</small></td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</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 40 — AI 呼叫總覽(含 RAG/MCP 編排矩陣)
|
||
</small></p>
|
||
</div>
|
||
|
||
<script>
|
||
async function triggerCodeReview() {
|
||
if (!confirm('觸發 Code Review Pipeline?\n\n會對最新 commit 跑 5 step 審查(Hermes 掃描 → OpenClaw 摘要 → EA 決策 → NemoTron 行動),背景執行。')) return;
|
||
try {
|
||
const r = await fetch('/observability/ai_calls/trigger_code_review', {method: 'POST'});
|
||
const d = await r.json();
|
||
if (d.ok) {
|
||
alert(`✅ ${d.message}\n\nPipeline ID: ${d.pipeline_id}\nCommit: ${d.commit_sha}\n變更檔案: ${d.changed_files_count} 個`);
|
||
} else {
|
||
alert('❌ ' + (d.error || '觸發失敗'));
|
||
}
|
||
} catch (e) {
|
||
alert('Error: ' + e);
|
||
}
|
||
}
|
||
</script>
|
||
{% endblock %}
|