Files
2026-10-23 10:35:28 +08:00

83 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
'use strict';
// 离线“真实模型”评测:用 fake LibreDesk 客户端跑 createAgent,不写回真实会话。
// 依赖:LLM、旧 KB (127.0.0.1:5432/products)、商品 RPC 隧道。
const fs = require('fs');
const path = require('path');
const { loadConfig } = require('../bridge/src/config');
const { createAgent } = require('../bridge/src/agent');
const { createTools } = require('../bridge/src/tools');
const { createRetriever } = require('../bridge/src/rag');
const ROOT = path.resolve(__dirname, '..');
const env = {};
for (const line of fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')) {
if (!line.includes('=') || line.trim().startsWith('#')) continue;
const i = line.indexOf('=');
env[line.slice(0, i).trim()] = line.slice(i + 1).trim();
}
const password = encodeURIComponent(env.POSTGRES_PASSWORD || '');
env.PG_URL = `postgres://crmnew:${password}@127.0.0.1:5440/bridge`;
env.RAG_PG_URL = 'postgres://postgres:postgres@127.0.0.1:5432/products';
const cfg = loadConfig({ ...process.env, ...env });
const CASES = [
{ id: 'policy-min-order', message: 'What is the minimum order value?', contact: { type: 'contact', email: 'eval@example.com' }, expectTool: 'search_kb', contains: ['30'] },
{ id: 'product-hoops', message: 'Do you have hoop earrings?', contact: { type: 'contact', email: 'eval@example.com' }, expectTool: 'search_products', contains: ['yehwang.com'] },
{ id: 'product-zh-rings', message: '推荐一些简约不锈钢戒指', contact: { type: 'contact', email: 'eval@example.com' }, expectTool: 'search_products', contains: ['戒指'] },
{ id: 'anon-order-gate', message: 'Where is my order?', contact: { type: 'visitor' }, containsAny: ['email', 'verify', 'verification', '邮箱', '验证'] },
];
function fakeLeadStore() {
const memory = new Map();
return {
get: async (uuid) => memory.get(uuid) || null,
save: async (uuid, slots) => { memory.set(uuid, { slots }); return { slots }; },
remove: async (uuid) => { memory.delete(uuid); },
};
}
async function runCase(c) {
let reply = '';
let metric = null;
const tools = createTools(cfg, { retriever: createRetriever(cfg) });
tools.leadStore = fakeLeadStore();
const agent = createAgent(cfg, {
tools,
client: {
getMessages: async () => [{ id: 1, text_content: c.message, sender_type: 'contact', private: false }],
sendMessage: async (_uuid, content) => { reply = content; },
getConversation: async () => ({}),
},
verification: { getVerifiedEmail: async () => null, sendCode: async () => ({}), verifyCode: async () => ({}) },
metrics: { enabled: false, record: async (m) => { metric = m; } },
contextManager: { compressHistory: async (_c, h) => h },
llmCache: { enabled: false, get: async () => null, store: async () => false },
});
const conv = { uuid: `eval-${c.id}`, inbox_id: '1', channel: 'livechat', contact: c.contact };
await agent.run(conv, { uuid: `msg-${c.id}`, text_content: c.message, content: c.message });
return { reply, metric };
}
(async () => {
let failed = 0;
for (const c of CASES) {
try {
const { reply, metric } = await runCase(c);
const tools = (metric && metric.toolCalls || []).map((t) => t.name);
const okTool = !c.expectTool || tools.includes(c.expectTool);
const okContains = !c.contains || c.contains.every((s) => reply.includes(s));
const okAny = !c.containsAny || c.containsAny.some((s) => reply.toLowerCase().includes(s.toLowerCase()));
const ok = okTool && okContains && okAny;
if (!ok) failed += 1;
console.log(`${ok ? 'PASS' : 'FAIL'} ${c.id} tools=[${tools.join(',')}] reply=${reply.slice(0, 140).replace(/\n/g, ' ')}`);
} catch (err) {
failed += 1;
console.log(`FAIL ${c.id} error=${err.message}`);
}
}
console.log(`\n${CASES.length - failed}/${CASES.length} passed`);
process.exit(failed ? 1 : 0);
})().catch((err) => { console.error('[eval-live]', err.message); process.exit(1); });