Juanoto2012 commited on
Commit
ccb18a3
·
verified ·
1 Parent(s): 81a789f

Update server.js

Browse files
Files changed (1) hide show
  1. server.js +96 -68
server.js CHANGED
@@ -1,12 +1,12 @@
1
  import express from 'express';
2
  import cors from 'cors';
3
  import rateLimit from 'express-rate-limit';
4
- import { DuckDuckGoChat, Models } from 'duckduckgo-chat-interface';
5
 
6
  const app = express();
7
  const PORT = 7860;
8
 
9
- // --- CONFIGURACIÓN DE SEGURIDAD ---
10
  app.set('trust proxy', 1);
11
  app.disable('x-powered-by');
12
  app.use(cors());
@@ -17,106 +17,134 @@ app.use((req, res, next) => {
17
  res.setHeader('X-XSS-Protection', '1; mode=block');
18
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
19
  res.setHeader('Referrer-Policy', 'no-referrer');
20
- res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none';");
21
  next();
22
  });
23
 
24
  app.use(express.json({ limit: '50mb' }));
25
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
26
 
27
- // --- RATE LIMITING LOCAL ---
 
28
  const limiter = rateLimit({
29
  windowMs: 60 * 1000,
30
- max: 50,
31
  keyGenerator: (req) => req.ip,
32
- message: { error: { message: "Too many requests.", code: 429 } },
33
  standardHeaders: false,
34
  legacyHeaders: false,
35
  });
36
 
37
  app.get('/health', (req, res) => {
38
- res.json({ status: "online", proxy: "DuckDuckGo Library", node_module: "duckduckgo-chat-interface" });
39
  });
40
 
 
41
  app.get('/v1/models', (req, res) => {
42
  res.json({
43
  object: "list",
44
  data: [
45
- { id: "gpt-4o-mini", object: "model", type: "text", owned_by: "duckduckgo" },
46
- { id: "claude-3-haiku", object: "model", type: "text", owned_by: "duckduckgo" },
47
- { id: "llama-3.1-70b", object: "model", type: "text", owned_by: "duckduckgo" },
48
- { id: "mixtral-8x7b", object: "model", type: "text", owned_by: "duckduckgo" }
49
  ]
50
  });
51
  });
52
 
53
- app.post('/v1/chat/completions', limiter, async (req, res) => {
54
- try {
55
- const { model = "gpt-4o-mini", messages, stream } = req.body;
56
-
57
- // 1. Mapear tu string de modelo al Enum que usa la librería
58
- let chatModel = Models.GPT4Mini;
59
- if (model.includes("claude")) chatModel = Models.Claude3Haiku;
60
- if (model.includes("llama")) chatModel = Models.Llama3_3_70B;
61
- if (model.includes("mistral") || model.includes("mixtral")) chatModel = Models.MistralSmall;
62
-
63
- // 2. Inicializar la sesión de DuckDuckGo
64
- const chat = new DuckDuckGoChat(chatModel);
65
- await chat.initialize();
66
-
67
- // Extraemos solo el mensaje del usuario (DuckDuckGo no soporta bien historiales complejos por API sin manejo avanzado)
68
- // Concatenamos el historial para darle contexto al modelo si hay varios mensajes.
69
- const fullPrompt = messages.map(m => `${m.role}: ${m.content}`).join('\n\n');
70
-
71
- // 3A. Modo Texto Completo (No Streaming)
72
- if (!stream) {
73
- const response = await chat.sendMessage(fullPrompt);
74
- return res.json({
75
- id: 'chatcmpl-' + Date.now(),
76
- object: 'chat.completion',
77
- created: Math.floor(Date.now() / 1000),
78
- model: model,
79
- choices: [{ message: { role: 'assistant', content: response }, finish_reason: 'stop' }]
80
- });
81
- }
82
 
83
- // 3B. Modo Streaming (Traducción en tiempo real a OpenAI)
84
- res.writeHead(200, {
85
- 'Content-Type': 'text/event-stream',
86
- 'Cache-Control': 'no-cache',
87
- 'Connection': 'keep-alive'
88
- });
89
-
90
- await chat.sendMessageStream(fullPrompt, (chunk) => {
91
- const data = {
92
- id: 'chatcmpl-' + Date.now(),
93
- object: 'chat.completion.chunk',
94
- created: Math.floor(Date.now() / 1000),
95
- model: model,
96
- choices: [{ delta: { content: chunk } }]
97
  };
98
- res.write(`data: ${JSON.stringify(data)}\n\n`);
99
- });
 
 
 
 
 
 
 
100
 
101
- res.write('data: [DONE]\n\n');
102
- res.end();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- } catch (err) {
105
- console.error("[DDG LIBRARY ERROR]", err);
106
- const statusCode = err.message.includes('503') ? 503 : 500;
107
-
108
  if (!res.headersSent) {
109
- res.status(statusCode).json({
110
  error: {
111
- message: "Error de proveedor. Si recibes 503 constantemente, Cloudflare está bloqueando la IP de Hugging Face.",
112
- details: err.message,
113
- code: statusCode
114
- }
115
  });
116
  }
117
  }
118
  });
119
 
120
  app.listen(PORT, '0.0.0.0', () => {
121
- console.log(`[SYS] DuckDuckGo Library Node.js Server Ready. Puerto: ${PORT}`);
 
122
  });
 
1
  import express from 'express';
2
  import cors from 'cors';
3
  import rateLimit from 'express-rate-limit';
4
+ import { Readable } from 'stream';
5
 
6
  const app = express();
7
  const PORT = 7860;
8
 
