ComEleE5Small / index.js
plvictor's picture
Update index.js
90ca2b1 verified
import express from 'express';
import cors from 'cors';
import { pipeline, env } from '@xenova/transformers';
import path from "path";
import { fileURLToPath } from 'url';
import textToSpeechAPI from './textToSpeech/textToSpeechAPI.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
app.use(cors());
app.use(express.json());
let extractor = null;
let isInitializing = false;
const init = async () => {
if (extractor || isInitializing) return extractor;
isInitializing = true;
console.log('🔄 Inicializando E5...');
try {
// Configurações para modelo local
// env.allowRemoteModels = false;
//env.allowLocalModels = true;
env.useBrowserCache = false;
// Define o caminho para a pasta de modelos
//env.localModelPath = path.join(__dirname, 'models');
const modelPath = 'Xenova/multilingual-e5-small';
extractor = await pipeline('feature-extraction', modelPath, {
quantized: true,
device: 'cpu',
local_files_only: false,
});
console.log('✅ E5 carregado!');
return extractor;
} catch (error) {
console.error('❌ Erro:', error.message);
throw error;
} finally {
isInitializing = false;
}
};
console.log('🔄 Inicializando TTS...');
textToSpeechAPI(app);
console.log('✅ TTS carregado!');
// Health check mínimo
app.get('/', (req, res) => {
const uptimeSeconds = process.uptime(); // tempo desde que o servidor iniciou
const uptime = `${Math.floor(uptimeSeconds / 60)} min ${Math.floor(uptimeSeconds % 60)} sec`;
const now = new Date();
res.json({
status: extractor ? 'ready' : 'loading',
model: 'e5-small',
api_version: 'v1',
timestamp: now.toISOString(),
uptime: uptime,
server_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
requester_ip: req.headers['x-forwarded-for'] || req.socket.remoteAddress
});
});
app.get('/ping', (req, res) => res.json({ pong: true }));
// Endpoint principal otimizado
app.post('/api/embed', async (req, res) => {
try {
if (!extractor) await init();
const { text, type = 'query' } = req.body;
if (!text) return res.status(400).json({ error: 'Texto necessário' });
// Processa com configurações de baixa memória
const result = await extractor([`${type}: ${text}`], {
pooling: 'mean',
normalize: true,
// Reduz batch size para economizar RAM
batch_size: 1,
// Usa menos threads
num_threads: 1
});
const embedding = Array.from(result[0].data);
res.json({ embedding });
// Force garbage collection se disponível
if (global.gc) global.gc();
} catch (err) {
console.error('Erro embed:', err.message);
res.status(500).json({ error: 'Erro interno' });
}
});
// Batch otimizado para múltiplos textos
app.post('/api/embed/batch', async (req, res) => {
try {
if (!extractor) await init();
const { texts, type = 'query' } = req.body;
if (!Array.isArray(texts)) return res.status(400).json({ error: 'Array necessário' });
// Processa em chunks pequenos para não estourar memória
const chunkSize = 3;
const embeddings = [];
for (let i = 0; i < texts.length; i += chunkSize) {
const chunk = texts.slice(i, i + chunkSize);
const prefixed = chunk.map(text => `${type}: ${text}`);
const results = await extractor(prefixed, {
pooling: 'mean',
normalize: true,
batch_size: 1,
num_threads: 1
});
embeddings.push(...results.map(r => Array.from(r.data)));
// Pequena pausa entre chunks
await new Promise(resolve => setTimeout(resolve, 10));
}
res.json({
embeddings,
count: embeddings.length
});
if (global.gc) global.gc();
} catch (err) {
console.error('Erro batch:', err.message);
res.status(500).json({ error: 'Erro interno' });
}
});
const PORT = process.env.PORT || 7860;
// Timeout agressivo
const timeout = setTimeout(() => {
if (isInitializing) {
console.log('⚠️ Timeout - reiniciando');
process.exit(1);
}
}, 90000); // 90s
// Inicializa o modelo automaticamente
init().catch(console.error);
app.listen(PORT, '0.0.0.0', () => {
console.log(`🚀 E5 na porta ${PORT}`);
});
// Cleanup
process.on('SIGTERM', () => {
console.log('📴 Finalizando...');
process.exit(0);
});
process.on('uncaughtException', (err) => {
console.error('💥 Erro crítico:', err.message);
process.exit(1);
});