File size: 20,479 Bytes
e375f30 25fe4a1 e375f30 25fe4a1 e375f30 25fe4a1 e375f30 7ce979b e375f30 7ce979b e375f30 8e4faae 1f1edd2 8e4faae e375f30 7ce979b e375f30 1f1edd2 7ce979b 8e4faae 04355f4 e375f30 7ce979b e375f30 7ce979b e375f30 7ce979b e375f30 7ce979b e375f30 7ce979b e375f30 7ce979b c440ffa e375f30 291850b e375f30 291850b aba2af9 e375f30 7ce979b 291850b e375f30 291850b e375f30 291850b e375f30 25fe4a1 e375f30 25fe4a1 e375f30 25fe4a1 e375f30 25fe4a1 e375f30 7ce979b e375f30 25fe4a1 e375f30 291850b e375f30 25fe4a1 e375f30 291850b 25fe4a1 e375f30 25fe4a1 e375f30 25fe4a1 e375f30 25fe4a1 e375f30 291850b e375f30 25fe4a1 e375f30 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 |
/**
* 并行分析器 - 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,
};
|