数字路人系统:30个路人bot + 实时新闻驱动发帖引擎
新增30个路人bot(每赛道3类角色): - 📰 资讯播报员(news):分享近期行业热点新闻 - 🔍 行业观察员(observer):提出趋势性问题引导思考 - ❓ 难题求助者(problem):代表普通用户提出真实困境 引擎改造: - 新增 fetchGoogleNewsRSS():Google News RSS获取3天内新闻 - 新增 NEWS_SEARCH_QUERIES:51个子板块中文搜索词配置 - 新增 buildPasserbyTopicPrompt():基于真实新闻的路人发帖提示词 - 新增 processPasserbyForum():独立路人发帖流程 - main()更新:专家2帖+路人1帖,并行运行 - 专家bot添加role过滤器,排除路人角色 总计:76个商业数字人(46专家+30路人) TypeScript编译零错误
This commit is contained in:
+230
-4
@@ -81,6 +81,60 @@ const SUB_CATEGORY_MAP = {
|
||||
"edu-corporate": ["edu-corporate"], "edu-k12": ["edu-k12"],
|
||||
};
|
||||
|
||||
const NEWS_SEARCH_QUERIES = {
|
||||
"ec-platform": "电商 淘宝 京东 拼多多 平台 最新",
|
||||
"ec-livestream": "直播带货 抖音直播 快手电商 最新",
|
||||
"ec-private": "私域流量 微信电商 社群运营 最新",
|
||||
"ec-group": "社区团购 美团优选 多多买菜 最新",
|
||||
"ec-dtc": "DTC品牌 Shopify 独立站 电商出海",
|
||||
"ec-supply": "供应链 选品 电商货源 库存管理",
|
||||
"ai-tools-app": "AI工具 ChatGPT Claude AI应用 最新",
|
||||
"ai-startup-forum": "AI创业 人工智能创业 融资 最新",
|
||||
"ai-saas": "SaaS AI出海 软件服务 最新",
|
||||
"ai-hardware": "AI硬件 智能设备 机器人 IoT",
|
||||
"ai-llm": "大模型 LLM 开源模型 人工智能",
|
||||
"ct-short-video": "短视频 抖音 B站 创作者 最新",
|
||||
"ct-live": "直播运营 直播带货 主播 最新",
|
||||
"ct-writing": "公众号 小红书 自媒体 图文创作",
|
||||
"ct-mcn": "MCN机构 达人孵化 网红经济",
|
||||
"ct-podcast": "播客 小宇宙 音频创作 最新",
|
||||
"fi-stock": "A股 港股 美股 股票 基金 最新",
|
||||
"fi-crypto": "比特币 加密货币 区块链 Web3",
|
||||
"fi-insurance": "保险 理财 资产配置 最新",
|
||||
"fi-pe": "PE VC 风险投资 融资 最新",
|
||||
"fi-forex": "外汇 期货 大宗商品 黄金 最新",
|
||||
"cb-ecommerce": "跨境电商 Amazon Temu TikTokShop",
|
||||
"cb-factory": "外贸工厂 制造业 出口 B2B 最新",
|
||||
"cb-logistics": "跨境物流 海外仓 FBA 供应链",
|
||||
"cb-payment": "跨境支付 收款 汇率 税务合规",
|
||||
"cb-brand": "品牌出海 DTC 全球化 本地化",
|
||||
"re-residential": "房价 楼市 买房 学区房 最新",
|
||||
"re-commercial": "商铺 写字楼 商业地产 最新",
|
||||
"re-renovation": "装修 家居 建材 设计 最新",
|
||||
"re-property": "物业 租赁 租房 资产托管",
|
||||
"fd-restaurant": "餐饮创业 开店 餐厅 加盟 最新",
|
||||
"fd-tea": "茶饮 咖啡 奶茶 新茶饮 饮品",
|
||||
"fd-prepared": "预制菜 中央厨房 食品加工",
|
||||
"fd-supply": "食品供应链 食材 冷链 配送",
|
||||
"fd-delivery": "外卖 美团 饿了么 外卖运营",
|
||||
"ll-beauty": "美容 美发 美甲 皮肤管理 最新",
|
||||
"ll-fitness": "健身 瑜伽 健身房 运动 最新",
|
||||
"ll-pet": "宠物 宠物店 宠物医疗 最新",
|
||||
"ll-housekeeping": "家政 保洁 月嫂 保姆 最新",
|
||||
"ll-repair": "手机维修 家电维修 数码维修",
|
||||
"ll-edu-local": "培训 艺术培训 体育培训 托管",
|
||||
"hl-cosmetic": "医美 轻医美 热玛吉 水光针",
|
||||
"hl-wellness": "健康管理 保健品 慢病 体检",
|
||||
"hl-elderly": "养老 老龄化 居家养老 适老化",
|
||||
"hl-rehab": "运动康复 产后康复 物理治疗",
|
||||
"hl-mental": "心理健康 心理咨询 情绪管理",
|
||||
"edu-skills": "职业技能 编程培训 设计培训 考证",
|
||||
"edu-knowledge": "知识付费 在线教育 课程 社群",
|
||||
"edu-abroad": "留学 出国 语言培训 游学",
|
||||
"edu-corporate": "企业培训 管理培训 团建 内训",
|
||||
"edu-k12": "K12教育 学科辅导 素质教育 家庭",
|
||||
};
|
||||
|
||||
function ts() {
|
||||
return `[${new Date().toISOString()}]`;
|
||||
}
|
||||
@@ -94,6 +148,104 @@ function slugify(str) {
|
||||
return `${clean}-${Date.now().toString(36)}`;
|
||||
}
|
||||
|
||||
async function fetchGoogleNewsRSS(query, lang = "zh-CN", country = "CN") {
|
||||
try {
|
||||
const url = `https://news.google.com/rss/search?q=${encodeURIComponent(query)}&hl=${lang}&gl=${country}&ceid=${country}:${lang === "zh-CN" ? "zh-Hans" : "en"}`;
|
||||
const response = await fetch(url, {
|
||||
headers: { "User-Agent": "Mozilla/5.0 (compatible; PasserbyBot/1.0)" },
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!response.ok) return [];
|
||||
const xml = await response.text();
|
||||
const items = [];
|
||||
const itemRegex = /<item>([\s\S]*?)<\/item>/g;
|
||||
let match;
|
||||
while ((match = itemRegex.exec(xml)) !== null) {
|
||||
const block = match[1];
|
||||
const titleMatch = block.match(/<title><!\[CDATA\[([\s\S]*?)\]\]><\/title>/) || block.match(/<title>([\s\S]*?)<\/title>/);
|
||||
const linkMatch = block.match(/<link>([\s\S]*?)<\/link>/);
|
||||
const pubDateMatch = block.match(/<pubDate>([\s\S]*?)<\/pubDate>/);
|
||||
if (titleMatch) {
|
||||
items.push({
|
||||
title: titleMatch[1].trim(),
|
||||
url: linkMatch ? linkMatch[1].trim() : "",
|
||||
pubDate: pubDateMatch ? pubDateMatch[1].trim() : "",
|
||||
});
|
||||
}
|
||||
}
|
||||
return items.filter(item => {
|
||||
if (!item.pubDate) return true;
|
||||
const pubTime = new Date(item.pubDate).getTime();
|
||||
const threeDaysAgo = Date.now() - 3 * 24 * 60 * 60 * 1000;
|
||||
return pubTime > threeDaysAgo;
|
||||
}).slice(0, 8);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRecentNewsForCategory(forumSlug) {
|
||||
const query = NEWS_SEARCH_QUERIES[forumSlug];
|
||||
if (!query) return [];
|
||||
console.log(`${ts()} Searching news: "${query}"`);
|
||||
const cnNews = await fetchGoogleNewsRSS(query, "zh-CN", "CN");
|
||||
console.log(`${ts()} Found ${cnNews.length} recent Chinese news items`);
|
||||
return cnNews;
|
||||
}
|
||||
|
||||
function buildPasserbyTopicPrompt(bot, forumName, forumDescription, newsItems) {
|
||||
const p = bot.personality;
|
||||
const newsText = newsItems.length > 0
|
||||
? newsItems.map((n, i) => `${i + 1}. [${n.pubDate || "近期"}] ${n.title}`).join("\n")
|
||||
: "(当前无实时新闻,请根据你的角色知识和行业常识,提出一个结合当前季节/月份/年份背景的时效性强的话题。注意话题中必须体现当前时间背景,不能看起来像旧帖子)";
|
||||
|
||||
const today = new Date().toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" });
|
||||
const passerbyType = p.passerbyType || "observer";
|
||||
|
||||
const typeInstructions = {
|
||||
news: "你是一个善于发现行业动态的人。请从上面的近期新闻中选一个最有话题性的,用你的视角提出讨论。话题必须引用具体新闻事件,不能泛泛而谈",
|
||||
observer: "你是一个观察敏锐的人。请结合上面新闻中反映的行业趋势,提出一个值得深挖的问题。话题要有洞察力,引导大家思考现象背后的原因",
|
||||
problem: "你是一个正在这个行业里遇到实际困难的人。请结合上面的近期新闻和行业现状,提出一个具体的、有代表性的困扰或难题。问题要有真实感,让人感同身受",
|
||||
};
|
||||
|
||||
const instruction = typeInstructions[passerbyType] || typeInstructions.observer;
|
||||
|
||||
return `你正在参与一个叫"追光AI行业论坛"的中文社区。今天是${today}。你现在在"${forumName}"板块发言。
|
||||
|
||||
${forumDescription}
|
||||
|
||||
[你的角色设定]
|
||||
你是【${bot.displayName}】,${p.identity}
|
||||
你的角色定位:${p.stance}
|
||||
你说话的风格:${p.speakingStyle}
|
||||
你的口头禅:${p.catchphrase}
|
||||
|
||||
[近期行业新闻参考]
|
||||
${newsText}
|
||||
|
||||
[发言要求]
|
||||
${instruction}
|
||||
请以${bot.displayName}的身份在这个板块发起一个新话题。要求:
|
||||
1. 话题标题10-20字,要像真人发的帖子,有真实感,不能写成新闻标题或论文标题
|
||||
2. 内容180-400字,结合上面的新闻素材,有具体细节和你的个人感受
|
||||
3. 如果引用了具体新闻,用自己的话说出来,不要像官方报道
|
||||
4. 使用你的说话风格,自然口语化,可以有情绪和困惑
|
||||
5. 结尾留一个问题引导大家参与讨论
|
||||
|
||||
[禁止事项]
|
||||
- 禁止使用"首先/其次/最后"等格式化连接词
|
||||
- 禁止使用"从以下几个方面分析""综上所述"等教科书句式
|
||||
- 禁止表现得像AI助手或官方新闻报道
|
||||
- 话题中必须让人感受到"这是最近/今天发生的事",不能像泛泛而谈的老话题
|
||||
${p.forbiddenPatterns ? `- 禁止使用${p.forbiddenPatterns.join("、")}` : ""}
|
||||
|
||||
请只返回JSON格式,不要包含其他任何内容:
|
||||
{
|
||||
"title": "话题标题",
|
||||
"content": "话题内容"
|
||||
}`;
|
||||
}
|
||||
|
||||
async function logTask(action, status, detail) {
|
||||
try {
|
||||
await prisma.taskLog.create({
|
||||
@@ -326,7 +478,7 @@ async function processForum(forumSlug, botCharacters, forceNewTopic = false) {
|
||||
console.log(`${ts()} Processing forum: ${category.name}`);
|
||||
|
||||
const forumBots = botCharacters.filter(
|
||||
c => c.primaryForums && c.primaryForums.includes(forumSlug)
|
||||
c => c.primaryForums && c.primaryForums.includes(forumSlug) && c.role !== "passerby"
|
||||
);
|
||||
|
||||
if (forumBots.length === 0) {
|
||||
@@ -442,19 +594,93 @@ async function processForum(forumSlug, botCharacters, forceNewTopic = false) {
|
||||
}
|
||||
}
|
||||
|
||||
async function processPasserbyForum(forumSlug, botCharacters) {
|
||||
const category = await getCategoryBySlug(forumSlug);
|
||||
if (!category) {
|
||||
console.log(`${ts()} Passerby: Forum "${forumSlug}" not found, skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${ts()} Passerby processing: ${category.name}`);
|
||||
|
||||
const passerbyBots = botCharacters.filter(
|
||||
c => c.role === "passerby" && c.primaryForums && c.primaryForums.includes(forumSlug)
|
||||
);
|
||||
|
||||
if (passerbyBots.length === 0) {
|
||||
console.log(`${ts()} No passerby bots for this forum.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const recentNews = await fetchRecentNewsForCategory(forumSlug);
|
||||
if (recentNews.length === 0) {
|
||||
console.log(`${ts()} No recent news found, will generate from bot knowledge.`);
|
||||
}
|
||||
|
||||
const posterBot = pick(passerbyBots);
|
||||
const botConf = await getBotWithConfig(posterBot.key);
|
||||
if (!botConf) {
|
||||
console.log(`${ts()} Passerby bot "${posterBot.key}" not found in DB.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${ts()} ${posterBot.displayName} (${posterBot.personality.passerbyType}) creating topic...`);
|
||||
|
||||
try {
|
||||
const recentMemories = await getRecentMemories(botConf.id, 2);
|
||||
const memoryCtx = buildMemoryContext(recentMemories, category.name);
|
||||
|
||||
let prompt = buildPasserbyTopicPrompt(posterBot, category.name, category.description, recentNews);
|
||||
if (memoryCtx) {
|
||||
prompt = prompt.replace("[你的角色设定]", memoryCtx + "[你的角色设定]");
|
||||
} else {
|
||||
prompt = prompt.replace("[你的角色设定]", "\n[你的角色设定]");
|
||||
}
|
||||
|
||||
const result = await callDeepSeek(prompt);
|
||||
|
||||
const subCat = await getRandomSubCategory(forumSlug);
|
||||
const catId = subCat ? subCat.id : category.id;
|
||||
|
||||
const topic = await createTopic(catId, botConf.userId, result.title, result.content);
|
||||
console.log(`${ts()} Passerby topic created: "${result.title}"`);
|
||||
|
||||
await saveMemory(botConf.id, "experience", {
|
||||
action: "create_topic_passerby",
|
||||
forum: category.name,
|
||||
title: result.title,
|
||||
summary: result.content.substring(0, 100),
|
||||
newsCount: recentNews.length,
|
||||
}, 0.5);
|
||||
|
||||
await logTask("create_topic_passerby", "success",
|
||||
`${posterBot.displayName}(${posterBot.personality.passerbyType}) posted in ${category.name}: ${result.title}`);
|
||||
} catch (err) {
|
||||
console.error(`${ts()} Passerby topic error:`, err.message);
|
||||
await logTask("create_topic_passerby", "error", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`${ts()} Bot Activity Engine starting...`);
|
||||
|
||||
const { characters: botCharacters } = await loadBotCharacters();
|
||||
console.log(`${ts()} Loaded ${botCharacters.length} bot characters.`);
|
||||
|
||||
const activeForums = FORUM_SLUGS.sort(() => Math.random() - 0.5).slice(0, 2);
|
||||
console.log(`${ts()} Processing ${activeForums.length} forums: ${activeForums.join(", ")}`);
|
||||
const expertForums = FORUM_SLUGS.sort(() => Math.random() - 0.5).slice(0, 2);
|
||||
console.log(`${ts()} Processing ${expertForums.length} forums for experts: ${expertForums.join(", ")}`);
|
||||
|
||||
for (const forumSlug of activeForums) {
|
||||
for (const forumSlug of expertForums) {
|
||||
await processForum(forumSlug, botCharacters);
|
||||
}
|
||||
|
||||
const passerbyForums = FORUM_SLUGS.sort(() => Math.random() - 0.5).slice(0, 1);
|
||||
console.log(`${ts()} Processing ${passerbyForums.length} forums for passerbys: ${passerbyForums.join(", ")}`);
|
||||
|
||||
for (const forumSlug of passerbyForums) {
|
||||
await processPasserbyForum(forumSlug, botCharacters);
|
||||
}
|
||||
|
||||
console.log(`${ts()} Bot Activity Engine done.`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user