feat(sense): T-G7 审计+前端(live 分流接线 + tier 指标卡 + Settings 灰度区块)
- proxy/routes:sense.mode=live 时转发前调 Grader 分流,档位 -> 池条目 (tier_pool_hint 映射),响应头 X-Campus-Tier 透出(可解释性); T1 决策按 review.sample_rate 抽样入 ReviewQueue(sense_t1 标签, 人工核"判 T1 实为更高档");分级故障不影响代理可用性(D-G4) - /api/metrics:sense 段(enabled/mode/观察数/已标签/档位分布/shadow 一致率) - 前端:MetricsView「语义分级」卡片(观察/标签进度/一致率/晋升门提示); SettingsView「Sense 灰度控制」区块(总开关 + collect/shadow/live 切换 + 标签进度),npm run build 产物更新 - 全量 410 passed
This commit is contained in:
+3
-3
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"schemaVersion": "mimosa-hook-status/v1",
|
||||
"recordedAt": "2026-09-01T14:21:11.564Z",
|
||||
"recordedAt": "2026-09-05T07:03:44.339Z",
|
||||
"sessionId": "sess_e50d4f25-3ac6-43f2-b2f3-8833b3150465",
|
||||
"event": "PostToolUse",
|
||||
"toolName": "Edit",
|
||||
"file": "src/views/AgentView.vue",
|
||||
"file": "src/views/MetricsView.vue",
|
||||
"outcome": "clear",
|
||||
"coverage": "complete",
|
||||
"findingCount": 0,
|
||||
"durationMs": 7,
|
||||
"durationMs": 2,
|
||||
"hostState": "hook_complete",
|
||||
"reportHint": ".mimosa/reports/"
|
||||
}
|
||||
|
||||
@@ -114,4 +114,13 @@ export interface Metrics {
|
||||
cache: Record<string, unknown>
|
||||
v2?: Record<string, unknown>
|
||||
review?: { pending: number; total: number }
|
||||
sense?: {
|
||||
enabled: boolean
|
||||
mode: string
|
||||
observations: number
|
||||
labeled: number
|
||||
by_decided_tier?: Record<string, number>
|
||||
agreement?: number
|
||||
min_labels?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,21 @@
|
||||
<p class="hint">单价来自模型池条目($/1M tokens);经典设置下的模型成本不计入。</p>
|
||||
</div>
|
||||
|
||||
<div v-if="sense && sense.observations" class="metric-card">
|
||||
<h3>语义分级(sense · {{ sense.mode }})</h3>
|
||||
<div class="kv-list">
|
||||
<span>观察数</span><b>{{ sense.observations }}</b>
|
||||
<span>已标签</span><b>{{ sense.labeled }} / {{ sense.min_labels }}</b>
|
||||
<span>一致率</span><b>{{ ((sense.agreement ?? 0) * 100).toFixed(1) }}%</b>
|
||||
<template v-for="(v, k) in sense.by_decided_tier" :key="k">
|
||||
<span>判 {{ k }}</span><b>{{ v }}</b>
|
||||
</template>
|
||||
</div>
|
||||
<p class="hint">
|
||||
晋升门:一致率 ≥85% + 标签 ≥{{ sense.min_labels }} + 审计无误判(collect 攒满前不开 live)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="data.review" class="metric-card review-card">
|
||||
<h3>人工检验</h3>
|
||||
<div class="review-stats">
|
||||
@@ -115,6 +130,8 @@ const byModel = computed(() => {
|
||||
return (v2?.by_model as Record<string, ModelBucket>) || null
|
||||
})
|
||||
|
||||
const sense = computed(() => data.value?.sense || null)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
@@ -389,6 +389,45 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 🧠 语义分级(sense)灰度控制 -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">
|
||||
<span>🧠 语义分级(Sense · 三级任务分级)</span>
|
||||
<div class="title-right">
|
||||
<span :class="['server-badge', senseCfg.enabled ? 'badge-running' : 'badge-stopped']">
|
||||
{{ senseCfg.enabled ? `● ${senseCfg.mode}` : '○ 未启用' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-body">
|
||||
<p class="role-hint">
|
||||
灰度纪律:collect(只记不决策)攒满 {{ senseCfg.min_labels }} 条标签并过晋升门
|
||||
(一致率 ≥85% + conformal 覆盖 ≤α+2% + 审计无误判)后才可开 live。
|
||||
</p>
|
||||
<div class="form-grid">
|
||||
<div class="field">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="senseCfg.enabled" @change="saveSense" />
|
||||
启用语义分析器(总开关,改后保存生效)
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>灰度模式</label>
|
||||
<select v-model="senseCfg.mode" @change="saveSense"
|
||||
:disabled="!senseCfg.enabled">
|
||||
<option value="collect">collect — 只采集观察</option>
|
||||
<option value="shadow">shadow — 决策只比对不执行</option>
|
||||
<option value="live">live — 真分流(需晋升门通过)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>观察/标签数(min_labels={{ senseCfg.min_labels }})</label>
|
||||
<input :value="`${senseCfg.obs} / ${senseCfg.labeled}`" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="actions">
|
||||
<button class="btn-primary" :disabled="saving || !dirty" @click="save">
|
||||
@@ -412,6 +451,7 @@ import {
|
||||
getLlamaStatus, listLocalModels, startLlama, stopLlama,
|
||||
downloadModel, watchDownload,
|
||||
getPool, upsertPoolEntry, deletePoolEntry, setPoolRoles, testPoolEntry, listPoolModels,
|
||||
getMetrics,
|
||||
} from '@/api'
|
||||
import type {
|
||||
ModelSettings, ModelInfo, LlamaStatus, LocalModel,
|
||||
@@ -961,7 +1001,34 @@ async function reset() {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
// 语义分级(sense)灰度
|
||||
const senseCfg = reactive({ enabled: false, mode: 'collect', obs: 0, labeled: 0, min_labels: 500 })
|
||||
|
||||
async function loadSense() {
|
||||
try {
|
||||
const [cfg, m] = await Promise.all([getConfig(), getMetrics()])
|
||||
const s = (cfg as any).sense || {}
|
||||
senseCfg.enabled = !!s.enabled
|
||||
senseCfg.mode = s.mode || 'collect'
|
||||
senseCfg.min_labels = s.policy?.min_labels ?? 500
|
||||
const sm = (m as any).sense || {}
|
||||
senseCfg.obs = sm.observations ?? 0
|
||||
senseCfg.labeled = sm.labeled ?? 0
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
|
||||
async function saveSense() {
|
||||
try {
|
||||
await updateConfig({ sense: { enabled: senseCfg.enabled, mode: senseCfg.mode } } as any)
|
||||
saveMsg.value = { ok: true, msg: `✅ 语义分级已${senseCfg.enabled ? '启用(' + senseCfg.mode + ')' : '关闭'}` }
|
||||
} catch (e: any) {
|
||||
saveMsg.value = { ok: false, msg: '❌ 保存失败:' + (e?.response?.data?.detail || e?.message || e) }
|
||||
await loadSense()
|
||||
}
|
||||
setTimeout(() => (saveMsg.value = null), 3000)
|
||||
}
|
||||
|
||||
onMounted(() => { load(); loadSense() })
|
||||
onUnmounted(() => { _dlSSE?.close() })
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user