From f82343949626454e90dada016f7138b9fe533b96 Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 26 Jun 2026 19:10:12 +0800 Subject: [PATCH] fix: hide caller keys in observability UI --- config.py | 2 +- docs/AI_INTELLIGENCE_MODULE_SOT.md | 3 ++- routes/admin_observability_routes.py | 2 +- templates/admin/quality_trend.html | 8 ++++---- templates/admin/rag_queries.html | 9 +++++---- tests/test_frontend_v2_assets.py | 17 +++++++++++++++++ 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/config.py b/config.py index bc496ff..0dbdc1b 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.717" +SYSTEM_VERSION = "V10.718" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index be4de98..88a96b6 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -2,7 +2,7 @@ > **最後更新**: 2026-06-26 (台北時間) > **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口、高可見頁面繁中化守門、比價/作戰 UI 工作台化、跨平台來源治理與商品身份 UI 契約已建立,GCP embedding 熔斷延後處理、110 proxy rescue 與 direct host health skip 已建立 -> **適用版本**: V10.717 +> **適用版本**: V10.718 --- @@ -802,3 +802,4 @@ POSTGRES_HOST=momo-db | 2026-06-26 | AI 銷售建議前端不得保留 raw content 路徑 | V10.715 起 `/ai_recommend` 只使用已整理的外部訊號摘要與可用線索;搜尋或商品洞察結果若未整理成結構,前端只顯示可理解的待整理狀態,不再把 raw content 當上下文或 fallback 分支保留。 | | 2026-06-26 | 首頁覆核候選必須標示為待確認商品 | V10.716 起首頁 PChome 覆核區不再使用「PChome 候選」作為可見主語,統一改為「PChome 待確認商品」與「開 PChome 待確認商品」,避免使用者把未確認同款誤認為正式比價結果。 | | 2026-06-26 | 供貨風險頁不得使用資料表或英文模組名作為主語 | V10.717 起缺貨清單與補貨通知頁統一使用「供貨風險、缺貨處理清單、補貨通知紀錄」等營運語言,不再顯示「缺貨資料表、缺貨資料、Vendor Stockout」等資料庫或英文模組感文案。 | +| 2026-06-26 | AI 觀測頁不得外露 caller key | V10.718 起 AI 品質診斷與知識召回頁使用「使用情境」作為可見主語,並透過 `obs_label.caller()` 顯示營運名稱;前台不得直接顯示 `{{ caller }}`、`top_k` 或「全部呼叫端」等工程語言。 | diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index cdbfaf9..c059882 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -588,7 +588,7 @@ def rag_queries_dashboard(): 'queried_at': r[1].strftime('%Y-%m-%d %H:%M:%S') if r[1] else '', 'caller': r[2], 'query_text': r[3] or '', - 'top_k': int(r[4] or 0), + 'take_count': int(r[4] or 0), 'threshold': round(float(r[5] or 0), 3), 'hit_count': int(r[6] or 0), 'used_results': used_ids, diff --git a/templates/admin/quality_trend.html b/templates/admin/quality_trend.html index ff4d289..53b7951 100644 --- a/templates/admin/quality_trend.html +++ b/templates/admin/quality_trend.html @@ -18,12 +18,12 @@ {% set rag_total = (rag_overall_dist | sum(attribute='count')) if rag_overall_dist else 0 %}
-
品質診斷 · {{ days }} 日視窗

AI 品質診斷台

用反饋、知識分數與行動閉環檢查 AI 建議是否可靠。

反饋總量
{{ total_feedback.value }}呼叫端反饋總量
最差均分
{{ "%.2f"|format(worst_avg.value) }}最差呼叫端平均分
蒸餾樣本
{{ episode_total }}蒸餾池 {{ days }} 日
知識評分
{{ rag_total }}已回饋知識查詢
+
品質診斷 · {{ days }} 日視窗

AI 品質診斷台

用反饋、知識分數與行動閉環檢查 AI 建議是否可靠。

反饋總量
{{ total_feedback.value }}使用情境反饋總量
最低均分
{{ "%.2f"|format(worst_avg.value) }}最低使用情境平均分
蒸餾樣本
{{ episode_total }}蒸餾池 {{ days }} 日
知識評分
{{ rag_total }}已回饋知識查詢
{% if error %}
{{ error }}
{% 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 %}
無反饋資料
+
使用情境反饋

使用情境反饋分佈

