feat(h5): web 联网能力、实时查询路由与 session Finish 对齐
- 新增 web 能力并挂载 platform/web(web_search/fetch_url) - 实时查询强制 web skill,router fallback 与 await session Finish - Session Broker 覆盖率/指标、stream replay 与相关单测/E2E 脚本 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env node
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
checkSessionBrokerCoverage,
|
||||
formatSessionBrokerCoverageViolations,
|
||||
SESSION_BROKER_PATCH2_FILES,
|
||||
} from '../session-broker-coverage.mjs';
|
||||
|
||||
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const violations = checkSessionBrokerCoverage(rootDir);
|
||||
|
||||
if (violations.length > 0) {
|
||||
console.error('session broker coverage check failed:\n');
|
||||
console.error(formatSessionBrokerCoverageViolations(violations));
|
||||
console.error('\nSession ownership must go through session-broker.mjs (createSessionAccess / resolveSessionAccess).');
|
||||
console.error(`Patch 2 modules: ${SESSION_BROKER_PATCH2_FILES.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`session broker coverage ok (patch2=${SESSION_BROKER_PATCH2_FILES.length}, facade=session-broker.mjs)`,
|
||||
);
|
||||
@@ -16,6 +16,10 @@ const testFiles = [
|
||||
'chat-finish-sync.test.mjs',
|
||||
'conversation-display.test.mjs',
|
||||
'mindspace-h5-html-finish-guard.test.mjs',
|
||||
'session-broker-coverage.test.mjs',
|
||||
'session-stream.test.mjs',
|
||||
'session-stream-store.test.mjs',
|
||||
'session-broker-metrics.test.mjs',
|
||||
];
|
||||
|
||||
function run(label, command, args) {
|
||||
@@ -31,6 +35,7 @@ function run(label, command, args) {
|
||||
}
|
||||
|
||||
run('unit tests', process.execPath, ['--test', ...testFiles]);
|
||||
run('session broker coverage', process.execPath, ['scripts/check-session-broker-coverage.mjs']);
|
||||
run('goosed proxy boundary', process.execPath, ['scripts/check-goosed-proxy-boundary.mjs']);
|
||||
run('mindspace publish guards', process.execPath, ['scripts/verify-mindspace-publish-guards.mjs']);
|
||||
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Full local E2E: john login → agent run → routing → session reply quality.
|
||||
* Usage: JOHN_PASSWORD=888888 node scripts/verify-world-cup-flow-e2e.mjs
|
||||
*/
|
||||
import crypto from 'node:crypto';
|
||||
import { loadH5Environment } from './load-env.mjs';
|
||||
import { createDbPool } from '../db.mjs';
|
||||
import { createUserAuth, USER_COOKIE } from '../user-auth.mjs';
|
||||
import {
|
||||
CHAT_INTENT_ROUTE,
|
||||
classifyWithRules,
|
||||
resolveChatIntentRouterPolicy,
|
||||
} from '../chat-intent-router.mjs';
|
||||
|
||||
loadH5Environment(import.meta.dirname);
|
||||
|
||||
const PORTAL = `http://127.0.0.1:${process.env.H5_PORT ?? 8081}`;
|
||||
const USERNAME = 'john';
|
||||
const PASSWORD = process.env.JOHN_PASSWORD ?? process.env.H5_ACCESS_PASSWORD ?? '888888';
|
||||
const QUERY = '世界杯现在赛况如何';
|
||||
const MAX_RUN_WAIT_MS = Number(process.env.E2E_RUN_WAIT_MS ?? 300_000);
|
||||
const MAX_REPLY_WAIT_MS = 120_000;
|
||||
|
||||
const issues = [];
|
||||
const checks = [];
|
||||
|
||||
function pass(label, detail = '') {
|
||||
checks.push({ ok: true, label, detail });
|
||||
console.log(`✔ ${label}${detail ? `: ${detail}` : ''}`);
|
||||
}
|
||||
|
||||
function fail(label, detail = '') {
|
||||
issues.push({ label, detail });
|
||||
checks.push({ ok: false, label, detail });
|
||||
console.error(`✘ ${label}${detail ? `: ${detail}` : ''}`);
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function loginViaApi() {
|
||||
const response = await fetch(`${PORTAL}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: USERNAME, password: PASSWORD }),
|
||||
});
|
||||
const body = await response.json().catch(() => ({}));
|
||||
if (!response.ok || !body?.authenticated) {
|
||||
throw new Error(`HTTP 登录失败 ${response.status}: ${JSON.stringify(body)}`);
|
||||
}
|
||||
const setCookie = response.headers.getSetCookie?.() ?? [];
|
||||
const cookieLine = setCookie.find((line) => line.startsWith(`${USER_COOKIE}=`))
|
||||
?? response.headers.get('set-cookie');
|
||||
const match = String(cookieLine ?? '').match(new RegExp(`${USER_COOKIE}=([^;]+)`));
|
||||
if (match?.[1]) return decodeURIComponent(match[1]);
|
||||
|
||||
const pool = await createDbPool();
|
||||
const auth = createUserAuth(pool);
|
||||
const result = await auth.login({ username: USERNAME, password: PASSWORD, ip: '127.0.0.1' });
|
||||
await pool.end();
|
||||
if (!result.ok) {
|
||||
throw new Error(`登录 cookie 解析失败,且 DB login 失败: ${result.message ?? 'unknown'}`);
|
||||
}
|
||||
return result.token;
|
||||
}
|
||||
|
||||
async function createRun(token) {
|
||||
const requestId = crypto.randomUUID();
|
||||
const response = await fetch(`${PORTAL}/api/agent/runs`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `${USER_COOKIE}=${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
request_id: requestId,
|
||||
user_message: {
|
||||
id: crypto.randomUUID(),
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: QUERY }],
|
||||
metadata: { userVisible: true, displayText: QUERY },
|
||||
},
|
||||
}),
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(`POST /agent/runs ${response.status}: ${JSON.stringify(payload)}`);
|
||||
}
|
||||
const run = payload.run ?? payload;
|
||||
return { runId: run.id, requestId, status: run.status };
|
||||
}
|
||||
|
||||
async function getRun(token, runId) {
|
||||
const response = await fetch(`${PORTAL}/api/agent/runs/${runId}`, {
|
||||
headers: { Cookie: `${USER_COOKIE}=${token}` },
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
const run = payload.run ?? payload;
|
||||
if (!response.ok) {
|
||||
throw new Error(`GET run ${response.status}: ${JSON.stringify(payload)}`);
|
||||
}
|
||||
return run;
|
||||
}
|
||||
|
||||
async function getSession(token, sessionId) {
|
||||
const response = await fetch(`${PORTAL}/api/sessions/${encodeURIComponent(sessionId)}`, {
|
||||
headers: { Cookie: `${USER_COOKIE}=${token}` },
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
return { ok: false, status: response.status, payload };
|
||||
}
|
||||
return { ok: true, session: payload };
|
||||
}
|
||||
|
||||
async function readRunEvents(runId) {
|
||||
const pool = await createDbPool();
|
||||
const [rows] = await pool.query(
|
||||
`SELECT event_type, data_json, created_at
|
||||
FROM h5_agent_run_events
|
||||
WHERE run_id = ?
|
||||
ORDER BY created_at ASC`,
|
||||
[runId],
|
||||
);
|
||||
await pool.end();
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function readSessionStreamCount(sessionId) {
|
||||
const pool = await createDbPool();
|
||||
const [[row]] = await pool.query(
|
||||
`SELECT COUNT(*) AS cnt FROM h5_session_stream_events WHERE agent_session_id = ?`,
|
||||
[sessionId],
|
||||
);
|
||||
await pool.end();
|
||||
return Number(row?.cnt ?? 0);
|
||||
}
|
||||
|
||||
function extractAssistantTexts(sessionPayload) {
|
||||
const conversation = sessionPayload?.conversation
|
||||
?? sessionPayload?.session?.conversation
|
||||
?? sessionPayload?.messages
|
||||
?? [];
|
||||
const texts = [];
|
||||
for (const message of conversation) {
|
||||
if (message?.role !== 'assistant') continue;
|
||||
const content = message?.content;
|
||||
if (typeof content === 'string') texts.push(content);
|
||||
else if (Array.isArray(content)) {
|
||||
texts.push(
|
||||
content
|
||||
.map((item) => (item?.type === 'text' ? item.text ?? '' : ''))
|
||||
.join(''),
|
||||
);
|
||||
}
|
||||
}
|
||||
return texts.filter(Boolean);
|
||||
}
|
||||
|
||||
function verifyStaticRouter() {
|
||||
const rule = classifyWithRules({
|
||||
text: QUERY,
|
||||
llmRouterEnabled: true,
|
||||
userMessage: {
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: QUERY }],
|
||||
metadata: { displayText: QUERY },
|
||||
},
|
||||
});
|
||||
if (rule?.route !== CHAT_INTENT_ROUTE.AGENT) {
|
||||
fail('规则快路径', `期望 agent_orchestration,实际 ${rule?.route ?? 'null'}`);
|
||||
return;
|
||||
}
|
||||
pass('规则快路径', `${rule.source} / ${rule.reason}`);
|
||||
|
||||
const policy = resolveChatIntentRouterPolicy({
|
||||
env: { MEMIND_CHAT_ROUTER_FALLBACK_ROUTE: 'direct_chat' },
|
||||
});
|
||||
if (policy.fallbackRoute !== CHAT_INTENT_ROUTE.AGENT) {
|
||||
fail('fallback 策略', `direct_chat 未被纠正,实际 ${policy.fallbackRoute}`);
|
||||
return;
|
||||
}
|
||||
pass('fallback 策略', 'timeout 强制 agent_orchestration');
|
||||
}
|
||||
|
||||
async function verifyAdminConfig() {
|
||||
const pool = await createDbPool();
|
||||
const [[row]] = await pool.query(
|
||||
`SELECT config_json FROM h5_memory_v2_admin_config WHERE config_scope = 'global' LIMIT 1`,
|
||||
);
|
||||
await pool.end();
|
||||
const router = row?.config_json?.chatIntentRouter ?? {};
|
||||
if (router.fallbackRoute !== 'agent_orchestration') {
|
||||
fail('Admin DB fallbackRoute', String(router.fallbackRoute ?? 'missing'));
|
||||
} else {
|
||||
pass('Admin DB fallbackRoute', router.fallbackRoute);
|
||||
}
|
||||
if (Number(router.timeoutMs) < 2000) {
|
||||
fail('Admin DB timeoutMs', String(router.timeoutMs ?? 'missing'));
|
||||
} else {
|
||||
pass('Admin DB timeoutMs', `${router.timeoutMs}ms`);
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForAssistantReply(token, sessionId) {
|
||||
const started = Date.now();
|
||||
while (Date.now() - started < MAX_REPLY_WAIT_MS) {
|
||||
const result = await getSession(token, sessionId);
|
||||
if (result.ok) {
|
||||
const texts = extractAssistantTexts(result.session);
|
||||
const combined = texts.join('\n').trim();
|
||||
if (combined.length > 20) {
|
||||
return { texts, combined, elapsedMs: Date.now() - started };
|
||||
}
|
||||
}
|
||||
await sleep(2000);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('==> 世界杯链路 E2E 验证');
|
||||
console.log(` Portal: ${PORTAL}`);
|
||||
console.log(` 用户: ${USERNAME}\n`);
|
||||
|
||||
console.log('--- 静态检查 ---');
|
||||
verifyStaticRouter();
|
||||
await verifyAdminConfig();
|
||||
|
||||
console.log('\n--- HTTP 链路 ---');
|
||||
const token = await loginViaApi();
|
||||
pass('HTTP 登录', USERNAME);
|
||||
|
||||
const { runId } = await createRun(token);
|
||||
pass('POST /agent/runs', runId);
|
||||
|
||||
let run = null;
|
||||
const runStarted = Date.now();
|
||||
const terminalRunWaitMs = Number(process.env.E2E_RUN_WAIT_MS ?? 300_000);
|
||||
while (Date.now() - runStarted < terminalRunWaitMs) {
|
||||
run = await getRun(token, runId);
|
||||
if (['succeeded', 'failed'].includes(run.status)) break;
|
||||
await sleep(2000);
|
||||
}
|
||||
if (!run || !['succeeded', 'failed'].includes(run.status)) {
|
||||
fail('run 终态', `超时未完成,最后状态 ${run?.status ?? 'unknown'}`);
|
||||
} else if (run.status === 'failed') {
|
||||
fail('run 终态', run.error ?? 'failed');
|
||||
} else {
|
||||
pass('run 终态', `succeeded (${Date.now() - runStarted}ms)`);
|
||||
}
|
||||
|
||||
const events = await readRunEvents(runId);
|
||||
let routed = events.find((row) => row.event_type === 'intent_routed');
|
||||
let route = routed?.data_json?.route ?? null;
|
||||
let source = routed?.data_json?.source ?? null;
|
||||
let reason = routed?.data_json?.reason ?? null;
|
||||
|
||||
if (route === CHAT_INTENT_ROUTE.AGENT) {
|
||||
pass('intent_routed', `${source} / ${reason}`);
|
||||
} else {
|
||||
fail('intent_routed', `route=${route}, source=${source}, reason=${reason}`);
|
||||
}
|
||||
|
||||
const suggestedSkill = routed?.data_json?.suggestedSkill ?? null;
|
||||
if (suggestedSkill === 'web') {
|
||||
pass('强制 web skill', suggestedSkill);
|
||||
} else {
|
||||
fail('强制 web skill', `期望 web,实际 ${suggestedSkill ?? 'missing'}`);
|
||||
}
|
||||
|
||||
if (events.some((row) => row.event_type === 'direct_chat_completed')) {
|
||||
fail('通道', '出现 direct_chat_completed,不应走 direct chat');
|
||||
} else {
|
||||
pass('通道', '无 direct_chat_completed');
|
||||
}
|
||||
|
||||
const sessionId = run?.sessionId ?? run?.agent_session_id;
|
||||
if (!sessionId) {
|
||||
fail('session', 'run 未回填 sessionId');
|
||||
} else {
|
||||
pass('session 创建', sessionId);
|
||||
if (events.some((row) => row.event_type === 'session_started')) {
|
||||
pass('agent 派发', 'session_started 已记录');
|
||||
} else {
|
||||
fail('agent 派发', '缺少 session_started');
|
||||
}
|
||||
|
||||
const streamCount = await readSessionStreamCount(sessionId);
|
||||
if (streamCount > 0) {
|
||||
pass('session SSE 落库', `${streamCount} 条 stream events`);
|
||||
} else {
|
||||
fail('session SSE 落库', 'h5_session_stream_events 为空(4c replay 可能未生效或 agent 未流式输出)');
|
||||
}
|
||||
|
||||
console.log('\n--- 等待 assistant 回复(最长 120s)---');
|
||||
const reply = await waitForAssistantReply(token, sessionId);
|
||||
if (!reply) {
|
||||
fail('assistant 回复', '超时未从 GET /sessions 读到 assistant 文本');
|
||||
} else {
|
||||
pass('assistant 回复', `${reply.combined.length} 字 / ${reply.elapsedMs}ms`);
|
||||
console.log('\n--- 回复摘要 ---');
|
||||
console.log(reply.combined.slice(0, 800));
|
||||
if (/无法实时获取|无法获取.*最新赛况|建议你打开体育新闻/i.test(reply.combined)) {
|
||||
fail('回复质量', '仍是 direct chat 式拒答,未实际搜索赛况');
|
||||
} else if (/(世界杯|赛程|比分|淘汰赛|小组赛|FIFA|2026)/i.test(reply.combined)) {
|
||||
pass('回复质量', '包含赛况/世界杯相关内容');
|
||||
} else {
|
||||
fail('回复质量', '未检测到赛况关键词,请人工查看完整回复');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (run && !['succeeded', 'failed'].includes(run.status)) {
|
||||
const extraWaitStarted = Date.now();
|
||||
while (Date.now() - extraWaitStarted < MAX_REPLY_WAIT_MS) {
|
||||
run = await getRun(token, runId);
|
||||
if (['succeeded', 'failed'].includes(run.status)) break;
|
||||
await sleep(2000);
|
||||
}
|
||||
if (['succeeded', 'failed'].includes(run.status)) {
|
||||
pass('run 终态(延后)', `${run.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
const finalEvents = await readRunEvents(runId);
|
||||
routed = finalEvents.find((row) => row.event_type === 'intent_routed') ?? routed;
|
||||
route = routed?.data_json?.route ?? route;
|
||||
source = routed?.data_json?.source ?? source;
|
||||
reason = routed?.data_json?.reason ?? reason;
|
||||
const sessionFinishedIdx = finalEvents.findIndex((row) => row.event_type === 'session_finished');
|
||||
const succeededIdx = finalEvents.findIndex((row) => row.event_type === 'succeeded');
|
||||
if (sessionFinishedIdx >= 0 && succeededIdx > sessionFinishedIdx) {
|
||||
pass('run 与 Finish 对齐', 'session_finished 在 succeeded 之前');
|
||||
} else if (run?.status === 'running') {
|
||||
fail('run 与 Finish 对齐', 'run 仍在 running,可能 await Finish 尚未完成');
|
||||
} else if (run?.status === 'succeeded') {
|
||||
fail('run 与 Finish 对齐', '缺少 session_finished 或顺序不对');
|
||||
}
|
||||
|
||||
console.log('\n=== 汇总 ===');
|
||||
console.log(`通过: ${checks.filter((c) => c.ok).length}/${checks.length}`);
|
||||
if (issues.length) {
|
||||
console.log('待修复:');
|
||||
for (const item of issues) {
|
||||
console.log(` - ${item.label}: ${item.detail}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('全部检查通过');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err instanceof Error ? err.stack ?? err.message : err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Local smoke: verify "世界杯现在赛况如何" routes to agent_orchestration.
|
||||
* Usage: node scripts/verify-world-cup-routing.mjs
|
||||
*/
|
||||
import crypto from 'node:crypto';
|
||||
import { loadH5Environment } from './load-env.mjs';
|
||||
import { createDbPool } from '../db.mjs';
|
||||
import { createUserAuth } from '../user-auth.mjs';
|
||||
|
||||
loadH5Environment(import.meta.dirname);
|
||||
|
||||
const PORTAL = `http://127.0.0.1:${process.env.H5_PORT ?? 8081}`;
|
||||
const USERNAME = 'john';
|
||||
const PASSWORD = process.env.JOHN_PASSWORD ?? process.env.H5_ACCESS_PASSWORD ?? '888888';
|
||||
const QUERY = '世界杯现在赛况如何';
|
||||
const MAX_WAIT_MS = 120_000;
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function login() {
|
||||
const pool = await createDbPool();
|
||||
const auth = createUserAuth(pool);
|
||||
let result = await auth.login({ username: USERNAME, password: PASSWORD, ip: '127.0.0.1' });
|
||||
if (!result.ok) {
|
||||
throw new Error(`john 登录失败(请设置 JOHN_PASSWORD 环境变量): ${result.message ?? 'unknown'}`);
|
||||
}
|
||||
await pool.end();
|
||||
return result.token;
|
||||
}
|
||||
|
||||
async function createRun(token) {
|
||||
const requestId = crypto.randomUUID();
|
||||
const response = await fetch(`${PORTAL}/api/agent/runs`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Cookie: `tkmind_user_session=${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
request_id: requestId,
|
||||
user_message: {
|
||||
id: crypto.randomUUID(),
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: QUERY }],
|
||||
metadata: { userVisible: true, displayText: QUERY },
|
||||
},
|
||||
}),
|
||||
});
|
||||
const body = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(`POST /agent/runs ${response.status}: ${JSON.stringify(body)}`);
|
||||
}
|
||||
const run = body.run ?? body;
|
||||
return { runId: run.id, requestId, status: run.status };
|
||||
}
|
||||
|
||||
async function waitForRun(token, runId) {
|
||||
const started = Date.now();
|
||||
while (Date.now() - started < MAX_WAIT_MS) {
|
||||
const response = await fetch(`${PORTAL}/api/agent/runs/${runId}`, {
|
||||
headers: { Cookie: `tkmind_user_session=${token}` },
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
const body = payload.run ?? payload;
|
||||
if (!response.ok) {
|
||||
throw new Error(`GET /agent/runs/${runId} ${response.status}: ${JSON.stringify(payload)}`);
|
||||
}
|
||||
if (['succeeded', 'failed'].includes(body.status)) {
|
||||
return body;
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
throw new Error(`run ${runId} 未在 ${MAX_WAIT_MS}ms 内完成`);
|
||||
}
|
||||
|
||||
async function loadRunEvents(runId) {
|
||||
const pool = await createDbPool();
|
||||
const [rows] = await pool.query(
|
||||
`SELECT event_type, data_json, created_at
|
||||
FROM h5_agent_run_events
|
||||
WHERE run_id = ?
|
||||
ORDER BY created_at ASC`,
|
||||
[runId],
|
||||
);
|
||||
await pool.end();
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function loadAssistantReply(sessionId) {
|
||||
const pool = await createDbPool();
|
||||
const [rows] = await pool.query(
|
||||
`SELECT text FROM h5_conversation_messages
|
||||
WHERE agent_session_id = ? AND role = 'assistant'
|
||||
ORDER BY created_at DESC LIMIT 1`,
|
||||
[sessionId],
|
||||
);
|
||||
await pool.end();
|
||||
return rows[0]?.text ?? '';
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`==> Portal: ${PORTAL}`);
|
||||
const token = await login();
|
||||
console.log('==> john 登录成功');
|
||||
|
||||
const { runId } = await createRun(token);
|
||||
console.log(`==> run 已创建: ${runId}`);
|
||||
|
||||
const run = await waitForRun(token, runId);
|
||||
console.log(`==> run 终态: ${run.status} session=${run.sessionId ?? run.agent_session_id ?? 'n/a'}`);
|
||||
|
||||
const events = await loadRunEvents(runId);
|
||||
const routed = events.find((row) => row.event_type === 'intent_routed');
|
||||
const route = routed?.data_json?.route ?? null;
|
||||
const source = routed?.data_json?.source ?? null;
|
||||
const reason = routed?.data_json?.reason ?? null;
|
||||
|
||||
console.log('\n--- intent_routed ---');
|
||||
console.log(JSON.stringify({ route, source, reason, suggestedSkill: routed?.data_json?.suggestedSkill ?? null }, null, 2));
|
||||
|
||||
const sessionId = run.sessionId ?? run.agent_session_id;
|
||||
if (sessionId) {
|
||||
const reply = await loadAssistantReply(sessionId);
|
||||
console.log('\n--- assistant 回复摘要 ---');
|
||||
console.log(reply.slice(0, 500));
|
||||
if (/无法实时获取|无法获取.*最新赛况/i.test(reply)) {
|
||||
console.error('\nFAIL: 仍走 direct_chat 拒答路径');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (route !== 'agent_orchestration') {
|
||||
console.error(`\nFAIL: 期望 route=agent_orchestration,实际 ${route}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (run.status !== 'succeeded') {
|
||||
console.error(`\nFAIL: run 终态 ${run.status}: ${run.error ?? ''}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('\nPASS: 世界杯查询已走 agent 路径');
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err instanceof Error ? err.message : err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user