xiaobo ren commited on
Commit
7ce979b
·
1 Parent(s): ded900d

Implement multi-API support: ChatGPT, Grok, Claude, and Gemini with automatic routing

Browse files
server/analyzeStream.js CHANGED
@@ -9,15 +9,16 @@ import {
9
  import { BAZI_SYSTEM_INSTRUCTION, buildUserPrompt } from './prompt.js';
10
  import { calculateLifeTimeline } from './baziCalculator.js';
11
 
12
- const DEFAULT_API_BASE_URL = process.env.API_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta';
13
- const DEFAULT_API_KEY = process.env.API_KEY || 'AIzaSyBiz_9_zhb3c9mxl3dePVfIv1iiao-_6dw'; // Google Gemini API密钥
14
- const DEFAULT_MODEL = process.env.DEFAULT_MODEL || 'gemini-1.5-pro';
15
 
16
- // 备选模型列表 - 用于并发请求和降级(Google Gemini 支持的模型)
 
 
17
  const ALL_MODELS = [
 
 
 
18
  'gemini-1.5-pro',
19
- 'gemini-1.5-flash',
20
- 'gemini-pro',
21
  ];
22
 
23
  const COST_PER_ANALYSIS = process.env.COST_PER_ANALYSIS ? parseInt(process.env.COST_PER_ANALYSIS, 10) : 50;
@@ -35,7 +36,7 @@ const sendSSE = (res, event, data) => {
35
  /**
36
  * 单次API请求 - 返回Promise
37
  */
38
- const makeModelRequest = async (model, apiBaseUrl, apiKey, userPrompt, timeoutMs = 120000) => {
39
  const controller = new AbortController();
40
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
41
 
@@ -43,44 +44,14 @@ const makeModelRequest = async (model, apiBaseUrl, apiKey, userPrompt, timeoutMs
43
  console.log(`[${model}] 开始请求...`);
44
  const startTime = Date.now();
45
 
46
- // 检查是否是 Google Gemini API
47
- const isGeminiApi = apiKey.startsWith('AIzaSy');
48
- const apiUrl = isGeminiApi
49
- ? `${apiBaseUrl}/models/${model}:generateContent?key=${apiKey}`
50
- : `${apiBaseUrl}/chat/completions`;
51
-
52
- const headers = isGeminiApi
53
- ? { 'Content-Type': 'application/json' }
54
- : {
55
- 'Content-Type': 'application/json',
56
- Authorization: `Bearer ${apiKey}`,
57
- };
58
-
59
- const requestBody = isGeminiApi
60
- ? {
61
- contents: [{
62
- parts: [{
63
- text: `${BAZI_SYSTEM_INSTRUCTION}\n\n${userPrompt}`
64
- }]
65
- }],
66
- generationConfig: {
67
- temperature: 0.7,
68
- }
69
- }
70
- : {
71
- model: model,
72
- messages: [
73
- { role: 'system', content: BAZI_SYSTEM_INSTRUCTION },
74
- { role: 'user', content: userPrompt },
75
- ],
76
- temperature: 0.7,
77
- };
78
-
79
- const response = await fetch(apiUrl, {
80
  method: 'POST',
81
- headers: headers,
82
  signal: controller.signal,
83
- body: JSON.stringify(requestBody),
84
  });
85
 
86
  clearTimeout(timeoutId);
@@ -101,13 +72,8 @@ const makeModelRequest = async (model, apiBaseUrl, apiKey, userPrompt, timeoutMs
101
  return { success: false, model, error: 'INVALID_API_RESPONSE', elapsed };
102
  }
103
 
104
- // 处理 Google Gemini API 响应格式
105
- let content;
106
- if (isGeminiApi && jsonResult.candidates?.[0]?.content?.parts?.[0]?.text) {
107
- content = jsonResult.candidates[0].content.parts[0].text;
108
- } else {
109
- content = jsonResult.choices?.[0]?.message?.content;
110
- }
111
  if (!content) {
112
  console.warn(`[${model}] 无内容返回 (${elapsed}s)`);
113
  return { success: false, model, error: 'EMPTY_RESPONSE', elapsed };
@@ -158,12 +124,12 @@ const makeModelRequest = async (model, apiBaseUrl, apiKey, userPrompt, timeoutMs
158
  /**
159
  * 并发请求多个模型,返回第一个成功的结果
160
  */
161
- const raceModels = async (models, apiBaseUrl, apiKey, userPrompt, onProgress) => {
162
  onProgress(`正在并发请求 ${models.length} 个模型...`);
163
 
164
  // 创建所有请求的Promise
165
  const promises = models.map(model =>
166
- makeModelRequest(model, apiBaseUrl, apiKey, userPrompt, 180000)
167
  );
168
 
169
  // 使用Promise.allSettled等待所有请求完成,但我们会在第一个成功时就返回
@@ -228,11 +194,13 @@ export const handleAnalyzeStream = async (req, res) => {
228
  };
229
 
230
  if (!useCustomApi) {
231
- apiBaseUrl = DEFAULT_API_BASE_URL;
232
- apiKey = DEFAULT_API_KEY;
 
233
  modelName = DEFAULT_MODEL;
234
 
235
- if (!DEFAULT_API_KEY || DEFAULT_API_KEY === 'sk-example-key') {
 
236
  sendSSE(res, 'error', {
237
  error: 'SERVER_DEFAULT_KEY_NOT_SET',
238
  message: '服务器未配置API密钥,请使用自定义API或联系管理员'
@@ -296,7 +264,7 @@ export const handleAnalyzeStream = async (req, res) => {
296
 
297
  for (let attempt = 1; attempt <= 3; attempt++) {
298
  onProgress(`尝试第 ${attempt} 次...`);
299
- const response = await makeModelRequest(modelName, apiBaseUrl, apiKey, userPrompt, 60000);
300
 
301
  if (response.success) {
302
  result = response.data;
@@ -316,8 +284,8 @@ export const handleAnalyzeStream = async (req, res) => {
316
  onProgress('启动多模型并发请求策略...');
317
 
318
  // 第一轮:并发请求主模型和一个备选模型
319
- const firstRoundModels = [modelName, 'gemini-1.5-flash'];
320
- let raceResult = await raceModels(firstRoundModels, apiBaseUrl, apiKey, userPrompt, onProgress);
321
 
322
  if (raceResult.success) {
323
  result = raceResult.data;
@@ -325,8 +293,8 @@ export const handleAnalyzeStream = async (req, res) => {
325
  } else {
326
  // 第二轮:尝试其他模型
327
  onProgress('第一轮失败,启动第二轮备选模型...');
328
- const secondRoundModels = ['gemini-1.5-pro', 'gemini-pro'];
329
- raceResult = await raceModels(secondRoundModels, apiBaseUrl, apiKey, userPrompt, onProgress);
330
 
331
  if (raceResult.success) {
332
  result = raceResult.data;
@@ -339,7 +307,7 @@ export const handleAnalyzeStream = async (req, res) => {
339
  onProgress('并发请求全部失败,尝试逐个请求...');
340
  for (const model of ALL_MODELS) {
341
  onProgress(`最后尝试: ${model}...`);
342
- const response = await makeModelRequest(model, apiBaseUrl, apiKey, userPrompt, 45000);
343
  if (response.success) {
344
  result = response.data;
345
  usedModel = model;
 
9
  import { BAZI_SYSTEM_INSTRUCTION, buildUserPrompt } from './prompt.js';
10
  import { calculateLifeTimeline } from './baziCalculator.js';
11
 
12
+ import { buildApiRequest, parseApiResponse } from './apiConfig.js';
 
 
13
 
14
+ const DEFAULT_MODEL = process.env.DEFAULT_MODEL || 'gpt-4o';
15
+
16
+ // 备选模型列表 - 用于并发请求和降级
17
  const ALL_MODELS = [
18
+ 'gpt-4o',
19
+ 'grok-4',
20
+ 'claude-3-5-sonnet-20241022',
21
  'gemini-1.5-pro',
 
 
22
  ];
23
 
24
  const COST_PER_ANALYSIS = process.env.COST_PER_ANALYSIS ? parseInt(process.env.COST_PER_ANALYSIS, 10) : 50;
 
36
  /**
37
  * 单次API请求 - 返回Promise
38
  */
39
+ const makeModelRequest = async (model, userPrompt, timeoutMs = 120000) => {
40
  const controller = new AbortController();
41
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
42
 
 
44
  console.log(`[${model}] 开始请求...`);
45
  const startTime = Date.now();
46
 
47
+ // 使用新的 API 配置系统
48
+ const apiRequest = buildApiRequest(model, BAZI_SYSTEM_INSTRUCTION, userPrompt, 0.7);
49
+
50
+ const response = await fetch(apiRequest.url, {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  method: 'POST',
52
+ headers: apiRequest.headers,
53
  signal: controller.signal,
54
+ body: JSON.stringify(apiRequest.body),
55
  });
56
 
57
  clearTimeout(timeoutId);
 
72
  return { success: false, model, error: 'INVALID_API_RESPONSE', elapsed };
73
  }
74
 
75
+ // 使用新的响应解析系统
76
+ const content = parseApiResponse(jsonResult, model);
 
 
 
 
 
77
  if (!content) {
78
  console.warn(`[${model}] 无内容返回 (${elapsed}s)`);
79
  return { success: false, model, error: 'EMPTY_RESPONSE', elapsed };
 
124
  /**
125
  * 并发请求多个模型,返回第一个成功的结果
126
  */
127
+ const raceModels = async (models, userPrompt, onProgress) => {
128
  onProgress(`正在并发请求 ${models.length} 个模型...`);
129
 
130
  // 创建所有请求的Promise
131
  const promises = models.map(model =>
132
+ makeModelRequest(model, userPrompt, 180000)
133
  );
134
 
135
  // 使用Promise.allSettled等待所有请求完成,但我们会在第一个成功时就返回
 
194
  };
195
 
196
  if (!useCustomApi) {
197
+ // 不再需要固定的 API URL 和 Key,由 apiConfig.js 根据模型自动选择
198
+ apiBaseUrl = null; // 不再使用
199
+ apiKey = null; // 不再使用
200
  modelName = DEFAULT_MODEL;
201
 
202
+ // API 配置由 apiConfig.js 管理,无需检查
203
+ if (false) {
204
  sendSSE(res, 'error', {
205
  error: 'SERVER_DEFAULT_KEY_NOT_SET',
206
  message: '服务器未配置API密钥,请使用自定义API或联系管理员'
 
264
 
265
  for (let attempt = 1; attempt <= 3; attempt++) {
266
  onProgress(`尝试第 ${attempt} 次...`);
267
+ const response = await makeModelRequest(modelName, userPrompt, 60000);
268
 
269
  if (response.success) {
270
  result = response.data;
 
284
  onProgress('启动多模型并发请求策略...');
285
 
286
  // 第一轮:并发请求主模型和一个备选模型
287
+ const firstRoundModels = [modelName, 'gpt-4o'];
288
+ let raceResult = await raceModels(firstRoundModels, userPrompt, onProgress);
289
 
290
  if (raceResult.success) {
291
  result = raceResult.data;
 
293
  } else {
294
  // 第二轮:尝试其他模型
295
  onProgress('第一轮失败,启动第二轮备选模型...');
296
+ const secondRoundModels = ['grok-4', 'claude-3-5-sonnet-20241022'];
297
+ raceResult = await raceModels(secondRoundModels, userPrompt, onProgress);
298
 
299
  if (raceResult.success) {
300
  result = raceResult.data;
 
307
  onProgress('并发请求全部失败,尝试逐个请求...');
308
  for (const model of ALL_MODELS) {
309
  onProgress(`最后尝试: ${model}...`);
310
+ const response = await makeModelRequest(model, userPrompt, 45000);
311
  if (response.success) {
312
  result = response.data;
313
  usedModel = model;
server/apiConfig.js ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * 多 API 配置和路由
3
+ * 根据模型名称自动选择正确的 API 端点和密钥
4
+ */
5
+
6
+ // API 配置
7
+ const API_CONFIGS = {
8
+ // Google Gemini
9
+ gemini: {
10
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
11
+ apiKey: process.env.GEMINI_API_KEY || 'AIzaSyBiz_9_zhb3c9mxl3dePVfIv1iiao-_6dw',
12
+ models: ['gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-pro', 'gemini-3-pro-preview'],
13
+ format: 'gemini', // 使用 Gemini 原生格式
14
+ },
15
+
16
+ // OpenAI (ChatGPT)
17
+ openai: {
18
+ baseUrl: 'https://api.openai.com/v1',
19
+ apiKey: process.env.OPENAI_API_KEY || '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',
20
+ models: ['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'gpt-4o', 'gpt-4o-mini'],
21
+ format: 'openai', // 使用 OpenAI 格式
22
+ },
23
+
24
+ // Grok (xAI)
25
+ grok: {
26
+ baseUrl: 'https://api.x.ai/v1',
27
+ apiKey: process.env.GROK_API_KEY || 'xai-5nPioW6r81dYiWzr6jYkLewyjM32YHEP2PWU2oRAcIpwHsaqL2azw3mnLu8PDNuI7ErkkHxn7jjejWpR',
28
+ models: ['grok-4', 'grok-4-auto', 'grok-4-1-non-thinking-w-tool', 'grok-4-mini-thinking-tahoe', 'grok-beta'],
29
+ format: 'openai', // Grok 使用 OpenAI 兼容格式
30
+ },
31
+
32
+ // Claude (Anthropic)
33
+ claude: {
34
+ baseUrl: 'https://api.anthropic.com/v1',
35
+ apiKey: process.env.CLAUDE_API_KEY || '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',
36
+ models: ['claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-5-haiku-20241022', 'claude-haiku-4-5-20251001', 'claude-3-5-sonnet'],
37
+ format: 'claude', // 使用 Claude 格式
38
+ },
39
+ };
40
+
41
+ /**
42
+ * 根据模型名称获取 API 配置
43
+ */
44
+ export function getApiConfigForModel(modelName) {
45
+ // 检查每个 API 提供商的模型列表
46
+ for (const [provider, config] of Object.entries(API_CONFIGS)) {
47
+ if (config.models.includes(modelName)) {
48
+ return { ...config, provider };
49
+ }
50
+ }
51
+
52
+ // 默认使用 Gemini
53
+ return { ...API_CONFIGS.gemini, provider: 'gemini' };
54
+ }
55
+
56
+ /**
57
+ * 构建 API 请求
58
+ */
59
+ export function buildApiRequest(modelName, systemPrompt, userPrompt, temperature = 0.7) {
60
+ const config = getApiConfigForModel(modelName);
61
+
62
+ if (config.format === 'gemini') {
63
+ // Google Gemini 格式
64
+ return {
65
+ url: `${config.baseUrl}/models/${modelName}:generateContent?key=${config.apiKey}`,
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: {
68
+ contents: [{
69
+ parts: [{
70
+ text: `${systemPrompt}\n\n${userPrompt}`
71
+ }]
72
+ }],
73
+ generationConfig: {
74
+ temperature: temperature,
75
+ }
76
+ }
77
+ };
78
+ } else if (config.format === 'claude') {
79
+ // Claude 格式
80
+ return {
81
+ url: `${config.baseUrl}/messages`,
82
+ headers: {
83
+ 'Content-Type': 'application/json',
84
+ 'x-api-key': config.apiKey,
85
+ 'anthropic-version': '2023-06-01',
86
+ },
87
+ body: {
88
+ model: modelName,
89
+ max_tokens: 8192,
90
+ temperature: temperature,
91
+ messages: [
92
+ { role: 'user', content: `${systemPrompt}\n\n${userPrompt}` }
93
+ ]
94
+ }
95
+ };
96
+ } else {
97
+ // OpenAI 兼容格式 (OpenAI, Grok)
98
+ return {
99
+ url: `${config.baseUrl}/chat/completions`,
100
+ headers: {
101
+ 'Content-Type': 'application/json',
102
+ 'Authorization': `Bearer ${config.apiKey}`,
103
+ },
104
+ body: {
105
+ model: modelName,
106
+ messages: [
107
+ { role: 'system', content: systemPrompt },
108
+ { role: 'user', content: userPrompt },
109
+ ],
110
+ temperature: temperature,
111
+ }
112
+ };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 解析 API 响应
118
+ */
119
+ export function parseApiResponse(responseJson, modelName) {
120
+ const config = getApiConfigForModel(modelName);
121
+
122
+ if (config.format === 'gemini') {
123
+ // Google Gemini 响应格式
124
+ return responseJson.candidates?.[0]?.content?.parts?.[0]?.text || null;
125
+ } else if (config.format === 'claude') {
126
+ // Claude 响应格式
127
+ return responseJson.content?.[0]?.text || null;
128
+ } else {
129
+ // OpenAI 兼容格式
130
+ return responseJson.choices?.[0]?.message?.content || null;
131
+ }
132
+ }
133
+
134
+ export default API_CONFIGS;
135
+
server/celebrityAnalyzer.js CHANGED
@@ -5,12 +5,11 @@
5
  import fetch from 'node-fetch';
6
  import { AGENT_CELEBRITY_ANALYSIS_PROMPT } from './agentPrompts.js';
7
 
8
- const DEFAULT_API_BASE_URL = process.env.API_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta';
9
- const DEFAULT_API_KEY = process.env.API_KEY || 'AIzaSyBiz_9_zhb3c9mxl3dePVfIv1iiao-_6dw'; // Google Gemini API密钥
10
 
11
  // 名人分析使用的模型 - 需要高质量输出
12
- const CELEBRITY_ANALYSIS_MODEL = 'gemini-1.5-pro';
13
- const FALLBACK_MODELS = ['gemini-1.5-flash', 'gemini-pro'];
14
 
15
  /**
16
  * 构建名人分析的用户提示词
@@ -127,46 +126,21 @@ async function callLLMApi(model, userPrompt, timeoutMs = 120000) {
127
  console.log(`[celebrityAnalyzer] 使用模型 ${model} 开始生成分析...`);
128
  const startTime = Date.now();
129
 
130
- // 检查是否是 Google Gemini API
131
- const isGeminiApi = DEFAULT_API_KEY.startsWith('AIzaSy');
132
- const apiUrl = isGeminiApi
133
- ? `${DEFAULT_API_BASE_URL}/models/${model}:generateContent?key=${DEFAULT_API_KEY}`
134
- : `${DEFAULT_API_BASE_URL}/chat/completions`;
135
 
136
- const headers = isGeminiApi
137
- ? { 'Content-Type': 'application/json' }
138
- : {
139
- 'Content-Type': 'application/json',
140
- Authorization: `Bearer ${DEFAULT_API_KEY}`,
141
- };
142
-
143
- const requestBody = isGeminiApi
144
- ? {
145
- contents: [{
146
- parts: [{
147
- text: `${AGENT_CELEBRITY_ANALYSIS_PROMPT}\n\n${userPrompt}`
148
- }]
149
- }],
150
- generationConfig: {
151
- temperature: 0.7,
152
- maxOutputTokens: 8000,
153
- }
154
- }
155
- : {
156
- model: model,
157
- messages: [
158
- { role: 'system', content: AGENT_CELEBRITY_ANALYSIS_PROMPT },
159
- { role: 'user', content: userPrompt },
160
- ],
161
- temperature: 0.7,
162
- max_tokens: 8000,
163
- };
164
 
165
- const response = await fetch(apiUrl, {
166
  method: 'POST',
167
- headers: headers,
168
  signal: controller.signal,
169
- body: JSON.stringify(requestBody),
170
  });
171
 
172
  clearTimeout(timeoutId);
@@ -180,13 +154,8 @@ async function callLLMApi(model, userPrompt, timeoutMs = 120000) {
180
 
181
  const responseJson = await response.json();
182
 
183
- // 处理 Google Gemini API 响应格式
184
- let content;
185
- if (isGeminiApi && responseJson.candidates?.[0]?.content?.parts?.[0]?.text) {
186
- content = responseJson.candidates[0].content.parts[0].text;
187
- } else {
188
- content = responseJson.choices?.[0]?.message?.content;
189
- }
190
 
191
  if (!content) {
192
  return { success: false, error: 'EMPTY_RESPONSE', elapsed };
 
5
  import fetch from 'node-fetch';
6
  import { AGENT_CELEBRITY_ANALYSIS_PROMPT } from './agentPrompts.js';
7
 
8
+ import { buildApiRequest, parseApiResponse } from './apiConfig.js';
 
9
 
10
  // 名人分析使用的模型 - 需要高质量输出
11
+ const CELEBRITY_ANALYSIS_MODEL = 'gpt-4o';
12
+ const FALLBACK_MODELS = ['grok-4', 'claude-3-5-sonnet-20241022', 'gemini-1.5-pro'];
13
 
14
  /**
15
  * 构建名人分析的用户提示词
 
126
  console.log(`[celebrityAnalyzer] 使用模型 ${model} 开始生成分析...`);
127
  const startTime = Date.now();
128
 
129
+ // 使用新的 API 配置系统
130
+ const apiRequest = buildApiRequest(model, AGENT_CELEBRITY_ANALYSIS_PROMPT, userPrompt, 0.7);
 
 
 
131
 
132
+ // 如果是 OpenAI 格式,添加 max_tokens
133
+ if (apiRequest.body.max_tokens === undefined && apiRequest.body.generationConfig) {
134
+ apiRequest.body.generationConfig.maxOutputTokens = 8000;
135
+ } else if (apiRequest.body.max_tokens === undefined) {
136
+ apiRequest.body.max_tokens = 8000;
137
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ const response = await fetch(apiRequest.url, {
140
  method: 'POST',
141
+ headers: apiRequest.headers,
142
  signal: controller.signal,
143
+ body: JSON.stringify(apiRequest.body),
144
  });
145
 
146
  clearTimeout(timeoutId);
 
154
 
155
  const responseJson = await response.json();
156
 
157
+ // 使用新的响应解析系统
158
+ const content = parseApiResponse(responseJson, model);
 
 
 
 
 
159
 
160
  if (!content) {
161
  return { success: false, error: 'EMPTY_RESPONSE', elapsed };
server/index.js CHANGED
@@ -82,21 +82,24 @@ import { AGENT_DAILY_FORTUNE_PROMPT } from './agentPrompts.js';
82
  import { generateCelebrityAnalysis } from './celebrityAnalyzer.js';
83
  import { regenerateCoreDocument } from './coreDocumentEngine.js';
84
  import { startEmailScheduler } from './emailScheduler.js';
 
85
 
86
  dotenv.config();
87
 
88
  const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : (process.env.SPACE_ID ? 7860 : 3000);
89
  const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-me';
90
 
91
- // 使用 OpenAI 兼容的 Gemini API 代理服务
92
- const DEFAULT_API_BASE_URL = process.env.API_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta';
93
- const DEFAULT_API_KEY = process.env.API_KEY || 'AIzaSyBiz_9_zhb3c9mxl3dePVfIv1iiao-_6dw'; // Google Gemini API密钥
94
- const DEFAULT_MODEL = process.env.DEFAULT_MODEL || 'gemini-1.5-pro';
95
 
96
- // 模型降级列表:当主模型失败时依次尝试(Google Gemini 支持的模型)
 
 
97
  const FALLBACK_MODELS = [
98
- 'gemini-1.5-flash',
99
- 'gemini-pro',
 
 
100
  ];
101
 
102
  const FREE_INIT_POINTS = process.env.FREE_INIT_POINTS ? parseInt(process.env.FREE_INIT_POINTS, 10) : 1000;
@@ -133,6 +136,25 @@ const getAuthedUser = (req) => {
133
 
134
  app.get('/api/health', (_req, res) => res.json({ ok: true }));
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  app.post('/api/auth/register', async (req, res) => {
137
  const email = sanitizeEmail(req.body?.email);
138
  const password = String(req.body?.password || '');
@@ -310,17 +332,12 @@ app.post('/api/analyze', async (req, res) => {
310
  }
311
  // 未登录用户可以免费体验一次(游客模式,不扣点)
312
 
313
- apiBaseUrl = DEFAULT_API_BASE_URL;
314
- apiKey = DEFAULT_API_KEY;
 
315
  modelName = DEFAULT_MODEL;
316
 
317
- // 检查 API 配置
318
- if (!DEFAULT_API_KEY || DEFAULT_API_KEY === 'sk-example-key') {
319
- return res.status(500).json({
320
- error: 'SERVER_DEFAULT_KEY_NOT_SET',
321
- message: 'Please configure API key on server or use custom API with your own key'
322
- });
323
- }
324
  } else {
325
  if (!apiBaseUrl || !apiKey || !modelName) return res.status(400).json({ error: 'MISSING_CUSTOM_API_CONFIG' });
326
  }
@@ -347,50 +364,19 @@ app.post('/api/analyze', async (req, res) => {
347
  }
348
 
349
  // 单次请求函数
350
- const makeRequest = async (currentModel, currentApiBaseUrl, currentApiKey) => {
351
  const controller = new AbortController();
352
  const timeoutId = setTimeout(() => controller.abort(), 180000); // 180秒超时
353
 
354
  try {
355
- // Google Gemini API 使用不同端点格式
356
- // 检查是否是 Google Gemini API(密钥以 AIzaSy 开头)
357
- const isGeminiApi = currentApiKey.startsWith('AIzaSy');
358
- const apiUrl = isGeminiApi
359
- ? `${currentApiBaseUrl}/models/${currentModel}:generateContent?key=${currentApiKey}`
360
- : `${currentApiBaseUrl}/chat/completions`;
361
 
362
- const headers = isGeminiApi
363
- ? { 'Content-Type': 'application/json' }
364
- : {
365
- 'Content-Type': 'application/json',
366
- Authorization: `Bearer ${currentApiKey}`,
367
- };
368
-
369
- const requestBody = isGeminiApi
370
- ? {
371
- contents: [{
372
- parts: [{
373
- text: `${BAZI_SYSTEM_INSTRUCTION}\n\n${userPrompt}`
374
- }]
375
- }],
376
- generationConfig: {
377
- temperature: 0.7,
378
- }
379
- }
380
- : {
381
- model: currentModel,
382
- messages: [
383
- { role: 'system', content: BAZI_SYSTEM_INSTRUCTION },
384
- { role: 'user', content: userPrompt },
385
- ],
386
- temperature: 0.7,
387
- };
388
-
389
- const response = await fetch(apiUrl, {
390
  method: 'POST',
391
- headers: headers,
392
  signal: controller.signal,
393
- body: JSON.stringify(requestBody),
394
  });
395
 
396
  clearTimeout(timeoutId);
@@ -412,7 +398,7 @@ app.post('/api/analyze', async (req, res) => {
412
  while (retryCount <= maxRetries) {
413
  try {
414
  console.log(`尝试模型: ${currentModel} (第${retryCount + 1}次)`);
415
- const response = await makeRequest(currentModel, apiBaseUrl, apiKey);
416
 
417
  if (response.ok) {
418
  return { success: true, response, model: currentModel };
@@ -495,14 +481,8 @@ app.post('/api/analyze', async (req, res) => {
495
  });
496
  }
497
 
498
- // 处理 Google Gemini API 响应格式
499
- const isGeminiApi = apiKey.startsWith('AIzaSy');
500
- let content;
501
- if (isGeminiApi && jsonResult.candidates?.[0]?.content?.parts?.[0]?.text) {
502
- content = jsonResult.candidates[0].content.parts[0].text;
503
- } else {
504
- content = jsonResult.choices?.[0]?.message?.content;
505
- }
506
  if (!content) return res.status(502).json({ error: 'EMPTY_MODEL_RESPONSE' });
507
 
508
  // 清理可能的 markdown 代码块标记
@@ -2531,45 +2511,21 @@ async function generateDailyFortuneAI(bazi, dateKey, dayGanZhi, lunarDateStr, pr
2531
  `;
2532
 
2533
  try {
2534
- // 检查是否是 Google Gemini API
2535
- const isGeminiApi = DEFAULT_API_KEY.startsWith('AIzaSy');
2536
- const apiUrl = isGeminiApi
2537
- ? `${DEFAULT_API_BASE_URL}/models/gemini-1.5-pro:generateContent?key=${DEFAULT_API_KEY}`
2538
- : `${DEFAULT_API_BASE_URL}/chat/completions`;
2539
-
2540
- const headers = isGeminiApi
2541
- ? { 'Content-Type': 'application/json' }
2542
- : {
2543
- 'Content-Type': 'application/json',
2544
- 'Authorization': `Bearer ${DEFAULT_API_KEY}`,
2545
- };
2546
 
2547
- const requestBody = isGeminiApi
2548
- ? {
2549
- contents: [{
2550
- parts: [{
2551
- text: `${AGENT_DAILY_FORTUNE_PROMPT}\n\n${userPrompt}`
2552
- }]
2553
- }],
2554
- generationConfig: {
2555
- temperature: 0.7,
2556
- maxOutputTokens: 4000,
2557
- }
2558
- }
2559
- : {
2560
- model: 'gemini-1.5-pro',
2561
- messages: [
2562
- { role: 'system', content: AGENT_DAILY_FORTUNE_PROMPT },
2563
- { role: 'user', content: userPrompt },
2564
- ],
2565
- max_tokens: 4000,
2566
- temperature: 0.7,
2567
- };
2568
-
2569
- const response = await fetch(apiUrl, {
2570
  method: 'POST',
2571
- headers: headers,
2572
- body: JSON.stringify(requestBody),
2573
  });
2574
 
2575
  if (!response.ok) {
@@ -2578,13 +2534,8 @@ async function generateDailyFortuneAI(bazi, dateKey, dayGanZhi, lunarDateStr, pr
2578
 
2579
  const data = await response.json();
2580
 
2581
- // 处理 Google Gemini API 响应格式
2582
- let content;
2583
- if (isGeminiApi && data.candidates?.[0]?.content?.parts?.[0]?.text) {
2584
- content = data.candidates[0].content.parts[0].text;
2585
- } else {
2586
- content = data.choices?.[0]?.message?.content;
2587
- }
2588
 
2589
  if (!content) {
2590
  throw new Error('Empty AI response');
 
82
  import { generateCelebrityAnalysis } from './celebrityAnalyzer.js';
83
  import { regenerateCoreDocument } from './coreDocumentEngine.js';
84
  import { startEmailScheduler } from './emailScheduler.js';
85
+ import { buildApiRequest, parseApiResponse, getApiConfigForModel } from './apiConfig.js';
86
 
87
  dotenv.config();
88
 
89
  const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : (process.env.SPACE_ID ? 7860 : 3000);
90
  const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-me';
91
 
92
+ // API 配置 - 使用 apiConfig.js 进行智能路由
93
+ import { buildApiRequest, parseApiResponse, getApiConfigForModel } from './apiConfig.js';
 
 
94
 
95
+ const DEFAULT_MODEL = process.env.DEFAULT_MODEL || 'gpt-4o';
96
+
97
+ // 模型降级列表:当主模型失败时依次尝试
98
  const FALLBACK_MODELS = [
99
+ 'gpt-4o',
100
+ 'grok-4',
101
+ 'claude-3-5-sonnet-20241022',
102
+ 'gemini-1.5-pro',
103
  ];
104
 
105
  const FREE_INIT_POINTS = process.env.FREE_INIT_POINTS ? parseInt(process.env.FREE_INIT_POINTS, 10) : 1000;
 
136
 
137
  app.get('/api/health', (_req, res) => res.json({ ok: true }));
138
 
139
+ // API 连接测试端点
140
+ app.get('/api/test-apis', async (_req, res) => {
141
+ try {
142
+ const { testAllApis } = await import('./testApi.js');
143
+ const results = await testAllApis();
144
+ return res.json({
145
+ success: true,
146
+ results,
147
+ timestamp: new Date().toISOString(),
148
+ });
149
+ } catch (error) {
150
+ console.error('API 测试失败:', error);
151
+ return res.status(500).json({
152
+ success: false,
153
+ error: error.message
154
+ });
155
+ }
156
+ });
157
+
158
  app.post('/api/auth/register', async (req, res) => {
159
  const email = sanitizeEmail(req.body?.email);
160
  const password = String(req.body?.password || '');
 
332
  }
333
  // 未登录用户可以免费体验一次(游客模式,不扣点)
334
 
335
+ // 不再需要固定的 API URL 和 Key,由 apiConfig.js 根据模型自动选择
336
+ apiBaseUrl = null; // 不再使用
337
+ apiKey = null; // 不再使用
338
  modelName = DEFAULT_MODEL;
339
 
340
+ // API 配置由 apiConfig.js 管理,无需检查
 
 
 
 
 
 
341
  } else {
342
  if (!apiBaseUrl || !apiKey || !modelName) return res.status(400).json({ error: 'MISSING_CUSTOM_API_CONFIG' });
343
  }
 
364
  }
365
 
366
  // 单次请求函数
367
+ const makeRequest = async (currentModel) => {
368
  const controller = new AbortController();
369
  const timeoutId = setTimeout(() => controller.abort(), 180000); // 180秒超时
370
 
371
  try {
372
+ // 使用 API 配置系统
373
+ const apiRequest = buildApiRequest(currentModel, BAZI_SYSTEM_INSTRUCTION, userPrompt, 0.7);
 
 
 
 
374
 
375
+ const response = await fetch(apiRequest.url, {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  method: 'POST',
377
+ headers: apiRequest.headers,
378
  signal: controller.signal,
379
+ body: JSON.stringify(apiRequest.body),
380
  });
381
 
382
  clearTimeout(timeoutId);
 
398
  while (retryCount <= maxRetries) {
399
  try {
400
  console.log(`尝试模型: ${currentModel} (第${retryCount + 1}次)`);
401
+ const response = await makeRequest(currentModel);
402
 
403
  if (response.ok) {
404
  return { success: true, response, model: currentModel };
 
481
  });
482
  }
483
 
484
+ // 使用新的响应解析系统
485
+ const content = parseApiResponse(jsonResult, usedModel);
 
 
 
 
 
 
486
  if (!content) return res.status(502).json({ error: 'EMPTY_MODEL_RESPONSE' });
487
 
488
  // 清理可能的 markdown 代码块标记
 
2511
  `;
2512
 
2513
  try {
2514
+ // 使用新的 API 配置系统
2515
+ const model = 'gpt-4o'; // 使用 GPT-4o 作为默认模型
2516
+ const apiRequest = buildApiRequest(model, AGENT_DAILY_FORTUNE_PROMPT, userPrompt, 0.7);
 
 
 
 
 
 
 
 
 
2517
 
2518
+ // 如果是 OpenAI 格式,添加 max_tokens
2519
+ if (apiRequest.body.max_tokens === undefined && apiRequest.body.generationConfig) {
2520
+ apiRequest.body.generationConfig.maxOutputTokens = 4000;
2521
+ } else if (apiRequest.body.max_tokens === undefined) {
2522
+ apiRequest.body.max_tokens = 4000;
2523
+ }
2524
+
2525
+ const response = await fetch(apiRequest.url, {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2526
  method: 'POST',
2527
+ headers: apiRequest.headers,
2528
+ body: JSON.stringify(apiRequest.body),
2529
  });
2530
 
2531
  if (!response.ok) {
 
2534
 
2535
  const data = await response.json();
2536
 
2537
+ // 使用新的响应解析系统
2538
+ const content = parseApiResponse(data, model);
 
 
 
 
 
2539
 
2540
  if (!content) {
2541
  throw new Error('Empty AI response');
server/parallelAnalyzer.js CHANGED
@@ -15,25 +15,24 @@ import {
15
  AGENT_CRYPTO_PROMPT,
16
  } from './agentPrompts.js';
17
  import { generateFallbackKLine } from './baziCalculator.js';
 
18
 
19
- const DEFAULT_API_BASE_URL = process.env.API_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta';
20
- const DEFAULT_API_KEY = process.env.API_KEY || 'AIzaSyBiz_9_zhb3c9mxl3dePVfIv1iiao-_6dw'; // Google Gemini API密钥
21
-
22
- // 为不同Agent分配最适合的模型(使用 Google Gemini 支持的模型)
23
  const AGENT_MODEL_ASSIGNMENT = {
24
- core: 'gemini-1.5-pro', // 核心命理 - 逻辑推理强
25
- kline_past: 'gemini-1.5-pro', // 过去K线 - 数据结构化强
26
- kline_future: 'gemini-1.5-pro', // 未来K线 - 数据结构化强
27
- career: 'gemini-1.5-pro', // 事业财富 - 综合能力
28
- marriage: 'gemini-1.5-flash', // 婚姻健康 - 快速响应
29
- crypto: 'gemini-1.5-pro', // 币圈分析 - 币圈知识丰富
30
  };
31
 
32
- // 备用模型列表(Google Gemini 支持的模型)
33
  const FALLBACK_MODELS = [
34
- 'gemini-1.5-flash',
 
 
35
  'gemini-1.5-pro',
36
- 'gemini-pro',
37
  ];
38
 
39
  /**
@@ -49,7 +48,7 @@ export const sendSSE = (res, event, data) => {
49
  /**
50
  * 单个Agent请求
51
  */
52
- const makeAgentRequest = async (agentType, model, apiBaseUrl, apiKey, systemPrompt, userPrompt, timeoutMs = 60000) => {
53
  const controller = new AbortController();
54
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
55
 
@@ -57,44 +56,14 @@ const makeAgentRequest = async (agentType, model, apiBaseUrl, apiKey, systemProm
57
  console.log(`[Agent:${agentType}] 使用模型 ${model} 开始请求...`);
58
  const startTime = Date.now();
59
 
60
- // 检查是否是 Google Gemini API
61
- const isGeminiApi = apiKey.startsWith('AIzaSy');
62
- const apiUrl = isGeminiApi
63
- ? `${apiBaseUrl}/models/${model}:generateContent?key=${apiKey}`
64
- : `${apiBaseUrl}/chat/completions`;
65
-
66
- const headers = isGeminiApi
67
- ? { 'Content-Type': 'application/json' }
68
- : {
69
- 'Content-Type': 'application/json',
70
- Authorization: `Bearer ${apiKey}`,
71
- };
72
-
73
- const requestBody = isGeminiApi
74
- ? {
75
- contents: [{
76
- parts: [{
77
- text: `${systemPrompt}\n\n${userPrompt}`
78
- }]
79
- }],
80
- generationConfig: {
81
- temperature: 0.6,
82
- }
83
- }
84
- : {
85
- model: model,
86
- messages: [
87
- { role: 'system', content: systemPrompt },
88
- { role: 'user', content: userPrompt },
89
- ],
90
- temperature: 0.6, // 稍低的温度以保持一致性
91
- };
92
-
93
- const response = await fetch(apiUrl, {
94
  method: 'POST',
95
- headers: headers,
96
  signal: controller.signal,
97
- body: JSON.stringify(requestBody),
98
  });
99
 
100
  clearTimeout(timeoutId);
@@ -102,7 +71,7 @@ const makeAgentRequest = async (agentType, model, apiBaseUrl, apiKey, systemProm
102
 
103
  if (!response.ok) {
104
  const errText = await response.text();
105
- console.warn(`[Agent:${agentType}] 请求失败 (${elapsed}s): ${response.status}`);
106
  return { success: false, agentType, error: `HTTP ${response.status}`, elapsed };
107
  }
108
 
@@ -115,13 +84,8 @@ const makeAgentRequest = async (agentType, model, apiBaseUrl, apiKey, systemProm
115
  return { success: false, agentType, error: 'INVALID_API_RESPONSE', elapsed };
116
  }
117
 
118
- // 处理 Google Gemini API 响应格式
119
- let content;
120
- if (isGeminiApi && jsonResult.candidates?.[0]?.content?.parts?.[0]?.text) {
121
- content = jsonResult.candidates[0].content.parts[0].text;
122
- } else {
123
- content = jsonResult.choices?.[0]?.message?.content;
124
- }
125
  if (!content) {
126
  return { success: false, agentType, error: 'EMPTY_RESPONSE', elapsed };
127
  }
@@ -197,7 +161,7 @@ const makeAgentRequestWithRetry = async (agentType, apiBaseUrl, apiKey, systemPr
197
 
198
  for (const model of modelsToTry) {
199
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
200
- const result = await makeAgentRequest(agentType, model, apiBaseUrl, apiKey, systemPrompt, userPrompt);
201
 
202
  if (result.success) {
203
  // 验证返回数据是否完整
@@ -307,8 +271,9 @@ const buildAgentUserPrompt = (input, skeletonData, agentType) => {
307
  * @param {function} onProgress - 进度回调
308
  */
309
  export const runParallelAgents = async (input, skeletonData, res, onProgress) => {
310
- const apiBaseUrl = DEFAULT_API_BASE_URL;
311
- const apiKey = DEFAULT_API_KEY;
 
312
 
313
  const agents = [
314
  { type: 'core', prompt: AGENT_CORE_PROMPT, priority: 1 },
 
15
  AGENT_CRYPTO_PROMPT,
16
  } from './agentPrompts.js';
17
  import { generateFallbackKLine } from './baziCalculator.js';
18
+ import { buildApiRequest, parseApiResponse } from './apiConfig.js';
19
 
20
+ // 为不同Agent分配最适合的模型
 
 
 
21
  const AGENT_MODEL_ASSIGNMENT = {
22
+ core: 'grok-4', // 核心命理 - 逻辑推理强
23
+ kline_past: 'gpt-4o', // 过去K线 - 数据结构化强
24
+ kline_future: 'gpt-4o', // 未来K线 - 数据结构化强
25
+ career: 'claude-3-5-sonnet-20241022', // 事业财富 - 综合能力
26
+ marriage: 'grok-4-auto', // 婚姻健康 - 快速响应
27
+ crypto: 'grok-4', // 币圈分析 - 币圈知识丰富
28
  };
29
 
30
+ // 备用模型列表
31
  const FALLBACK_MODELS = [
32
+ 'gpt-4o',
33
+ 'grok-4',
34
+ 'claude-3-5-sonnet-20241022',
35
  'gemini-1.5-pro',
 
36
  ];
37
 
38
  /**
 
48
  /**
49
  * 单个Agent请求
50
  */
51
+ const makeAgentRequest = async (agentType, model, systemPrompt, userPrompt, timeoutMs = 60000) => {
52
  const controller = new AbortController();
53
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
54
 
 
56
  console.log(`[Agent:${agentType}] 使用模型 ${model} 开始请求...`);
57
  const startTime = Date.now();
58
 
59
+ // 使用新的 API 配置系统
60
+ const apiRequest = buildApiRequest(model, systemPrompt, userPrompt, 0.6);
61
+
62
+ const response = await fetch(apiRequest.url, {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  method: 'POST',
64
+ headers: apiRequest.headers,
65
  signal: controller.signal,
66
+ body: JSON.stringify(apiRequest.body),
67
  });
68
 
69
  clearTimeout(timeoutId);
 
71
 
72
  if (!response.ok) {
73
  const errText = await response.text();
74
+ console.warn(`[Agent:${agentType}] 请求失败 (${elapsed}s): ${response.status} - ${errText.substring(0, 200)}`);
75
  return { success: false, agentType, error: `HTTP ${response.status}`, elapsed };
76
  }
77
 
 
84
  return { success: false, agentType, error: 'INVALID_API_RESPONSE', elapsed };
85
  }
86
 
87
+ // 使用新的响应解析系统
88
+ const content = parseApiResponse(jsonResult, model);
 
 
 
 
 
89
  if (!content) {
90
  return { success: false, agentType, error: 'EMPTY_RESPONSE', elapsed };
91
  }
 
161
 
162
  for (const model of modelsToTry) {
163
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
164
+ const result = await makeAgentRequest(agentType, model, systemPrompt, userPrompt);
165
 
166
  if (result.success) {
167
  // 验证返回数据是否完整
 
271
  * @param {function} onProgress - 进度回调
272
  */
273
  export const runParallelAgents = async (input, skeletonData, res, onProgress) => {
274
+ // 不再需要固定的 API URL 和 Key,由 apiConfig.js 根据模型自动选择
275
+ const apiBaseUrl = null; // 不再使用
276
+ const apiKey = null; // 不再使用
277
 
278
  const agents = [
279
  { type: 'core', prompt: AGENT_CORE_PROMPT, priority: 1 },
server/testApi.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * API 连接测试端点
3
+ */
4
+ import { buildApiRequest, parseApiResponse, getApiConfigForModel } from './apiConfig.js';
5
+ import fetch from 'node-fetch';
6
+
7
+ export async function testAllApis() {
8
+ const testModels = [
9
+ { name: 'gpt-4o', provider: 'OpenAI' },
10
+ { name: 'grok-4', provider: 'Grok' },
11
+ { name: 'claude-3-5-sonnet-20241022', provider: 'Claude' },
12
+ { name: 'gemini-1.5-pro', provider: 'Gemini' },
13
+ ];
14
+
15
+ const results = [];
16
+
17
+ for (const { name, provider } of testModels) {
18
+ try {
19
+ console.log(`测试 ${provider} (${name})...`);
20
+ const config = getApiConfigForModel(name);
21
+ const testPrompt = 'Say "Hello" in one word.';
22
+
23
+ const apiRequest = buildApiRequest(name, 'You are a helpful assistant.', testPrompt, 0.7);
24
+
25
+ const startTime = Date.now();
26
+ const response = await fetch(apiRequest.url, {
27
+ method: 'POST',
28
+ headers: apiRequest.headers,
29
+ body: JSON.stringify(apiRequest.body),
30
+ });
31
+
32
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
33
+
34
+ if (!response.ok) {
35
+ const errorText = await response.text();
36
+ results.push({
37
+ provider,
38
+ model: name,
39
+ status: 'FAILED',
40
+ statusCode: response.status,
41
+ error: errorText.substring(0, 200),
42
+ elapsed,
43
+ });
44
+ continue;
45
+ }
46
+
47
+ const data = await response.json();
48
+ const content = parseApiResponse(data, name);
49
+
50
+ results.push({
51
+ provider,
52
+ model: name,
53
+ status: 'SUCCESS',
54
+ statusCode: response.status,
55
+ response: content?.substring(0, 100) || 'Empty response',
56
+ elapsed,
57
+ });
58
+
59
+ } catch (error) {
60
+ results.push({
61
+ provider,
62
+ model: name,
63
+ status: 'ERROR',
64
+ error: error.message,
65
+ });
66
+ }
67
+ }
68
+
69
+ return results;
70
+ }
71
+