{% for caller, info in trends %}{% else %}{% endfor %}
使用情境平均倒讚總數趨勢分布
{{ obs_label.caller(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 %}
行動計畫

行動計畫狀態分布

{% for a in action_plans_status %}{% endfor %}
狀態計畫類型數量
{{ obs_label.status(a.status) }}{{ obs_label.plan_type(a.plan_type) }}{{ a.count }}
{% endif %}
- {% if rag_root_causes %}
根因分析

知識根因建議

{% for rc in rag_root_causes %}
{{ rc.caller }}{{ "%.2f"|format(rc.avg_score) }}/5{{ rc.feedback_n }} 筆
    {% for h in rc.hits %}
  • {{ obs_label.insight(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 %} + {% if rag_root_causes %}
根因分析

知識根因建議

{% for rc in rag_root_causes %}
{{ obs_label.caller(rc.caller) }}{{ "%.2f"|format(rc.avg_score) }}/5{{ rc.feedback_n }} 筆
    {% for h in rc.hits %}
  • {{ obs_label.insight(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 %}{{ obs_label.caller(rec.caller) }}:{{ rec.reason }}
  • {% endfor %}
{% endif %} {% if action_outcomes_stats %}
動作成效

實際動作成效

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

AI 品質診斷台

diff --git a/templates/admin/rag_queries.html b/templates/admin/rag_queries.html index 1419cc9..9ceac6b 100644 --- a/templates/admin/rag_queries.html +++ b/templates/admin/rag_queries.html @@ -15,23 +15,24 @@ {% set total = summary.total if summary else 0 %} +{% import "admin/_observability_labels.html" as obs_label %}
知識召回雷達 · {{ hours }} 小時視窗

知識召回雷達

追蹤知識是否命中、是否省下模型呼叫,避免業績建議缺少根據。

-
- {% if summary and summary.total > 0 %}
查詢數
{{ "{:,}".format(summary.total) }}
{{ summary.distinct_callers }} 個呼叫端
命中率
{{ "%.1f"|format(summary.hit_rate) }}%
{{ summary.with_hits }} 次命中 · {{ summary.no_hits }} 次未命中
省下呼叫
{{ "%.1f"|format(summary.saved_rate) }}%
{{ summary.saved }} 次省下 LLM
反饋分
{{ "%.2f"|format(summary.avg_score) }}
{{ summary.feedback_count }} 筆 · 平均 {{ summary.avg_hits }} 次命中
{% endif %} +
+ {% if summary and summary.total > 0 %}
查詢數
{{ "{:,}".format(summary.total) }}
{{ summary.distinct_callers }} 個使用情境
命中率
{{ "%.1f"|format(summary.hit_rate) }}%
{{ summary.with_hits }} 次命中 · {{ summary.no_hits }} 次未命中
省下模型
{{ "%.1f"|format(summary.saved_rate) }}%
{{ summary.saved }} 次省下模型呼叫
反饋分
{{ "%.2f"|format(summary.avg_score) }}
{{ summary.feedback_count }} 筆 · 平均 {{ summary.avg_hits }} 次命中
{% endif %}
{% if error %}
{{ error }}
{% endif %}
-
查詢串流

最近 50 筆查詢詳情

{% if queries %}{% for q in queries %}{% endfor %}
時間呼叫端查詢top_k門檻命中已省下反饋動作
{{ 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 %}已省下{% else %}{% endif %}{% if q.feedback_score is not none %}{{ q.feedback_score }}/5{% else %}{% endif %}{% if q.hit_count > 0 %}{% endif %}
{% else %}
過去 {{ hours }} 小時無符合條件的知識查詢紀錄。
{% endif %}
+
查詢串流

最近 50 筆查詢詳情

{% if queries %}{% for q in queries %}{% endfor %}
時間使用情境查詢取用數門檻命中已省下反饋動作
{{ q.queried_at }}{{ obs_label.caller(q.caller) }}{{ q.query_text }}{% if q.query_text|length >= 200 %}…{% endif %}{{ q.take_count }}{{ q.threshold }}{% if q.hit_count > 0 %}{{ q.hit_count }}{% else %}0{% endif %}{% if q.saved_call %}已省下{% else %}{% endif %}{% if q.feedback_score is not none %}{{ q.feedback_score }}/5{% else %}{% endif %}{% if q.hit_count > 0 %}{% endif %}
{% else %}
過去 {{ hours }} 小時無符合條件的知識查詢紀錄。
{% endif %}
diff --git a/tests/test_frontend_v2_assets.py b/tests/test_frontend_v2_assets.py index 89a266c..9dbf740 100644 --- a/tests/test_frontend_v2_assets.py +++ b/tests/test_frontend_v2_assets.py @@ -709,6 +709,8 @@ def test_utility_pages_keep_operator_copy_professional(): stockout_list = (ROOT / "templates/vendor_stockout_list_v2.html").read_text(encoding="utf-8") stockout_send_email = (ROOT / "templates/vendor_stockout_send_email_v2.html").read_text(encoding="utf-8") stockout_vendor_management = (ROOT / "templates/vendor_stockout_vendor_management_v2.html").read_text(encoding="utf-8") + quality_trend = (ROOT / "templates/admin/quality_trend.html").read_text(encoding="utf-8") + rag_queries = (ROOT / "templates/admin/rag_queries.html").read_text(encoding="utf-8") ai_calls = (ROOT / "templates/admin/ai_calls_dashboard.html").read_text(encoding="utf-8") observability_labels = (ROOT / "templates/admin/_observability_labels.html").read_text(encoding="utf-8") host_health = (ROOT / "templates/admin/host_health.html").read_text(encoding="utf-8") @@ -730,6 +732,8 @@ def test_utility_pages_keep_operator_copy_professional(): stockout_list, stockout_send_email, stockout_vendor_management, + quality_trend, + rag_queries, ai_calls, observability_labels, host_health, @@ -761,6 +765,17 @@ def test_utility_pages_keep_operator_copy_professional(): assert "供應商窗口" in stockout_vendor_management assert "維護正確窗口" in stockout_vendor_management assert "Vendor Stockout" not in stockout_vendor_management + assert "使用情境反饋分佈" in quality_trend + assert "最低使用情境平均分" in quality_trend + assert "{{ caller }}" not in quality_trend + assert "{{ rc.caller }}" not in quality_trend + assert "{{ rec.caller }}" not in quality_trend + assert "全部使用情境" in rag_queries + assert "各使用情境知識表現" in rag_queries + assert "僅看已省下模型呼叫" in rag_queries + assert "top_k" not in rag_queries + assert "{{ q.caller }}" not in rag_queries + assert "{{ c.caller }}" not in rag_queries assert "使用情境" in ai_calls assert "全部使用情境" in ai_calls assert "情境 × 知識命中矩陣" in ai_calls @@ -807,6 +822,8 @@ def test_utility_pages_keep_operator_copy_professional(): "全部呼叫端", "呼叫端 ×", "{{ c.caller }}", + "{{ q.caller }}", + "{{ caller }}", "NIM Elephant", "OpenRouter", "GitLab 部署紀錄",