9
+ // --- CONFIGURACIÓN DE SEGURIDAD PRIVADA ---
10
  app.set('trust proxy', 1);
11
  app.disable('x-powered-by');
12
  app.use(cors());
 
17
  res.setHeader('X-XSS-Protection', '1; mode=block');
18
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
19
  res.setHeader('Referrer-Policy', 'no-referrer');
 
20
  next();
21
  });
22
 
23
  app.use(express.json({ limit: '50mb' }));
24
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
25
 
26
+ // --- RATE LIMITING ALTO (500 CONCURRENTES) ---
27
+ // Permitimos 500 peticiones por minuto por IP local.
28
  const limiter = rateLimit({
29
  windowMs: 60 * 1000,
30
+ max: 500,
31
  keyGenerator: (req) => req.ip,
32
+ message: { error: { message: "Servidor a máxima capacidad. Intenta en unos segundos.", code: 429 } },
33
  standardHeaders: false,
34
  legacyHeaders: false,
35
  });
36
 
37
  app.get('/health', (req, res) => {
38
+ res.json({ status: "online", type: "High Concurrency Proxy", max_capacity: 500 });
39
  });
40
 
41
+ // --- MODELOS DISPONIBLES ---
42
  app.get('/v1/models', (req, res) => {
43
  res.json({
44
  object: "list",
45
  data: [
46
+ { id: "gpt-4o", object: "model", type: "text", owned_by: "system" },
47
+ { id: "claude-3-5-sonnet", object: "model", type: "text", owned_by: "system" },
48
+ { id: "gemini-1.5-pro", object: "model", type: "text", owned_by: "system" }
 
49
  ]
50
  });
51
  });
52
 
53
+ // --- POOL DE PROVEEDORES ---
54
+ // Usando api.llm7.io como solicitaste. Puedes añadir más separados por comas.
55
+ const PROVIDERS = [
56
+ "https://api.llm7.io/v1/chat/completions"
57
+ ];
58
+
59
+ let providerIndex = 0;
60
+
61
+ // Distribuye las peticiones si hay más de un proveedor en el array
62
+ function getNextProvider() {
63
+ const url = PROVIDERS[providerIndex];
64
+ providerIndex = (providerIndex + 1) % PROVIDERS.length;
65
+ return url;
66
+ }
67
+
68
+ // --- EL PROXY DE ALTO RENDIMIENTO ---
69
+ app.post(['/v1/chat/completions'], limiter, async (req, res) => {
70
+ const clientIp = req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress;
71
+
72
+ // Configuración de Timeout para evitar que 500 conexiones se queden colgadas en memoria
73
+ const controller = new AbortController();
74
+ const timeoutId = setTimeout(() => controller.abort(), 45000); // 45 segundos de tiempo de vida
75
+
76
+ // Reintento interno: si un proveedor falla, intenta con el siguiente antes de darle error al usuario
77
+ let attempts = 0;
78
+ let responseSent = false;
79
+ let lastError = null;
 
 
80
 
81
+ while (attempts < PROVIDERS.length && !responseSent) {
82
+ const targetUrl = getNextProvider();
83
+ attempts++;
84
+
85
+ try {
86
+ const fetchHeaders = {
87
+ "Content-Type": "application/json",
88
+ "Accept": "*/*",
89
+ "X-Forwarded-For": clientIp, // Delegar IP divide el rate limit en el proveedor final
90
+ "Authorization": "Bearer sk-dummy-key-12345" // Clave falsa para proveedores que validan sintaxis
 
 
 
 
91
  };
92
+
93
+ const response = await fetch(targetUrl, {
94
+ method: "POST",
95
+ headers: fetchHeaders,
96
+ body: JSON.stringify(req.body),
97
+ signal: controller.signal
98
+ });
99
+
100
+ clearTimeout(timeoutId);
101
 
102
+ if (!response.ok) {
103
+ lastError = `HTTP ${response.status} de ${targetUrl}`;
104
+ continue; // Falla este proveedor, el while loop intentará con el siguiente
105
+ }
106
+
107
+ const responseHeaders = new Headers(response.headers);
108
+ res.writeHead(response.status, {
109
+ 'Content-Type': responseHeaders.get('content-type') || 'application/json',
110
+ 'Cache-Control': 'no-cache',
111
+ 'Connection': 'keep-alive' // Vital para alta concurrencia
112
+ });
113
+
114
+ if (response.body) {
115
+ const stream = Readable.fromWeb(response.body);
116
+ stream.pipe(res);
117
+
118
+ req.on('close', () => {
119
+ stream.destroy(); // Limpiar memoria si el usuario cierra la conexión de golpe
120
+ });
121
+ } else {
122
+ res.end();
123
+ }
124
+
125
+ responseSent = true;
126
+
127
+ } catch (err) {
128
+ lastError = err.name === 'AbortError' ? 'Timeout excedido' : err.message;
129
+ // Sigue intentando con el siguiente proveedor
130
+ }
131
+ }
132
 
133
+ if (!responseSent) {
134
+ clearTimeout(timeoutId);
 
 
135
  if (!res.headersSent) {
136
+ res.status(503).json({
137
  error: {
138
+ message: "Todos los proveedores base rechazaron la conexión simultánea. Intenta reducir la concurrencia.",
139
+ details: lastError,
140
+ code: 503
141
+ }
142
  });
143
  }
144
  }
145
  });
146
 
147
  app.listen(PORT, '0.0.0.0', () => {
148
+ console.log(`[SYS] Ventarys API v2 corriendo en puerto: ${PORT}`);
149
+ console.log(`[SYS] Capacidad: 500 req/min. Conectado a: api.llm7.io`);
150
  });