mirror of
https://github.com/TencentCloud/TencentDB-Agent-Memory
synced 2026-07-10 20:34:30 +00:00
feat: release v0.3.3 — Hermes adapter, context offload, core refactor
This commit is contained in:
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user