#!/usr/bin/env node 'use strict'; /** * 验证 RAG + 引用:创建临时 AI Assistant → 调 preview API 问一个问题 → 打印命中来源 → 删除。 * 用法:node scripts/verify-rag.js "What is the minimum order value?" */ const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const question = process.argv.slice(2).join(' ') || 'What is the minimum order value?'; function loadEnv(file) { const out = {}; for (const line of fs.readFileSync(file, 'utf8').split('\n')) { const t = line.trim(); if (!t || t.startsWith('#')) continue; const eq = t.indexOf('='); if (eq < 0) continue; let v = t.slice(eq + 1).trim(); if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1); out[t.slice(0, eq).trim()] = v; } return out; } function createClient(baseURL) { const cookies = new Map(); let csrf = ''; const cookieHeader = () => [...cookies.entries()].map(([k, v]) => `${k}=${v}`).join('; '); function store(res) { const list = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [res.headers.get('set-cookie')].filter(Boolean); for (const c of list) { const [pair] = c.split(';'); const eq = pair.indexOf('='); if (eq > 0) { const k = pair.slice(0, eq).trim(); const v = pair.slice(eq + 1).trim(); cookies.set(k, v); if (k === 'csrf_token') csrf = v; } } } async function req(method, p, body) { const headers = { Accept: 'application/json' }; if (cookies.size) headers.Cookie = cookieHeader(); if (body !== undefined && !(body instanceof FormData)) headers['Content-Type'] = 'application/json'; if (csrf && method !== 'GET') headers['X-CSRFTOKEN'] = csrf; const res = await fetch(baseURL + p, { method, headers, body: body === undefined ? undefined : (body instanceof FormData ? body : JSON.stringify(body)), }); store(res); const text = await res.text(); let json = null; try { json = text ? JSON.parse(text) : null; } catch { /* ignore */ } if (!res.ok) throw new Error(`${method} ${p} -> HTTP ${res.status}: ${(json && json.message) || text.slice(0, 400)}`); return json; } return { get: (p) => req('GET', p), post: (p, b) => req('POST', p, b), del: (p) => req('DELETE', p) }; } async function main() { const env = loadEnv(path.join(ROOT, '.env')); const base = (env.LIBREDESK_BASE_URL || 'http://192.168.31.61:9000').replace(/\/+$/, ''); const c = createClient(base); await c.post('/api/v1/auth/login', { email: env.LIBREDESK_SYSTEM_EMAIL || 'System', password: env.LIBREDESK_SYSTEM_USER_PASSWORD }); const data = { name: `KB Verify ${Date.now()}`, description: 'temporary assistant for RAG verification', instructions: 'Answer from the knowledge base. If the knowledge base has no answer, say you could not find it.', guardrails: '', expectation: '', tone: 'professional', response_length: 'balanced', max_turns: 5, fallback_team_id: null, citations_enabled: true, handoff_enabled: false, languages: [], enabled: true, tool_ids: [], }; const form = new FormData(); form.append('data', JSON.stringify(data)); const created = await c.post('/api/v1/ai/assistants', form); const assistant = created.data; console.log(`临时 Assistant #${assistant.id} 创建成功`); try { const preview = await c.post(`/api/v1/ai/assistants/${assistant.id}/preview`, { message: question }); const out = preview.data || {}; console.log(`\n问题: ${question}`); console.log(`回复:\n${out.reply}\n`); console.log('命中来源:'); for (const s of out.sources || []) { console.log(` - [${s.type}] #${s.id} score=${(s.score || 0).toFixed(3)} ${s.title || ''}${s.url ? ' ' + s.url : ''}`); } if (!(out.sources || []).length) console.log(' (无命中)'); } finally { await c.del(`/api/v1/ai/assistants/${assistant.id}`); console.log(`\n临时 Assistant #${assistant.id} 已删除`); } } main().catch((err) => { console.error('[verify-rag]', err.message); process.exit(1); });