feat(observability): 收尾 Agent 與商業戰情頁
All checks were successful
CD Pipeline / deploy (push) Successful in 7m39s

This commit is contained in:
OoO
2026-05-05 13:36:31 +08:00
parent 054685826a
commit c57b8f40ee
2 changed files with 685 additions and 607 deletions

View File

@@ -1,257 +1,28 @@
{% extends "ewoooc_base.html" %}
{% block title %}Agent 編排矩陣{% endblock %}
{% block title %}Agent 指揮矩陣{% endblock %}
{% block ewooo_content %}
<style>
.agent-hero,.agent-panel,.agent-table-shell{border:1px solid var(--obs-line);border-radius:26px;background:var(--obs-card);box-shadow:0 16px 38px rgba(70,46,28,.08)}
.agent-hero{padding:clamp(1.2rem,2.4vw,2rem);background:radial-gradient(circle at 12% 14%,rgba(201,100,66,.18),transparent 24rem),radial-gradient(circle at 88% 8%,rgba(79,111,143,.14),transparent 22rem),linear-gradient(135deg,rgba(255,248,239,.98),rgba(255,255,255,.74))}.agent-kicker{color:var(--obs-accent);font-size:.76rem;letter-spacing:.13em;text-transform:uppercase;font-weight:850}.agent-title{margin:.45rem 0 .25rem;font-family:Georgia,'Times New Roman',serif;font-size:clamp(2rem,4vw,3.8rem);letter-spacing:-.055em;line-height:.98}.agent-subtitle{color:var(--obs-muted);max-width:870px;line-height:1.7}.agent-filter{display:flex;gap:.55rem;flex-wrap:wrap;margin-top:1rem;padding:.8rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.58)}.agent-command{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;margin-top:1rem}.agent-signal{padding:.95rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.62)}.agent-label{color:var(--obs-muted);font-size:.72rem;letter-spacing:.1em;text-transform:uppercase}.agent-value{display:block;margin-top:.28rem;font-size:clamp(1.45rem,2.8vw,2.35rem);font-weight:880;letter-spacing:-.045em}.agent-grid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(330px,.8fr);gap:1rem;margin-top:1rem}.agent-stack{display:grid;gap:1rem}.agent-panel-head,.agent-table-title{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;padding:1.05rem 1.1rem .25rem}.agent-panel-title,.agent-table-title h3{margin:.15rem 0 0;font-size:1.1rem;font-weight:850;letter-spacing:-.025em}.agent-panel-body{padding:1rem 1.1rem 1.1rem}.agent-table-shell{overflow:hidden;margin-top:1rem}.agent-card{padding:.9rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.58);margin-bottom:.75rem}.agent-card-top{display:flex;justify-content:space-between;gap:.8rem;align-items:start}.agent-meter{height:7px;border-radius:999px;background:rgba(86,64,48,.1);overflow:hidden;margin-top:.65rem}.agent-meter span{display:block;height:100%;background:var(--obs-accent)}.rec-card{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58);margin-bottom:.7rem}.status-good{color:var(--obs-green)}.status-warn{color:var(--obs-amber)}.status-bad{color:var(--obs-red)}.status-blue{color:var(--obs-blue)}@media(max-width:1100px){.agent-command{grid-template-columns:repeat(2,minmax(0,1fr))}.agent-grid{grid-template-columns:1fr}}@media(max-width:720px){.agent-command{grid-template-columns:1fr}}
</style>
<div class="container-fluid mt-3">
<h2 class="mb-3"><i class="fas fa-network-wired me-2"></i>Agent 編排矩陣
<small class="text-muted">{{ hours }}h 內 4 Agent × Ollama × Gemini × MCP × RAG 協作全景</small>
</h2>
<section class="agent-hero"><div class="agent-kicker"><i class="fas fa-network-wired me-1"></i> Agent Command Matrix · {{ hours }}h Window</div><h1 class="agent-title">Agent 指揮矩陣</h1><p class="agent-subtitle">這頁回答 AI 中樞如何分工:誰在用 Ollama、誰還在吃付費 LLM、哪些 Agent 有 RAG 命中、哪些工作流已經接上 MCP。這不是列表是指揮官視角。</p><form method="get" class="agent-filter"><select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">{% 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></form>{% if overall %}<div class="agent-command"><div class="agent-signal"><div class="agent-label">Total Calls</div><span class="agent-value">{{ "{:,}".format(overall.total_calls) }}</span><small class="text-muted">{{ "{:,}".format(overall.total_tokens) }} tokens</small></div><div class="agent-signal"><div class="agent-label">Ollama Share</div><span class="agent-value status-good">{{ "%.0f"|format(overall.local_pct) }}%</span><small class="text-muted">{{ "{:,}".format(overall.local_calls) }} local calls</small></div><div class="agent-signal"><div class="agent-label">Paid Cost</div><span class="agent-value {% if overall.total_cost > 0 %}status-warn{% else %}status-good{% endif %}">${{ "%.2f"|format(overall.total_cost) }}</span><small class="text-muted">{{ "{:,}".format(overall.paid_calls) }} paid calls</small></div><div class="agent-signal"><div class="agent-label">RAG Rate</div><span class="agent-value status-blue">{{ "%.0f"|format(overall.rag_rate) }}%</span><small class="text-muted">{{ "{:,}".format(overall.rag_hits) }} hits</small></div></div>{% endif %}</section>
{% if error %}<div class="alert alert-warning mt-3"><strong><i class="fas fa-triangle-exclamation me-1"></i></strong>{{ error }}</div>{% endif %}
{% if error %}
<div class="alert alert-warning"><strong><i class="fas fa-exclamation-triangle me-1"></i></strong> {{ error }}</div>
{% endif %}
<section class="agent-grid">
<div class="agent-stack">
<article class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">4 Agent Matrix</div><h3>LLM × MCP × RAG 編排矩陣</h3></div></div><div class="table-responsive"><table class="table mb-0"><thead class="table-light"><tr><th>Agent</th><th class="text-end">呼叫</th><th class="text-end">成本</th><th class="text-end">Ollama</th><th class="text-end">付費</th><th class="text-end">MCP</th><th class="text-end">RAG</th><th class="text-end">錯誤</th><th class="text-end">耗時</th></tr></thead><tbody>{% for ag in agent_matrix %}<tr><td><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></td><td class="text-end">{% if ag.calls > 0 %}<strong>{{ "{:,}".format(ag.calls) }}</strong><small class="d-block text-muted">{{ "{:,}".format(ag.tokens) }} tk</small>{% else %}<small class="text-muted"></small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}${{ "%.2f"|format(ag.cost) }}{% else %}<small class="text-muted"></small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-good">{{ "%.0f"|format(ag.ollama_pct) }}%</strong><small class="d-block text-muted">A {{ ag.ollama_gcp_a }} · B {{ ag.ollama_gcp_b }} · 111 {{ ag.ollama_111 }}</small>{% else %}<small class="text-muted"></small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.paid_pct > 50 %}status-bad{% elif ag.paid_pct > 20 %}status-warn{% endif %}">{{ "%.0f"|format(ag.paid_pct) }}%</strong><small class="d-block text-muted">Gemini {{ ag.gemini }}{% if ag.other_paid %} · 其他 {{ ag.other_paid }}{% endif %}</small>{% else %}<small class="text-muted"></small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.mcp_rate >= 30 %}status-blue{% elif ag.mcp_rate >= 10 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(ag.mcp_rate) }}%</strong><small class="d-block text-muted">{{ ag.mcp_calls }}</small>{% else %}<small class="text-muted"></small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-blue">{{ "%.1f"|format(ag.rag_rate) }}%</strong><small class="d-block text-muted">{{ ag.rag_hits }}</small>{% else %}<small class="text-muted"></small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.error_rate >= 15 %}status-bad{% elif ag.error_rate >= 5 %}status-warn{% else %}status-good{% endif %}">{{ "%.1f"|format(ag.error_rate) }}%</strong><small class="d-block text-muted">{{ ag.errors }}</small>{% else %}<small class="text-muted"></small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}{{ ag.avg_ms }} ms{% else %}<small class="text-muted"></small>{% endif %}</td></tr>{% endfor %}</tbody></table></div></article>
</div>
<aside class="agent-stack">
<article class="agent-panel"><div class="agent-panel-head"><div><div class="agent-label">Agent Cards</div><h2 class="agent-panel-title">分工健康速覽</h2></div></div><div class="agent-panel-body">{% for ag in agent_matrix %}<div class="agent-card"><div class="agent-card-top"><div><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></div><span class="badge {% if ag.error_rate >= 15 %}bg-danger{% elif ag.calls == 0 %}bg-secondary{% else %}bg-success{% endif %}">{{ ag.calls }} calls</span></div><div class="agent-meter"><span style="width:{{ ag.ollama_pct|round|int if ag.calls > 0 else 0 }}%"></span></div><small class="text-muted">Ollama {{ "%.0f"|format(ag.ollama_pct) if ag.calls > 0 else 0 }}% · RAG {{ "%.0f"|format(ag.rag_rate) if ag.calls > 0 else 0 }}% · MCP {{ "%.0f"|format(ag.mcp_rate) if ag.calls > 0 else 0 }}%</small></div>{% endfor %}</div></article>
</aside>
</section>
<!-- 時間範圍 -->
<form method="get" class="row g-2 mb-3">
<div class="col-auto">
<select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">
{% 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>
</form>
<!-- 整體 KPI -->
{% if overall %}
<div class="row g-3 mb-3">
<div class="col-lg-3 col-md-6">
<div class="card h-100">
<div class="card-body">
<small class="text-muted d-block"><i class="fas fa-network-wired me-1"></i>總呼叫</small>
<h3 class="mb-0">{{ "{:,}".format(overall.total_calls) }}</h3>
<small class="text-muted">Token{{ "{:,}".format(overall.total_tokens) }}</small>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card h-100" style="border-left: 4px solid #198754;">
<div class="card-body">
<small class="text-muted d-block"><i class="fas fa-server me-1"></i>本地 Ollama 比例</small>
<h3 class="mb-0 text-success">{{ "%.0f"|format(overall.local_pct) }}<small>%</small></h3>
<small class="text-muted">{{ "{:,}".format(overall.local_calls) }} 呼叫</small>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card h-100" style="border-left: 4px solid #ffc107;">
<div class="card-body">
<small class="text-muted d-block"><i class="fas fa-coins me-1"></i>付費 LLM 成本</small>
<h3 class="mb-0">${{ "%.2f"|format(overall.total_cost) }}</h3>
<small class="text-muted">{{ "{:,}".format(overall.paid_calls) }} 付費呼叫({{ "%.0f"|format(overall.paid_pct) }}%</small>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card h-100" style="border-left: 4px solid #6f42c1;">
<div class="card-body">
<small class="text-muted d-block"><i class="fas fa-magnifying-glass-chart me-1"></i>RAG 命中率</small>
<h3 class="mb-0" style="color: #6f42c1;">{{ "%.0f"|format(overall.rag_rate) }}<small>%</small></h3>
<small class="text-muted">{{ "{:,}".format(overall.rag_hits) }} hits</small>
</div>
</div>
</div>
</div>
{% endif %}
<!-- 4 Agent 矩陣 -->
<div class="card mb-3">
<div class="card-header">
<strong><i class="fas fa-th me-2"></i>4 Agent × LLM × MCP × RAG 矩陣</strong>
<small class="text-muted">資料來源ai_calls × mcp_calls × rag_query_logcaller 自動分組)</small>
</div>
<div class="card-body p-0">
<table class="table mb-0" style="font-size: 0.92em;">
<thead class="table-light">
<tr>
<th>Agent</th>
<th class="text-end">呼叫</th>
<th class="text-end">成本</th>
<th class="text-end">本地 Ollama</th>
<th class="text-end">付費 LLM</th>
<th class="text-end">MCP 編排</th>
<th class="text-end">RAG 命中</th>
<th class="text-end">錯誤率</th>
<th class="text-end">耗時</th>
</tr>
</thead>
<tbody>
{% for ag in agent_matrix %}
<tr>
<td>
<strong>{{ ag.label }}</strong>
<small class="d-block text-muted">{{ ag.desc }}</small>
</td>
<td class="text-end">
{% if ag.calls > 0 %}
<strong>{{ "{:,}".format(ag.calls) }}</strong>
<small class="d-block text-muted">{{ "{:,}".format(ag.tokens) }} tk</small>
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">
{% if ag.calls > 0 %}
${{ "%.2f"|format(ag.cost) }}
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">
{% if ag.calls > 0 %}
<strong class="text-success">{{ "%.0f"|format(ag.ollama_pct) }}%</strong>
<small class="d-block text-muted">
GCP-A {{ ag.ollama_gcp_a }} ·
GCP-B {{ ag.ollama_gcp_b }} ·
111 {{ ag.ollama_111 }}
</small>
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">
{% if ag.calls > 0 %}
<strong class="{% if ag.paid_pct > 50 %}text-danger{% elif ag.paid_pct > 20 %}text-warning{% endif %}">
{{ "%.0f"|format(ag.paid_pct) }}%
</strong>
<small class="d-block text-muted">
Gemini {{ ag.gemini }}{% if ag.other_paid %} · 其他 {{ ag.other_paid }}{% endif %}
</small>
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">
{% if ag.calls > 0 %}
<strong class="{% if ag.mcp_rate >= 30 %}text-info{% elif ag.mcp_rate >= 10 %}text-warning{% else %}text-muted{% endif %}">
{{ "%.1f"|format(ag.mcp_rate) }}%
</strong>
<small class="d-block text-muted">{{ ag.mcp_calls }} request_id</small>
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">
{% if ag.calls > 0 %}
<strong style="color: #6f42c1;">{{ "%.1f"|format(ag.rag_rate) }}%</strong>
<small class="d-block text-muted">{{ ag.rag_hits }} hits</small>
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">
{% if ag.calls > 0 %}
<strong class="{% if ag.error_rate >= 15 %}text-danger{% elif ag.error_rate >= 5 %}text-warning{% else %}text-success{% endif %}">
{{ "%.1f"|format(ag.error_rate) }}%
</strong>
<small class="d-block text-muted">{{ ag.errors }} 次</small>
{% else %}
<small class="text-muted"></small>
{% endif %}
</td>
<td class="text-end">
{% if ag.calls > 0 %}{{ ag.avg_ms }} ms{% else %}<small class="text-muted"></small>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer small text-muted">
<i class="fas fa-info-circle me-1"></i>
<strong>本地 Ollama</strong> = GCP-A + GCP-B + 111 三主機級聯(免費);
<strong>付費 LLM</strong> = Gemini / Claude / NIM / OpenRouter
<strong>MCP 編排率</strong> = caller 透過 ai_calls.request_id 串接到 mcp_calls 的比例;
<strong>RAG 命中</strong> = ai_calls.rag_hit=true 的 caller-level 比例。
</div>
</div>
<!-- 自動編排建議 -->
{% if recommendations %}
<div class="card mb-3" style="border-left: 4px solid #6f42c1;">
<div class="card-header bg-light">
<strong><i class="fas fa-lightbulb me-2"></i>編排策略自動建議</strong>
<small class="text-muted">— rule-based 規則引擎5 條判斷</small>
</div>
<div class="card-body p-2">
<ul class="list-unstyled mb-0">
{% for r in recommendations %}
<li class="mb-2 p-2" style="background: #fafafa; border-radius: 6px;
{% if r.severity == 'high' %}border-left: 3px solid #dc3545;
{% elif r.severity == 'med' %}border-left: 3px solid #ffc107;
{% else %}border-left: 3px solid #0dcaf0;{% endif %}">
<span class="badge {% if r.severity == 'high' %}bg-danger{% elif r.severity == 'med' %}bg-warning text-dark{% else %}bg-info text-dark{% endif %} me-1">{{ r.severity|upper }}</span>
<strong>{{ r.agent }}</strong>
<div class="small mt-1">
<i class="fas fa-search me-1"></i><strong>發現:</strong>{{ r.finding }}
</div>
<div class="small text-muted">
<i class="fas fa-arrow-right me-1"></i><strong>建議:</strong>{{ r.suggestion }}
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<!-- MCP server × caller 細節 -->
{% if mcp_matrix %}
<div class="card mb-3">
<div class="card-header">
<strong><i class="fas fa-bolt me-2"></i>MCP server × caller 工作量明細</strong>
<small class="text-muted">資料來源mcp_calls過去 {{ hours }}h前 30 筆)</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>MCP Server</th>
<th>呼叫端 (caller)</th>
<th class="text-end">tool 呼叫</th>
<th class="text-end">cache 命中</th>
<th class="text-end">cache 率</th>
<th class="text-end">成本</th>
</tr>
</thead>
<tbody>
{% for m in mcp_matrix %}
<tr>
<td><code>{{ m.server }}</code></td>
<td><code>{{ m.caller }}</code></td>
<td class="text-end">{{ "{:,}".format(m.calls) }}</td>
<td class="text-end">{{ m.cache_hits }}</td>
<td class="text-end">
<span class="{% if m.cache_rate >= 50 %}text-success{% elif m.cache_rate >= 20 %}text-warning{% endif %}">
{{ "%.0f"|format(m.cache_rate) }}%
</span>
</td>
<td class="text-end">${{ "%.4f"|format(m.cost) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<p class="text-muted mt-3"><small>
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 46 — Agent 編排矩陣
8 表跨 JOINai_calls × mcp_calls × rag_query_log × ai_insights × learning_episodes
× incidents × heal_logs × host_health_probes
</small></p>
{% if recommendations %}<section class="agent-panel mt-3"><div class="agent-panel-head"><div><div class="agent-label">Rules</div><h2 class="agent-panel-title">編排策略自動建議</h2></div></div><div class="agent-panel-body">{% for r in recommendations %}<div class="rec-card"><span class="badge {% if r.severity == 'high' %}bg-danger{% elif r.severity == 'med' %}bg-warning{% else %}bg-info{% endif %} me-1">{{ r.severity|upper }}</span><strong>{{ r.agent }}</strong><div class="small mt-1"><i class="fas fa-search me-1"></i><strong>發現:</strong>{{ r.finding }}</div><div class="small text-muted"><i class="fas fa-arrow-right me-1"></i><strong>建議:</strong>{{ r.suggestion }}</div></div>{% endfor %}</div></section>{% endif %}
{% if mcp_matrix %}<section class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">MCP Detail</div><h3>MCP server × caller 工作量</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>MCP Server</th><th>Caller</th><th class="text-end">tool 呼叫</th><th class="text-end">cache</th><th class="text-end">cache 率</th><th class="text-end">成本</th></tr></thead><tbody>{% for m in mcp_matrix %}<tr><td><code>{{ m.server }}</code></td><td><code>{{ m.caller }}</code></td><td class="text-end">{{ "{:,}".format(m.calls) }}</td><td class="text-end">{{ m.cache_hits }}</td><td class="text-end"><span class="{% if m.cache_rate >= 50 %}status-good{% elif m.cache_rate >= 20 %}status-warn{% endif %}">{{ "%.0f"|format(m.cache_rate) }}%</span></td><td class="text-end">${{ "%.4f"|format(m.cost) }}</td></tr>{% endfor %}</tbody></table></div></section>{% endif %}
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 — Agent 指揮矩陣</small></p>
</div>
{% endblock %}

File diff suppressed because it is too large Load Diff