Files
zhuiguang-ai/scripts/bot-adversarial-learning-run.mjs
T

148 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 数字人 vs 真人"对抗学习" 调度入口
// 每周给所有专家 bot 各选 1 篇真实高赞真人帖,提取"为什么火"的洞察
// 用法:
// node scripts/bot-adversarial-learning-run.mjs # 默认本周
// node scripts/bot-adversarial-learning-run.mjs --week=2026-W23 # 指定周
// node scripts/bot-adversarial-learning-run.mjs --force # 强制重学(覆盖已有)
// node scripts/bot-adversarial-learning-run.mjs --bot=laochen # 单 bot
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { fileURLToPath } from "url";
import { dirname } from "path";
import "dotenv/config";
import {
runWeeklyAdversarialLearning,
getISOWeekKey,
disconnectAdversarialLearning,
} from "./lib/bot-adversarial-learning.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const base = process.env.DATABASE_URL.replace("mysql://", "mariadb://");
const sep = base.includes("?") ? "&" : "?";
const connectionString = `${base}${sep}connection_limit=3&pool_timeout=10`;
const adapter = new PrismaMariaDb(connectionString);
const prisma = new PrismaClient({ adapter });
function ts() {
return `[${new Date().toISOString()}]`;
}
function parseArgs() {
const args = process.argv.slice(2);
const out = { week: null, force: false, bot: null, lookback: 7 };
for (const a of args) {
if (a.startsWith("--week=")) out.week = a.split("=")[1];
else if (a === "--force") out.force = true;
else if (a.startsWith("--bot=")) out.bot = a.split("=")[1];
else if (a.startsWith("--lookback=")) out.lookback = parseInt(a.split("=")[1], 10);
}
return out;
}
async function main() {
const args = parseArgs();
const weekKey = args.week || getISOWeekKey();
console.log(
`${ts()} Adversarial Learning run starting (week=${weekKey}, force=${args.force}, bot=${args.bot || "all"}, lookback=${args.lookback}d)`
);
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL 未配置");
}
const startTime = Date.now();
// --force 时:先把本周已有 learning 删掉(针对限定 bot)
if (args.force) {
const where = args.bot
? { bot: { user: { email: `bot_${args.bot}@zhuiguang.ai` } }, weekKey }
: { weekKey };
const del = await prisma.botAdversarialLearning.deleteMany({ where });
console.log(`${ts()} --force: 删除了 ${del.count} 条已有 learning`);
}
// 单 bot 模式
let botConfigIds = null;
if (args.bot) {
const u = await prisma.user.findFirst({ where: { email: `bot_${args.bot}@zhuiguang.ai` } });
if (!u) {
console.error(`${ts()} Bot "${args.bot}" 未找到`);
process.exit(1);
}
const c = await prisma.botConfig.findUnique({ where: { userId: u.id } });
if (!c) {
console.error(`${ts()} Bot "${args.bot}" 没有 BotConfig`);
process.exit(1);
}
botConfigIds = [c.id];
}
const summary = await runWeeklyAdversarialLearning({
weekKey,
lookbackDays: args.lookback,
botConfigIds,
});
const duration = Date.now() - startTime;
console.log(
`${ts()} Done: bots=${summary.botCount} learned=${summary.learned} skipped=${summary.skipped} errors=${summary.errors} (${duration}ms)`
);
for (const r of summary.results.slice(0, 10)) {
if (r.error) {
console.log(`${ts()} ✗ ${r.botName} ${r.error}`);
} else if (r.skipped) {
console.log(`${ts()} ⤼ ${r.botName} skip=${r.reason}`);
} else {
const rec = r.record;
console.log(
`${ts()} ✓ ${r.botName} ← topic#${rec.sourceRefId} "${rec.sourceTitle?.slice(0, 30)}" rel=${rec.relevanceScore} cat=${JSON.stringify(rec.learnCategories)}`
);
}
}
await prisma.taskLog.create({
data: {
taskKey: "bot-adversarial-learning-run",
taskName: "Bot 对抗学习(真人高赞帖)",
status: summary.errors > 0 ? "partial" : "success",
startedAt: new Date(startTime),
finishedAt: new Date(),
triggerBy: "cron",
result: {
weekKey,
botCount: summary.botCount,
learned: summary.learned,
skipped: summary.skipped,
errors: summary.errors,
force: args.force,
lookbackDays: args.lookback,
durationMs: duration,
},
},
});
}
main()
.catch(async (e) => {
console.error(`${ts()} Fatal:`, e);
try {
await prisma.taskLog.create({
data: {
taskKey: "bot-adversarial-learning-run",
taskName: "Bot 对抗学习(真人高赞帖)",
status: "failed",
startedAt: new Date(),
finishedAt: new Date(),
triggerBy: "cron",
error: (e && e.message ? e.message : String(e)).slice(0, 1000),
},
});
} catch {}
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
await disconnectAdversarialLearning();
});