| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>RAG 评估系统</title>
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
-
- body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- padding: 20px;
- }
-
- .container {
- max-width: 1200px;
- margin: 0 auto;
- }
-
- .header {
- text-align: center;
- color: white;
- margin-bottom: 30px;
- }
-
- .header h1 {
- font-size: 2.5rem;
- font-weight: 600;
- margin-bottom: 8px;
- text-shadow: 0 2px 4px rgba(0,0,0,0.1);
- }
-
- .header p {
- font-size: 1.1rem;
- opacity: 0.9;
- }
-
- .card {
- background: white;
- border-radius: 16px;
- box-shadow: 0 10px 40px rgba(0,0,0,0.15);
- padding: 30px;
- margin-bottom: 24px;
- }
-
- .card-title {
- font-size: 1.25rem;
- font-weight: 600;
- color: #1a1a2e;
- margin-bottom: 20px;
- padding-bottom: 12px;
- border-bottom: 2px solid #f0f0f5;
- }
-
- .form-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
- gap: 20px;
- }
-
- .form-group {
- display: flex;
- flex-direction: column;
- }
-
- .form-group.full-width {
- grid-column: 1 / -1;
- }
-
- .form-group label {
- font-size: 0.875rem;
- font-weight: 500;
- color: #4a4a6a;
- margin-bottom: 6px;
- }
-
- .form-group label .required {
- color: #e74c3c;
- margin-left: 2px;
- }
-
- .form-group input,
- .form-group select {
- padding: 12px 16px;
- border: 1.5px solid #e0e0e8;
- border-radius: 10px;
- font-size: 0.95rem;
- transition: all 0.2s ease;
- background: #fafafa;
- }
-
- .form-group input:focus,
- .form-group select:focus {
- outline: none;
- border-color: #667eea;
- background: white;
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
- }
-
- .form-group input::placeholder {
- color: #9999aa;
- }
-
- .btn {
- padding: 14px 32px;
- border: none;
- border-radius: 10px;
- font-size: 1rem;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s ease;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- }
-
- .btn-primary {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: white;
- }
-
- .btn-primary:hover {
- transform: translateY(-2px);
- box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
- }
-
- .btn-primary:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- transform: none;
- }
-
- .btn-secondary {
- background: #f0f0f5;
- color: #4a4a6a;
- }
-
- .btn-secondary:hover {
- background: #e5e5ed;
- }
-
- .button-group {
- display: flex;
- gap: 12px;
- margin-top: 20px;
- }
-
- .results-section {
- display: none;
- }
-
- .results-section.show {
- display: block;
- }
-
- .metrics-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
- gap: 16px;
- margin-bottom: 30px;
- }
-
- .metric-card {
- background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%);
- border-radius: 12px;
- padding: 20px;
- text-align: center;
- border: 1px solid #e8ecf8;
- }
-
- .metric-card .value {
- font-size: 2rem;
- font-weight: 700;
- color: #667eea;
- margin-bottom: 4px;
- }
-
- .metric-card .label {
- font-size: 0.85rem;
- color: #6a6a8a;
- text-transform: capitalize;
- }
-
- .chart-container {
- position: relative;
- height: 350px;
- margin-bottom: 20px;
- }
-
- .loading-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0,0,0,0.5);
- z-index: 1000;
- justify-content: center;
- align-items: center;
- }
-
- .loading-overlay.show {
- display: flex;
- }
-
- .loading-box {
- background: white;
- border-radius: 16px;
- padding: 40px 60px;
- text-align: center;
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
- }
-
- .spinner {
- width: 50px;
- height: 50px;
- border: 4px solid #f0f0f5;
- border-top-color: #667eea;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- margin: 0 auto 20px;
- }
-
- @keyframes spin {
- to { transform: rotate(360deg); }
- }
-
- .loading-text {
- font-size: 1.1rem;
- color: #4a4a6a;
- }
-
- .info-table {
- width: 100%;
- border-collapse: collapse;
- margin-top: 20px;
- }
-
- .info-table th,
- .info-table td {
- padding: 12px 16px;
- text-align: left;
- border-bottom: 1px solid #f0f0f5;
- }
-
- .info-table th {
- background: #f8f9fc;
- font-weight: 600;
- color: #4a4a6a;
- font-size: 0.85rem;
- }
-
- .info-table td {
- color: #2a2a4a;
- font-size: 0.9rem;
- }
-
- .info-table tr:hover {
- background: #fafbfd;
- }
-
- .badge {
- display: inline-block;
- padding: 4px 10px;
- border-radius: 20px;
- font-size: 0.75rem;
- font-weight: 500;
- }
-
- .badge-success {
- background: #d4edda;
- color: #155724;
- }
-
- .badge-info {
- background: #e7f1ff;
- color: #0056b3;
- }
-
- .error-message {
- background: #fff5f5;
- border: 1px solid #fed7d7;
- border-radius: 10px;
- padding: 16px 20px;
- color: #c53030;
- margin-top: 20px;
- display: none;
- }
-
- .error-message.show {
- display: block;
- }
-
- .charts-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 24px;
- }
-
- @media (max-width: 768px) {
- .charts-grid {
- grid-template-columns: 1fr;
- }
-
- .header h1 {
- font-size: 1.8rem;
- }
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>RAG 评估系统</h1>
- <p>基于 RAGAS 框架的 RAG 系统质量评估工具</p>
- </div>
-
- <div class="card">
- <div class="card-title">评估配置</div>
- <div class="form-grid">
- <div class="form-group full-width">
- <label>文件URL <span class="required">*</span></label>
- <input type="text" id="fileUrl" placeholder="请输入JSON文件的网络地址 (包含question和ground_truth)">
- </div>
-
- <div class="form-group full-width">
- <label>知识库ID <span class="required">*</span></label>
- <input type="text" id="knowledgeIds" placeholder="多个ID用英文逗号分隔,如: id1, id2, id3">
- </div>
-
- <div class="form-group">
- <label>嵌入模型</label>
- <select id="embeddingId">
- <option value="e5">E5 (默认)</option>
- <option value="multilingual-e5-large-instruct">Multilingual E5 Large</option>
- <option value="bge">BGE</option>
- </select>
- </div>
-
- <div class="form-group">
- <label>LLM 模型</label>
- <select id="model">
- <option value="Qwen3-Coder-30B-loft">Qwen3-Coder-30B-loft (默认)</option>
- <option value="deepseek-chat">DeepSeek Chat</option>
- </select>
- </div>
-
- <div class="form-group">
- <label>Temperature</label>
- <input type="number" id="temperature" value="0.6" min="0" max="2" step="0.1">
- </div>
-
- <div class="form-group">
- <label>Top P</label>
- <input type="number" id="topP" value="0.7" min="0" max="1" step="0.1">
- </div>
-
- <div class="form-group">
- <label>Max Tokens</label>
- <input type="number" id="maxTokens" value="4096" min="256" max="32768" step="256">
- </div>
-
- <div class="form-group">
- <label>检索切片数量</label>
- <input type="number" id="sliceCount" value="5" min="1" max="20">
- </div>
- </div>
-
- <div class="button-group">
- <button class="btn btn-primary" id="submitBtn" onclick="runEvaluation()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <polygon points="5 3 19 12 5 21 5 3"></polygon>
- </svg>
- 开始评估
- </button>
- <button class="btn btn-secondary" onclick="resetForm()">
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
- <path d="M3 3v5h5"></path>
- </svg>
- 重置
- </button>
- </div>
-
- <div class="error-message" id="errorMessage"></div>
- </div>
-
- <div class="results-section" id="resultsSection">
- <div class="card">
- <div class="card-title">评估结果概览</div>
- <div class="metrics-grid" id="metricsGrid"></div>
-
- <div class="charts-grid">
- <div>
- <h4 style="margin-bottom: 16px; color: #4a4a6a;">指标雷达图</h4>
- <div class="chart-container">
- <canvas id="radarChart"></canvas>
- </div>
- </div>
- <div>
- <h4 style="margin-bottom: 16px; color: #4a4a6a;">指标柱状图</h4>
- <div class="chart-container">
- <canvas id="barChart"></canvas>
- </div>
- </div>
- </div>
- </div>
-
- <div class="card">
- <div class="card-title">评估信息</div>
- <table class="info-table">
- <tbody id="infoTable"></tbody>
- </table>
- </div>
- </div>
- </div>
-
- <div class="loading-overlay" id="loadingOverlay">
- <div class="loading-box">
- <div class="spinner"></div>
- <div class="loading-text">正在评估中,请稍候...</div>
- <div style="font-size: 0.85rem; color: #999; margin-top: 8px;">评估可能需要几分钟时间</div>
- </div>
- </div>
-
- <script>
- let radarChart = null;
- let barChart = null;
-
- function showError(message) {
- const el = document.getElementById('errorMessage');
- el.textContent = message;
- el.classList.add('show');
- }
-
- function hideError() {
- document.getElementById('errorMessage').classList.remove('show');
- }
-
- function showLoading(show) {
- const overlay = document.getElementById('loadingOverlay');
- const btn = document.getElementById('submitBtn');
- if (show) {
- overlay.classList.add('show');
- btn.disabled = true;
- } else {
- overlay.classList.remove('show');
- btn.disabled = false;
- }
- }
-
- function resetForm() {
- document.getElementById('fileUrl').value = '';
- document.getElementById('knowledgeIds').value = '';
- document.getElementById('embeddingId').value = 'e5';
- document.getElementById('model').value = 'Qwen3-Coder-30B-loft';
- document.getElementById('temperature').value = '0.6';
- document.getElementById('topP').value = '0.7';
- document.getElementById('maxTokens').value = '4096';
- document.getElementById('sliceCount').value = '5';
- document.getElementById('resultsSection').classList.remove('show');
- hideError();
- }
-
- async function runEvaluation() {
- hideError();
-
- const fileUrl = document.getElementById('fileUrl').value.trim();
- const knowledgeIdsStr = document.getElementById('knowledgeIds').value.trim();
-
- if (!fileUrl) {
- showError('请输入文件URL');
- return;
- }
- if (!knowledgeIdsStr) {
- showError('请输入知识库ID');
- return;
- }
-
- const knowledgeIds = knowledgeIdsStr.split(',').map(s => s.trim()).filter(s => s);
-
- const payload = {
- file_url: fileUrl,
- knowledge_ids: knowledgeIds,
- embedding_id: document.getElementById('embeddingId').value,
- model: document.getElementById('model').value,
- temperature: parseFloat(document.getElementById('temperature').value),
- top_p: parseFloat(document.getElementById('topP').value),
- max_tokens: parseInt(document.getElementById('maxTokens').value),
- slice_count: parseInt(document.getElementById('sliceCount').value)
- };
-
- showLoading(true);
-
- try {
- const response = await fetch('/rag/evaluate', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
-
- const result = await response.json();
-
- if (result.code !== 200) {
- showError(result.message || '评估失败');
- return;
- }
-
- renderResults(result.data);
- document.getElementById('resultsSection').classList.add('show');
-
- } catch (err) {
- showError('请求失败: ' + err.message);
- } finally {
- showLoading(false);
- }
- }
-
- function renderResults(data) {
- const metrics = data.metrics || {};
- const knowledgeIds = data.knowledge_ids || [];
-
- // Render metric cards
- const grid = document.getElementById('metricsGrid');
- grid.innerHTML = '';
-
- const metricLabels = {
- 'faithfulness': '忠实度',
- 'answer_correctness': '答案正确性',
- 'answer_relevancy': '答案相关性',
- 'context_precision': '上下文精确度',
- 'context_recall': '上下文召回率',
- 'noise_sensitivity': '噪声敏感度',
- 'noise_sensitivity(mode=relevant)': '噪声敏感度',
- 'noise_sensitivity(mode=irrelevant)': '噪声敏感度(无关)'
- };
-
- for (const [key, value] of Object.entries(metrics)) {
- const card = document.createElement('div');
- card.className = 'metric-card';
- card.innerHTML = `
- <div class="value">${value !== null ? (value * 100).toFixed(1) + '%' : 'N/A'}</div>
- <div class="label">${metricLabels[key] || key}</div>
- `;
- grid.appendChild(card);
- }
-
- // Render charts
- renderCharts(metrics, metricLabels);
-
- // Render info table
- const infoTable = document.getElementById('infoTable');
- infoTable.innerHTML = `
- <tr>
- <th style="width: 180px;">知识库ID</th>
- <td>${knowledgeIds.map(id => `<span class="badge badge-info">${id}</span>`).join(' ')}</td>
- </tr>
- <tr>
- <th>评估数据量</th>
- <td>${data.count || 0} 条</td>
- </tr>
- <tr>
- <th>评估指标数</th>
- <td>${data.metric_names ? data.metric_names.length : 0} 个</td>
- </tr>
- <tr>
- <th>评估状态</th>
- <td><span class="badge badge-success">完成</span></td>
- </tr>
- `;
- }
-
- function renderCharts(metrics, metricLabels) {
- const labels = [];
- const values = [];
- const colors = [
- 'rgba(102, 126, 234, 0.8)',
- 'rgba(118, 75, 162, 0.8)',
- 'rgba(52, 152, 219, 0.8)',
- 'rgba(46, 204, 113, 0.8)',
- 'rgba(241, 196, 15, 0.8)',
- 'rgba(231, 76, 60, 0.8)',
- 'rgba(155, 89, 182, 0.8)',
- 'rgba(26, 188, 156, 0.8)'
- ];
-
- for (const [key, value] of Object.entries(metrics)) {
- labels.push(metricLabels[key] || key);
- values.push(value !== null ? value : 0);
- }
-
- // Destroy existing charts
- if (radarChart) radarChart.destroy();
- if (barChart) barChart.destroy();
-
- // Radar chart
- const radarCtx = document.getElementById('radarChart').getContext('2d');
- radarChart = new Chart(radarCtx, {
- type: 'radar',
- data: {
- labels: labels,
- datasets: [{
- label: '评估得分',
- data: values,
- backgroundColor: 'rgba(102, 126, 234, 0.2)',
- borderColor: 'rgba(102, 126, 234, 1)',
- borderWidth: 2,
- pointBackgroundColor: 'rgba(102, 126, 234, 1)',
- pointRadius: 4
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- scales: {
- r: {
- beginAtZero: true,
- max: 1,
- ticks: {
- stepSize: 0.2,
- callback: v => (v * 100) + '%'
- }
- }
- },
- plugins: {
- legend: { display: false }
- }
- }
- });
-
- // Bar chart
- const barCtx = document.getElementById('barChart').getContext('2d');
- barChart = new Chart(barCtx, {
- type: 'bar',
- data: {
- labels: labels,
- datasets: [{
- label: '评估得分',
- data: values,
- backgroundColor: colors.slice(0, values.length),
- borderRadius: 6
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- scales: {
- y: {
- beginAtZero: true,
- max: 1,
- ticks: {
- callback: v => (v * 100) + '%'
- }
- }
- },
- plugins: {
- legend: { display: false }
- }
- }
- });
- }
- </script>
- </body>
- </html>
|