fix: address code review feedback - extract constants, fix Claude model matching, workflow conditions
- llm-router.js: fix isClaudeModel() to properly match against CLAUDE_MODELS array - deputy-message-board.js: extract magic numbers to named constants (RETRY_BASE_DELAY_MS, MAX_ERRORS_KEPT, etc.) - subscription-server-v3.js: extract rate limit to MAX_SEND_CODE_PER_HOUR constant - deputy-message-board.yml: add github.event_name != 'schedule' condition to event mode job Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/a513d091-ce5c-40d4-bb3c-0adeff70b5b0 Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
parent
656febf8d6
commit
8d408d1ce2
|
|
@ -25,12 +25,13 @@ jobs:
|
||||||
name: "🎖️ 副将回复留言"
|
name: "🎖️ 副将回复留言"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: >
|
if: >
|
||||||
(github.event_name == 'issues' &&
|
github.event_name != 'schedule' &&
|
||||||
|
((github.event_name == 'issues' &&
|
||||||
github.event.action == 'opened' &&
|
github.event.action == 'opened' &&
|
||||||
contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board')) ||
|
contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board')) ||
|
||||||
(github.event_name == 'issue_comment' &&
|
(github.event_name == 'issue_comment' &&
|
||||||
contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board') &&
|
contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board') &&
|
||||||
github.event.comment.user.login != 'github-actions[bot]')
|
github.event.comment.user.login != 'github-actions[bot]'))
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,12 @@ const {
|
||||||
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
|
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
|
||||||
const STATUS_FILE = path.join(__dirname, '..', 'data', 'deputy-status.json');
|
const STATUS_FILE = path.join(__dirname, '..', 'data', 'deputy-status.json');
|
||||||
|
|
||||||
|
// ── 可调常量 ──
|
||||||
|
const RETRY_BASE_DELAY_MS = 2000; // LLM重试基础延迟
|
||||||
|
const MAX_ERRORS_KEPT = 20; // 保留最近N条错误记录
|
||||||
|
const MAX_ESCALATIONS_KEPT = 10; // 保留最近N条升级记录
|
||||||
|
const MAX_LLM_FAILURES_BEFORE_ESCALATION = 3; // 连续N次全模型失败后升级
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
// LLM多模型自动降级路由器 (内嵌版)
|
// LLM多模型自动降级路由器 (内嵌版)
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
|
|
@ -143,7 +149,7 @@ async function callLLMWithFallback(systemPrompt, userMessage) {
|
||||||
const errMsg = `${model}(attempt ${attempt}): ${err.message}`;
|
const errMsg = `${model}(attempt ${attempt}): ${err.message}`;
|
||||||
console.log(`[副将] ⚠️ ${errMsg}`);
|
console.log(`[副将] ⚠️ ${errMsg}`);
|
||||||
errors.push(errMsg);
|
errors.push(errMsg);
|
||||||
if (attempt < 2) await new Promise(r => setTimeout(r, 2000 * attempt));
|
if (attempt < 2) await new Promise(r => setTimeout(r, RETRY_BASE_DELAY_MS * attempt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -384,7 +390,7 @@ async function processMessage(issueNumber, question, author, ctx, systemSummary,
|
||||||
// ═══════════════════════════════════════════════
|
// ═══════════════════════════════════════════════
|
||||||
|
|
||||||
async function checkAndEscalate(status) {
|
async function checkAndEscalate(status) {
|
||||||
if (status.consecutive_llm_failures >= 3) {
|
if (status.consecutive_llm_failures >= MAX_LLM_FAILURES_BEFORE_ESCALATION) {
|
||||||
const title = `🚨 [副将升级] LLM连续${status.consecutive_llm_failures}次全模型调用失败`;
|
const title = `🚨 [副将升级] LLM连续${status.consecutive_llm_failures}次全模型调用失败`;
|
||||||
const body = `## <20><> 铸渊副将升级通报\n\n` +
|
const body = `## <20><> 铸渊副将升级通报\n\n` +
|
||||||
`**时间**: ${new Date().toISOString()}\n` +
|
`**时间**: ${new Date().toISOString()}\n` +
|
||||||
|
|
@ -402,7 +408,7 @@ async function checkAndEscalate(status) {
|
||||||
try {
|
try {
|
||||||
await createEscalationIssue(title, body);
|
await createEscalationIssue(title, body);
|
||||||
status.escalations.push({ time: new Date().toISOString(), type: 'llm_failure', detail: `连续${status.consecutive_llm_failures}次失败` });
|
status.escalations.push({ time: new Date().toISOString(), type: 'llm_failure', detail: `连续${status.consecutive_llm_failures}次失败` });
|
||||||
if (status.escalations.length > 10) status.escalations = status.escalations.slice(-10);
|
if (status.escalations.length > MAX_ESCALATIONS_KEPT) status.escalations = status.escalations.slice(-MAX_ESCALATIONS_KEPT);
|
||||||
console.log(`[副将] 🚨 升级Issue已创建`);
|
console.log(`[副将] 🚨 升级Issue已创建`);
|
||||||
} catch (err) { console.error(`[副将] ⚠️ 创建升级Issue失败: ${err.message}`); }
|
} catch (err) { console.error(`[副将] ⚠️ 创建升级Issue失败: ${err.message}`); }
|
||||||
}
|
}
|
||||||
|
|
@ -466,7 +472,7 @@ async function patrolMode() {
|
||||||
status.errors.push(`${new Date().toISOString()} · patrol: ${err.message}`);
|
status.errors.push(`${new Date().toISOString()} · patrol: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.errors.length > 20) status.errors = status.errors.slice(-20);
|
if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT);
|
||||||
await checkAndEscalate(status);
|
await checkAndEscalate(status);
|
||||||
status.last_success = repliedCount > 0 || processedCount === 0 ? new Date().toISOString() : status.last_success;
|
status.last_success = repliedCount > 0 || processedCount === 0 ? new Date().toISOString() : status.last_success;
|
||||||
saveStatus(status);
|
saveStatus(status);
|
||||||
|
|
@ -506,7 +512,7 @@ async function eventMode() {
|
||||||
status.errors.push(`${new Date().toISOString()} · event #${ISSUE_NUMBER}: ${err.message}`);
|
status.errors.push(`${new Date().toISOString()} · event #${ISSUE_NUMBER}: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.errors.length > 20) status.errors = status.errors.slice(-20);
|
if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT);
|
||||||
await checkAndEscalate(status);
|
await checkAndEscalate(status);
|
||||||
saveStatus(status);
|
saveStatus(status);
|
||||||
}
|
}
|
||||||
|
|
@ -527,7 +533,7 @@ main().catch(err => {
|
||||||
try {
|
try {
|
||||||
const status = readStatus();
|
const status = readStatus();
|
||||||
status.errors.push(`${new Date().toISOString()} · fatal: ${err.message}`);
|
status.errors.push(`${new Date().toISOString()} · fatal: ${err.message}`);
|
||||||
if (status.errors.length > 20) status.errors = status.errors.slice(-20);
|
if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT);
|
||||||
saveStatus(status);
|
saveStatus(status);
|
||||||
} catch { /* ignore */ }
|
} catch { /* ignore */ }
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ const CLAUDE_RELAY_PORT = parseInt(process.env.ZY_CLAUDE_RELAY_PORT || '18443',
|
||||||
const CLAUDE_RELAY_ENABLED = process.env.ZY_CLAUDE_RELAY_ENABLED === 'true';
|
const CLAUDE_RELAY_ENABLED = process.env.ZY_CLAUDE_RELAY_ENABLED === 'true';
|
||||||
|
|
||||||
function isClaudeModel(model) {
|
function isClaudeModel(model) {
|
||||||
return CLAUDE_MODELS.some(cm => model.includes('claude'));
|
return CLAUDE_MODELS.some(cm => model.includes(cm)) || model.includes('claude');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 路由器状态 ──────────────────────────────
|
// ── 路由器状态 ──────────────────────────────
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ const userManager = require('./user-manager');
|
||||||
// ── 操作快照文件 ──────────────────────────────
|
// ── 操作快照文件 ──────────────────────────────
|
||||||
const AUTH_SNAPSHOT_FILE = path.join(DATA_DIR, 'bandwidth-auth-snapshots.json');
|
const AUTH_SNAPSHOT_FILE = path.join(DATA_DIR, 'bandwidth-auth-snapshots.json');
|
||||||
const MAX_SNAPSHOTS = 500; // 最多保留500条快照记录
|
const MAX_SNAPSHOTS = 500; // 最多保留500条快照记录
|
||||||
|
const MAX_SEND_CODE_PER_HOUR = 3; // 每个IP每小时最多发送验证码次数
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存用户操作快照
|
* 保存用户操作快照
|
||||||
|
|
@ -1751,7 +1752,7 @@ async function submitCode(e) {
|
||||||
const hourPart = parseInt(k.split(':').pop(), 10);
|
const hourPart = parseInt(k.split(':').pop(), 10);
|
||||||
if (hourPart < currentHourPrefix - 1) delete rl[k];
|
if (hourPart < currentHourPrefix - 1) delete rl[k];
|
||||||
}
|
}
|
||||||
if (rl[hourKey] > 3) {
|
if (rl[hourKey] > MAX_SEND_CODE_PER_HOUR) {
|
||||||
res.writeHead(429, { 'Content-Type': 'application/json; charset=utf-8' });
|
res.writeHead(429, { 'Content-Type': 'application/json; charset=utf-8' });
|
||||||
res.end(JSON.stringify({ success: false, message: '发送频率过高,请1小时后重试' }));
|
res.end(JSON.stringify({ success: false, message: '发送频率过高,请1小时后重试' }));
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue