feat: 首次推送到Gitea - 完整项目代码 + 安全加固 + 知识库

This commit is contained in:
ZhuiGuangAI Dev
2026-10-02 18:52:49 +08:00
parent b44601bb66
commit 8205ae709c
1185 changed files with 49841 additions and 12802 deletions
+44 -6
View File
@@ -6,6 +6,8 @@ import { fileURLToPath } from "url";
import OpenAI from "openai";
import "dotenv/config";
import { withRetry } from "./lib/retry.mjs";
import { pickCommentStyle, getStyleHint } from "./lib/topic-diversity.mjs";
import { validateContextRelevance, detectBadPatterns } from "./lib/reply-quality.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const base = process.env.DATABASE_URL.replace("mysql://", "mariadb://");
@@ -16,11 +18,11 @@ const prisma = new PrismaClient({ adapter });
const openai = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/v1",
baseURL: "https://api.deepseek.com",
});
const BOT_DATA_PATH = resolve(__dirname, "..", "data", "bot-characters.json");
const MODEL = process.env.DEEPSEEK_MODEL || "deepseek-chat";
const MODEL = process.env.DEEPSEEK_MODEL || "deepseek-v4-pro";
const LAYER = {
SESSION: "SESSION",
@@ -91,10 +93,13 @@ async function incrementStat(botUserId, field, by = 1) {
}
async function getProcessedReplyIds(botUserId) {
// 只回看最近 7 天的反馈记忆,避免全量扫描随数据增长而变慢
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const memories = await prisma.botMemory.findMany({
where: {
botId: botUserId,
memoryType: MEMORY_TYPE.FEEDBACK,
createdAt: { gte: since },
},
select: { contextTags: true },
});
@@ -108,12 +113,19 @@ async function getProcessedReplyIds(botUserId) {
return ids;
}
function buildFollowUpPrompt(bot, topicTitle, topicContent, originalPost, allReplies) {
// 📄 核心 Prompt 模板参考: scripts/prompts/bot-feedback.txt
// 实际 prompt 构建逻辑保留了动态参数注入(角色设定、回复风格、上下文等)
function buildFollowUpPrompt(bot, topicTitle, topicContent, originalPost, allReplies, options = {}) {
const { commentStyle = null } = options;
const p = bot.personality;
const repliesText = allReplies
.map((r, i) => `${i + 1}. ${r.author}: ${r.content.slice(0, 250)}`)
.join("\n");
const styleHint = commentStyle
? `\n[本次回复风格指引] ${getStyleHint(commentStyle)}\n`
: "";
return `你正在参与"追光AI行业论坛"。你之前在"${topicTitle}"这个话题下发了主帖,现在有用户回复了你,你需要用你的人格接着聊。
[你的角色设定]
@@ -128,7 +140,7 @@ ${topicContent}
[新收到的回复]
${repliesText}
${styleHint}
[二次回复要求]
1. 像真人接话,120-350字,不能太长刷屏
2. 必须对前面至少一个具体观点有回应(赞同/质疑/补充案例/反问)
@@ -150,7 +162,8 @@ async function callDeepSeek(prompt) {
model: MODEL,
messages: [{ role: "user", content: prompt }],
temperature: 0.88,
max_tokens: 800,
// DeepSeek V4 推理 token 计入 max_tokens,预算不足会导致正文截断/为空
max_tokens: 4096,
})
);
const text = response.choices[0].message.content.trim();
@@ -272,7 +285,9 @@ async function processBotFeedback(botKey, botChar) {
await incrementStat(botConfig.id, "humanInteractions", humanCount);
}
// 只有当有真人回复时,Bot才进行二次回复(避免Bot之间互相回复)
if (
humanCount > 0 &&
newReplies.length >= MIN_FEEDBACK_TO_FOLLOWUP &&
Math.random() < FOLLOW_UP_PROBABILITY &&
followUpsSent < MAX_FOLLOWUP_PER_RUN_PER_BOT &&
@@ -283,12 +298,16 @@ async function processBotFeedback(botKey, botChar) {
author: p.user.name || "匿名",
content: p.content,
}));
// 选择回复风格,确保多样性
const commentStyle = pickCommentStyle();
const prompt = buildFollowUpPrompt(
botChar,
topic.title,
topic.content,
null,
repliesForPrompt
repliesForPrompt,
{ commentStyle }
);
const result = await callDeepSeek(prompt);
const followUpContent = (result.content || "").trim();
@@ -297,6 +316,19 @@ async function processBotFeedback(botKey, botChar) {
continue;
}
// 质量检查:反模式检测
const badPatterns = detectBadPatterns(followUpContent);
if (badPatterns.isGeneric) {
console.warn(`${ts()} ⚠️ ${botChar.displayName} 回复过于通用 (pattern_matches=${badPatterns.matchCount}), 但仍保留`);
}
// 关联度检查
const contextForRelevance = repliesForPrompt.map(r => r.content).join(" ");
const { score: relevanceScore } = validateContextRelevance(followUpContent, contextForRelevance);
// 多样性度量日志
console.log(`${ts()} 📊 ${botChar.displayName}: comment_style=${commentStyle}, relevance=${(relevanceScore / 100).toFixed(2)}, generic=${badPatterns.isGeneric}`);
const post = await prisma.forumPost.create({
data: {
topicId: topic.id,
@@ -324,6 +356,12 @@ async function processBotFeedback(botKey, botChar) {
topicId: topic.id,
triggeredBy: "feedback_loop",
summary: followUpContent.slice(0, 100),
qualityMetrics: {
commentStyle,
relevance: relevanceScore / 100,
isGeneric: badPatterns.isGeneric,
patternMatches: badPatterns.matchCount,
},
},
0.6,
{