lifekline / server /parallelAnalyzer.js
xiaobo ren
Fix 'Assignment to constant variable' error: change content from const to let in parallelAnalyzer
c440ffa
/**
* 并行分析器 - 6个Agent同时工作
* 实现渐进式SSE推送,用户感知到的等待时间 = 最快Agent的返回时间
* K线分为过去(出生到今年)和未来(今年到100岁)两个并行请求,提升生成速度
*/
import fetch from 'node-fetch';
import { nanoid } from 'nanoid';
import {
AGENT_PROMPTS,
AGENT_CORE_PROMPT,
AGENT_KLINE_PAST_PROMPT,
AGENT_KLINE_FUTURE_PROMPT,
AGENT_CAREER_PROMPT,
AGENT_MARRIAGE_PROMPT,
AGENT_CRYPTO_PROMPT,
} from './agentPrompts.js';
import { generateFallbackKLine } from './baziCalculator.js';
import { buildApiRequest, parseApiResponse } from './apiConfig.js';
// 为不同Agent分配最适合的模型
const AGENT_MODEL_ASSIGNMENT = {
core: 'grok-4-1-fast-reasoning', // 核心命理 - 逻辑推理强
kline_past: 'gpt-5-mini', // 过去K线 - 数据结构化强
kline_future: 'gpt-5-mini', // 未来K线 - 数据结构化强
career: 'gpt-5-mini', // 事业财富 - 综合能力
marriage: 'grok-4-1-fast-non-reasoning', // 婚姻健康 - 快速响应
crypto: 'grok-4-0709', // 币圈分析 - 币圈知识丰富
};
// 备用模型列表
const FALLBACK_MODELS = [
'gpt-5-mini',
'gpt-4.1',
'gpt-4o',
'grok-4-0709',
'grok-4-1-fast-reasoning',
'claude-sonnet-4-5',
'claude-3-5-haiku-20241022',
];
/**
* 发送SSE事件
*/
export const sendSSE = (res, event, data) => {
if (!res.writableEnded) {
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
}
};
/**
* 单个Agent请求
*/
const makeAgentRequest = async (agentType, model, systemPrompt, userPrompt, timeoutMs = 60000) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
console.log(`[Agent:${agentType}] 使用模型 ${model} 开始请求...`);
const startTime = Date.now();
// 使用新的 API 配置系统
const apiRequest = buildApiRequest(model, systemPrompt, userPrompt, 0.6);
const response = await fetch(apiRequest.url, {
method: 'POST',
headers: apiRequest.headers,
signal: controller.signal,
body: JSON.stringify(apiRequest.body),
});
clearTimeout(timeoutId);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
if (!response.ok) {
const errText = await response.text();
console.warn(`[Agent:${agentType}] 请求失败 (${elapsed}s): ${response.status} - ${errText.substring(0, 200)}`);
return { success: false, agentType, error: `HTTP ${response.status}`, elapsed };
}
const responseText = await response.text();
let jsonResult;
try {
jsonResult = JSON.parse(responseText);
} catch (e) {
console.warn(`[Agent:${agentType}] JSON解析失败 (${elapsed}s)`);
return { success: false, agentType, error: 'INVALID_API_RESPONSE', elapsed };
}
// 使用新的响应解析系统
let content = parseApiResponse(jsonResult, model);
if (!content) {
return { success: false, agentType, error: 'EMPTY_RESPONSE', elapsed };
}
// 清理内容
content = content.trim();
content = content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
content = content.replace(/^[\s\S]*?(?=\{)/m, '');
if (content.startsWith('```json')) content = content.slice(7);
else if (content.startsWith('```')) content = content.slice(3);
if (content.endsWith('```')) content = content.slice(0, -3);
content = content.trim();
const jsonStart = content.indexOf('{');
const jsonEnd = content.lastIndexOf('}');
if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
content = content.slice(jsonStart, jsonEnd + 1);
}
let data;
try {
data = JSON.parse(content);
} catch (parseErr) {
console.warn(`[Agent:${agentType}] 内容JSON解析失败 (${elapsed}s): ${content.substring(0, 100)}`);
return { success: false, agentType, error: 'INVALID_JSON_FORMAT', elapsed };
}
console.log(`[Agent:${agentType}] ✓ 成功 (${elapsed}s)`);
return { success: true, agentType, data, elapsed, model };
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
console.warn(`[Agent:${agentType}] 请求超时`);
return { success: false, agentType, error: 'TIMEOUT' };
}
console.warn(`[Agent:${agentType}] 请求异常: ${error.message}`);
return { success: false, agentType, error: error.message };
}
};
/**
* 验证Agent返回数据是否完整
*/
const validateAgentResponse = (agentType, data) => {
if (!data || typeof data !== 'object') return false;
const requiredFields = {
core: ['summary', 'personality'],
career: ['industry', 'wealth'],
marriage: ['marriage', 'health'],
crypto: ['crypto'],
kline_past: ['chartPoints'],
kline_future: ['chartPoints'],
};
const fields = requiredFields[agentType] || [];
for (const field of fields) {
if (!data[field] || (typeof data[field] === 'string' && data[field].trim().length < 10)) {
console.warn(`[Agent:${agentType}] 字段 ${field} 缺失或内容太短`);
return false;
}
}
return true;
};
/**
* 带重试的Agent请求
*/
const makeAgentRequestWithRetry = async (agentType, apiBaseUrl, apiKey, systemPrompt, userPrompt, maxRetries = 2) => {
const primaryModel = AGENT_MODEL_ASSIGNMENT[agentType] || 'gemini-1.5-pro';
const modelsToTry = [primaryModel, ...FALLBACK_MODELS.filter(m => m !== primaryModel)];
for (const model of modelsToTry) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const result = await makeAgentRequest(agentType, model, systemPrompt, userPrompt);
if (result.success) {
// 验证返回数据是否完整
if (validateAgentResponse(agentType, result.data)) {
return result;
}
console.warn(`[Agent:${agentType}] 模型 ${model} 返回数据不完整,尝试重新请求...`);
}
// 如果是最后一次尝试这个模型,切换到下一个模型
if (attempt === maxRetries) {
console.warn(`[Agent:${agentType}] 模型 ${model} 失败,尝试备用模型...`);
} else {
// 等待后重试
await new Promise(r => setTimeout(r, 1500));
}
}
}
return { success: false, agentType, error: 'ALL_ATTEMPTS_FAILED' };
};
/**
* 构建Agent用户提示词
*/
const buildAgentUserPrompt = (input, skeletonData, agentType) => {
const genderStr = input.gender === 'Male' ? '男 (乾造)' : '女 (坤造)';
// 精简的时间线数据
const timelineStr = JSON.stringify(skeletonData.timeline.slice(0, 30).map(t => ({
a: t.age,
y: t.year,
gz: t.ganZhi,
dy: t.daYun
})));
const baseInfo = `
【命主信息】
性别:${genderStr}
姓名:${input.name || '未提供'}
出生年份:${input.birthYear}
出生地点:${input.birthPlace || '未提供'}
【八字四柱】
年柱:${skeletonData.bazi[0]}
月柱:${skeletonData.bazi[1]}
日柱:${skeletonData.bazi[2]}
时柱:${skeletonData.bazi[3]}
【大运信息】
起运年龄:${skeletonData.startAge}
大运顺逆:${skeletonData.direction}
`;
// 根据Agent类型添加特定信息
const currentYear = new Date().getFullYear();
const birthYear = parseInt(input.birthYear, 10);
const currentAge = currentYear - birthYear + 1;
switch (agentType) {
case 'kline_past': {
// 过去K线:从出生到今年
const pastTimeline = skeletonData.timeline.filter(t => t.year <= currentYear);
const pastTimelineStr = JSON.stringify(pastTimeline.map(t => ({
a: t.age,
y: t.year,
gz: t.ganZhi,
dy: t.daYun
})));
return baseInfo + `\n【当前年份】${currentYear}年(${currentAge}岁)\n【待填充的过去时间轴(出生到今年)】\n${pastTimelineStr}`;
}
case 'kline_future': {
// 未来K线:从今年到100岁
const futureTimeline = skeletonData.timeline.filter(t => t.year >= currentYear);
const futureTimelineStr = JSON.stringify(futureTimeline.map(t => ({
a: t.age,
y: t.year,
gz: t.ganZhi,
dy: t.daYun
})));
return baseInfo + `\n【当前年份】${currentYear}年(${currentAge}岁)\n【待填充的未来时间轴(今年到100岁)】\n${futureTimelineStr}`;
}
case 'core':
return baseInfo + `\n【前30年时间轴参考】\n${timelineStr}\n\n请深度分析此八字的核心命理结构。`;
case 'career':
return baseInfo + `\n请专注分析此八字的事业财富运势。`;
case 'marriage':
return baseInfo + `\n请专注分析此八字的婚姻感情和健康状况。`;
case 'crypto':
return baseInfo + `\n当前年份:${currentYear}\n请专注分析此八字的币圈交易运势和投机潜力。`;
default:
return baseInfo;
}
};
/**
* 并行执行6个Agent分析
* @param {object} input - 用户输入
* @param {object} skeletonData - 时间线骨架
* @param {object} res - SSE响应对象
* @param {function} onProgress - 进度回调
*/
export const runParallelAgents = async (input, skeletonData, res, onProgress) => {
// 不再需要固定的 API URL 和 Key,由 apiConfig.js 根据模型自动选择
const apiBaseUrl = null; // 不再使用
const apiKey = null; // 不再使用
const agents = [
{ type: 'core', prompt: AGENT_CORE_PROMPT, priority: 1 },
{ type: 'kline_past', prompt: AGENT_KLINE_PAST_PROMPT, priority: 2 },
{ type: 'kline_future', prompt: AGENT_KLINE_FUTURE_PROMPT, priority: 3 },
{ type: 'career', prompt: AGENT_CAREER_PROMPT, priority: 4 },
{ type: 'marriage', prompt: AGENT_MARRIAGE_PROMPT, priority: 5 },
{ type: 'crypto', prompt: AGENT_CRYPTO_PROMPT, priority: 6 },
];
onProgress(`启动 ${agents.length} 个专业Agent并行分析...`);
const results = {};
const completedAgents = [];
// 创建所有Agent的Promise
const agentPromises = agents.map(agent => {
const userPrompt = buildAgentUserPrompt(input, skeletonData, agent.type);
return makeAgentRequestWithRetry(
agent.type,
apiBaseUrl,
apiKey,
agent.prompt,
userPrompt
).then(result => {
if (result.success) {
results[agent.type] = result.data;
completedAgents.push(agent.type);
// 立即推送该Agent的结果
sendSSE(res, `agent_${agent.type}_complete`, {
agentType: agent.type,
data: result.data,
elapsed: result.elapsed,
model: result.model,
completedCount: completedAgents.length,
totalAgents: agents.length,
});
onProgress(`✓ Agent[${agent.type}] 完成 (${result.elapsed}s) - 已完成 ${completedAgents.length}/${agents.length}`);
} else {
onProgress(`✗ Agent[${agent.type}] 失败: ${result.error}`);
sendSSE(res, `agent_${agent.type}_error`, {
agentType: agent.type,
error: result.error,
});
}
return result;
});
});
// 等待所有Agent完成
const allResults = await Promise.allSettled(agentPromises);
// 汇总结果
const successCount = allResults.filter(r => r.status === 'fulfilled' && r.value?.success).length;
onProgress(`并行分析完成: ${successCount}/${agents.length} 成功`);
return {
success: successCount > 0,
results,
completedAgents,
totalAgents: agents.length,
successCount,
};
};
/**
* 基于八字生成事业财富降级内容
* @param {object} core - 核心分析结果
* @param {object} skeletonData - 时间线骨架数据
*/
const generateCareerFallback = (core, skeletonData) => {
const bazi = core?.bazi || skeletonData?.bazi || [];
const dayPillar = bazi[2] || '';
const dayGan = dayPillar ? dayPillar[0] : '';
// 基于日主五行推断适合行业
const dayGanIndustries = {
'甲': { industries: '教育、文化、出版、环保、园艺', element: '木' },
'乙': { industries: '设计、美容、花艺、服装、医药', element: '木' },
'丙': { industries: '能源、娱乐、餐饮、传媒、演艺', element: '火' },
'丁': { industries: '科技、电子、文化创意、教育培训', element: '火' },
'戊': { industries: '房地产、建筑、矿业、农业、物流', element: '土' },
'己': { industries: '农业、食品、陶瓷、中介、服务业', element: '土' },
'庚': { industries: '金融、机械、汽车、五金、军工', element: '金' },
'辛': { industries: '珠宝、精密仪器、法律、金融、美容', element: '金' },
'壬': { industries: '物流、航运、旅游、水产、饮料', element: '水' },
'癸': { industries: '咨询、教育、医疗、心理、艺术', element: '水' },
};
const info = dayGanIndustries[dayGan] || { industries: '综合服务类行业', element: '平衡' };
const industry = `根据八字日主「${dayGan || '未知'}」分析,您五行属${info.element},事业适合方向包括:${info.industries}。日主强弱影响事业发展模式,建议结合实际情况选择最适合自己的发展道路。命局中官杀星代表事业机遇,财星代表财富获取能力,需综合判断以获得最佳事业规划。`;
const wealth = `从财运角度分析,八字中财星的强弱决定了财富获取的方式和规模。${dayGan ? `日主${dayGan}` : '您的命局'}具有一定的理财天赋,建议稳健投资为主。正财代表稳定收入如工资薪金,偏财代表投资理财等非固定收入。根据大运流年的不同,财运会有起伏变化,宜把握财运旺盛的年份积极进取。`;
return {
industry,
industryScore: 6,
wealth,
wealthScore: 6,
recommendedIndustries: [
{ name: info.industries.split('、')[0], reason: `五行属${info.element},与命局相合` },
{ name: info.industries.split('、')[1] || '综合服务', reason: '命理分析推荐' }
],
wealthPattern: '正偏财兼有',
wealthPotential: '中等偏上',
};
};
/**
* 合并多个Agent的结果为最终分析
* @param {object} agentResults - 各Agent返回的结果
* @param {object} skeletonData - 时间线骨架数据(用于K线降级)
*/
export const mergeAgentResults = (agentResults, skeletonData = null) => {
const { core, kline_past, kline_future, career, marriage, crypto } = agentResults;
// 如果career数据缺失,生成降级内容
const careerFallback = (!career?.industry || !career?.wealth)
? generateCareerFallback(core, skeletonData)
: null;
if (careerFallback) {
console.log('[mergeAgentResults] Career数据缺失,使用降级内容生成事业财富分析');
}
// K线数据:合并过去和未来的K线数据
let chartPoints = [];
const currentYear = new Date().getFullYear();
// 获取过去K线数据
const pastPoints = kline_past?.chartPoints || [];
// 获取未来K线数据
const futurePoints = kline_future?.chartPoints || [];
// 检查两个K线Agent是否都有数据
const hasPastData = pastPoints.length > 0;
const hasFutureData = futurePoints.length > 0;
if (hasPastData && hasFutureData) {
// 最佳情况:两段K线数据都有,正常合并
const allPoints = [...pastPoints];
// 未来K线避免重复年份
for (const point of futurePoints) {
if (!allPoints.some(p => p.year === point.year && p.age === point.age)) {
allPoints.push(point);
}
}
chartPoints = allPoints.sort((a, b) => a.age - b.age);
console.log(`[mergeAgentResults] K线完整合并: 过去${pastPoints.length}年 + 未来${futurePoints.length}年 = 总${chartPoints.length}年`);
} else if (hasPastData || hasFutureData) {
// 部分数据:只有一段K线数据,使用fallback补全缺失部分
console.warn(`[mergeAgentResults] K线数据不完整: 过去=${pastPoints.length}年, 未来=${futurePoints.length}年,使用fallback补全`);
if (skeletonData) {
const fallbackPoints = generateFallbackKLine(skeletonData);
const existingYears = new Set([
...pastPoints.map(p => p.year),
...futurePoints.map(p => p.year)
]);
// 合并已有数据
const allPoints = [...pastPoints, ...futurePoints];
// 用fallback填补缺失的年份
for (const point of fallbackPoints) {
if (!existingYears.has(point.year)) {
allPoints.push(point);
}
}
chartPoints = allPoints.sort((a, b) => a.age - b.age);
console.log(`[mergeAgentResults] K线混合合并: AI数据${pastPoints.length + futurePoints.length}年 + Fallback补全 = 总${chartPoints.length}年`);
} else {
// 无skeleton数据,只能用现有数据
chartPoints = [...pastPoints, ...futurePoints].sort((a, b) => a.age - b.age);
console.warn(`[mergeAgentResults] 无skeleton数据,仅使用部分K线: ${chartPoints.length}年`);
}
} else if (skeletonData) {
// 两段K线都失败,完全使用降级算法
console.log('[mergeAgentResults] K线Agent全部失败,使用完整降级算法生成K线数据');
chartPoints = generateFallbackKLine(skeletonData);
}
// 最终数据验证:确保数据点数量足够(至少50年)
const MIN_CHART_POINTS = 50;
if (chartPoints.length < MIN_CHART_POINTS && skeletonData) {
console.warn(`[mergeAgentResults] K线数据不足(${chartPoints.length}点 < ${MIN_CHART_POINTS}点),使用完整fallback替换`);
chartPoints = generateFallbackKLine(skeletonData);
console.log(`[mergeAgentResults] Fallback生成完成: ${chartPoints.length}年`);
}
// 合并过去和未来的关键事件
const pastEvents = kline_past?.pastEvents || core?.pastEvents || [];
const futureEvents = kline_future?.futureEvents || core?.futureEvents || [];
const keyYears = [
...(kline_past?.keyYears || []),
...(kline_future?.keyYears || [])
].sort((a, b) => a.year - b.year);
return {
// 基础信息
bazi: core?.bazi || [],
summary: core?.summary || '命理分析完成',
summaryScore: core?.summaryScore || 5,
// 核心Agent - 性格/六亲/风水
personality: core?.personality || '',
personalityScore: core?.personalityScore || 5,
family: core?.family || '',
familyScore: core?.familyScore || 5,
fengShui: core?.fengShui || '',
fengShuiScore: core?.fengShuiScore || 5,
// 个人特征
appearance: core?.appearance || '',
bodyType: core?.bodyType || '',
skin: core?.skin || '',
characterSummary: core?.characterSummary || '',
// 事业Agent - 使用fallback如果原数据缺失
industry: career?.industry || careerFallback?.industry || '暂无事业分析,请稍后重试',
industryScore: career?.industryScore || careerFallback?.industryScore || 5,
wealth: career?.wealth || careerFallback?.wealth || '暂无财富分析,请稍后重试',
wealthScore: career?.wealthScore || careerFallback?.wealthScore || 5,
// 婚姻健康Agent
marriage: marriage?.marriage || '',
marriageScore: marriage?.marriageScore || 5,
health: marriage?.health || '',
healthScore: marriage?.healthScore || 5,
healthBodyParts: marriage?.healthBodyParts || [],
// 币圈Agent
crypto: crypto?.crypto || '',
cryptoScore: crypto?.cryptoScore || 5,
cryptoYear: crypto?.cryptoYear || '待定',
cryptoStyle: crypto?.cryptoStyle || '现货定投',
// K线Agent - 使用已计算的chartPoints(含降级逻辑)
chartPoints: chartPoints,
// 运势预测
monthlyFortune: core?.monthlyFortune || marriage?.monthlyFortune || '',
monthlyHighlights: core?.monthlyHighlights || [],
yearlyFortune: core?.yearlyFortune || career?.yearlyFortune || '',
yearlyKeyEvents: core?.yearlyKeyEvents || career?.yearlyKeyEvents || [],
// 幸运元素
luckyColors: core?.luckyColors || [],
luckyDirections: core?.luckyDirections || [],
luckyZodiac: core?.luckyZodiac || [],
luckyNumbers: core?.luckyNumbers || [],
// 重点日期和事件
keyDatesThisYear: core?.keyDatesThisYear || [],
keyDatesThisMonth: core?.keyDatesThisMonth || [],
pastEvents: pastEvents,
futureEvents: futureEvents,
keyYears: keyYears,
};
};
export default {
runParallelAgents,
mergeAgentResults,
sendSSE,
};