Files
TencentDB-Agent-Memory/scripts/test-tdai-memory.mjs
T

320 lines
9.2 KiB
JavaScript
Raw Normal View History

/**
* TDAI Memory Test Script
*
* 测试 TDAI 记忆功能是否正常工作。
* 启动 Gateway,然后发送测试请求验证记忆捕获和召回。
*/
import { spawn } from "node:child_process";
import { createServer } from "node:http";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { EventEmitter } from "node:events";
import { tmpdir } from "node:os";
import { mkdir, rm, writeFile } from "node:fs/promises";
const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = join(__dirname, "..");
// 测试配置
const TEST_CONFIG = {
server: {
// Use 8422 by default to avoid clashing with the hermes sidecar on 8420.
port: Number(process.env.TDAI_TEST_PORT || 8422),
host: "127.0.0.1",
},
data: {
baseDir: join(tmpdir(), "tdai-memory-test"),
},
llm: {
baseUrl: process.env.TDAI_LLM_BASE_URL || "https://vdbteam.openai.azure.com/openai/v1",
apiKey: process.env.TDAI_LLM_API_KEY || "",
model: process.env.TDAI_LLM_MODEL || "gpt-5.2-chat",
maxTokens: 4096,
timeoutMs: 120000,
},
};
// 用于接收 Gateway 回调的本地服务器
const callbackServer = createServer((req, res) => {
console.log(`[Callback] ${req.method} ${req.url}`);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
});
let gatewayProcess = null;
async function ensureDataDir() {
await mkdir(TEST_CONFIG.data.baseDir, { recursive: true });
const configPath = join(TEST_CONFIG.data.baseDir, "tdai-gateway.yaml");
const configContent = `# TDAI Gateway Test Config
server:
port: ${TEST_CONFIG.server.port}
host: ${TEST_CONFIG.server.host}
data:
baseDir: ${TEST_CONFIG.data.baseDir}
llm:
baseUrl: ${TEST_CONFIG.llm.baseUrl}
apiKey: ${TEST_CONFIG.llm.apiKey || "sk-test-key"}
model: ${TEST_CONFIG.llm.model}
maxTokens: ${TEST_CONFIG.llm.maxTokens}
timeoutMs: ${TEST_CONFIG.llm.timeoutMs}
memory:
capture:
enabled: true
l0l1RetentionDays: 7
extraction:
enabled: true
enableDedup: true
recall:
enabled: true
maxResults: 5
scoreThreshold: 0.1
strategy: hybrid
pipeline:
everyNConversations: 3
enableWarmup: true
`;
await writeFile(configPath, configContent);
console.log(`[Config] Written to ${configPath}`);
}
function startCallbackServer() {
const port = 18420;
return new Promise((resolve, reject) => {
callbackServer.listen(port, "127.0.0.1", () => {
console.log(`[CallbackServer] Started on port ${port}`);
resolve(port);
});
callbackServer.on("error", reject);
});
}
function startGateway(callbackPort) {
return new Promise((resolve, reject) => {
const configPath = join(TEST_CONFIG.data.baseDir, "tdai-gateway.yaml");
// Launch the TS source directly via npx/tsx (no dist build step required).
const gatewayArgs = [
"npx",
"--yes",
"tsx",
"src/gateway/server.ts",
];
console.log(`[Gateway] Starting with args:`, gatewayArgs);
const env = {
...process.env,
TDAI_GATEWAY_PORT: String(TEST_CONFIG.server.port),
TDAI_GATEWAY_HOST: TEST_CONFIG.server.host,
TDAI_DATA_DIR: TEST_CONFIG.data.baseDir,
TDAI_LLM_BASE_URL: TEST_CONFIG.llm.baseUrl,
TDAI_LLM_API_KEY: TEST_CONFIG.llm.apiKey,
TDAI_LLM_MODEL: TEST_CONFIG.llm.model,
TDAI_CALLBACK_PORT: String(callbackPort),
};
gatewayProcess = spawn(gatewayArgs[0], gatewayArgs.slice(1), {
cwd: PROJECT_ROOT,
env,
stdio: ["ignore", "pipe", "pipe"],
});
gatewayProcess.stdout?.on("data", (data) => {
console.log(`[Gateway] ${data.toString().trim()}`);
});
gatewayProcess.stderr?.on("data", (data) => {
console.error(`[Gateway Error] ${data.toString().trim()}`);
});
let healthCheckCount = 0;
const maxHealthChecks = 30;
const checkInterval = 1000;
const checkHealth = () => {
healthCheckCount++;
const req = new Request(`http://${TEST_CONFIG.server.host}:${TEST_CONFIG.server.port}/health`);
fetch(req)
.then((res) => {
if (res.ok) {
resolve({ process: gatewayProcess, port: TEST_CONFIG.server.port });
return;
}
})
.catch(() => {});
if (healthCheckCount >= maxHealthChecks) {
reject(new Error("Gateway failed to start"));
}
setTimeout(checkHealth, checkInterval);
};
setTimeout(checkHealth, checkInterval);
gatewayProcess.on("error", (err) => {
reject(new Error(`Failed to start gateway: ${err.message}`));
});
});
}
async function testHealthCheck(gatewayPort) {
console.log("\n[Test] Health check...");
try {
const res = await fetch(`http://127.0.0.1:${gatewayPort}/health`);
const data = await res.json();
console.log(`[OK] Health check passed:`, JSON.stringify(data, null, 2));
return true;
} catch (err) {
console.error(`[FAIL] Health check failed:`, err.message);
return false;
}
}
async function testCapture(gatewayPort) {
console.log("\n[Test] Capture conversation...");
try {
const res = await fetch(`http://127.0.0.1:${gatewayPort}/capture`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_content: "你好,我是一个测试用户。我喜欢编程和咖啡。",
assistant_content: "你好!很高兴认识你。编程和咖啡都是很棒的爱好!",
session_key: "test-session-001",
session_id: "test-session-id-001",
user_id: "test-user",
}),
});
const data = await res.json();
console.log(`[OK] Capture response:`, JSON.stringify(data, null, 2));
return true;
} catch (err) {
console.error(`[FAIL] Capture failed:`, err.message);
return false;
}
}
async function testRecall(gatewayPort) {
console.log("\n[Test] Recall memories...");
try {
const res = await fetch(`http://127.0.0.1:${gatewayPort}/recall`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: "用户的爱好是什么?",
session_key: "test-session-001",
user_id: "test-user",
}),
});
const data = await res.json();
console.log(`[OK] Recall response:`, JSON.stringify(data, null, 2));
return true;
} catch (err) {
console.error(`[FAIL] Recall failed:`, err.message);
return false;
}
}
async function testSearchMemories(gatewayPort) {
console.log("\n[Test] Search memories...");
try {
const res = await fetch(`http://127.0.0.1:${gatewayPort}/search/memories`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: "编程",
limit: 5,
type: "episode",
}),
});
const data = await res.json();
console.log(`[OK] Search memories response:`, JSON.stringify(data, null, 2));
return true;
} catch (err) {
console.error(`[FAIL] Search memories failed:`, err.message);
return false;
}
}
async function stopGateway() {
if (gatewayProcess) {
console.log("\n[Cleanup] Stopping gateway...");
gatewayProcess.kill("SIGTERM");
gatewayProcess = null;
}
if (callbackServer.listening) {
callbackServer.close();
}
}
async function cleanup() {
await stopGateway();
// 不删除测试数据目录,保留用于调试
console.log("[Cleanup] Test data preserved at:", TEST_CONFIG.data.baseDir);
}
async function main() {
console.log("=".repeat(60));
console.log("TDAI Memory Test");
console.log("=".repeat(60));
const results = {
health: false,
capture: false,
recall: false,
search: false,
};
try {
// 1. 确保数据目录存在
await ensureDataDir();
// 2. 启动回调服务器
const callbackPort = await startCallbackServer();
// 3. 启动 Gateway
const { port: gatewayPort } = await startGateway(callbackPort);
// 等待 Gateway 完全启动
await new Promise((resolve) => setTimeout(resolve, 2000));
// 4. 运行测试
results.health = await testHealthCheck(gatewayPort);
if (results.health) {
// 等待一下让 Gateway 初始化
await new Promise((resolve) => setTimeout(resolve, 1000));
results.capture = await testCapture(gatewayPort);
if (results.capture) {
// 等待提取完成
await new Promise((resolve) => setTimeout(resolve, 3000));
results.recall = await testRecall(gatewayPort);
results.search = await testSearchMemories(gatewayPort);
}
}
} catch (err) {
console.error("\n[Test] Error:", err.message);
} finally {
await cleanup();
// 打印测试结果
console.log("\n" + "=".repeat(60));
console.log("Test Results:");
console.log("=".repeat(60));
console.log(` Health Check: ${results.health ? "PASS" : "FAIL"}`);
console.log(` Capture: ${results.capture ? "PASS" : "FAIL"}`);
console.log(` Recall: ${results.recall ? "PASS" : "FAIL"}`);
console.log(` Search: ${results.search ? "PASS" : "FAIL"}`);
const passed = Object.values(results).filter(Boolean).length;
console.log(`\nTotal: ${passed}/4 passed`);
}
}
main().catch(console.error);