From ca0373c49209c8b5d42c7f9499125609bfe0e769 Mon Sep 17 00:00:00 2001 From: Storme-bit Date: Sat, 4 Apr 2026 21:34:12 -0700 Subject: [PATCH] Added embedding service --- packages/embedding-service/src/index.js | 70 ++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/packages/embedding-service/src/index.js b/packages/embedding-service/src/index.js index 01e36c6..6d5db4a 100644 --- a/packages/embedding-service/src/index.js +++ b/packages/embedding-service/src/index.js @@ -5,13 +5,81 @@ const {getEnv} = require('@nexusai/shared'); const app = express(); app.use(express.json()); -const PORT = getEnv('PORT', '3003'); // Default to 3003 if PORT is not set +const PORT = getEnv('PORT', '3003'); // Default to 3003 if PORT is not set +const OLLAMA_URL = getEnv('OLLAMA_URL', 'http://localhost:11434'); // URL for Ollama API +const EMBED_MODEL = getEnv('EMBED_MODEL', 'nomic-embed-text'); // Ollama model for embeddings + +console.log('OLLAMA_URL:', OLLAMA_URL); +console.log('EMBED_MODEL:', EMBED_MODEL); + +//OLLAMA embedding helper function +async function embedText(text) { + const res = await fetch(`${OLLAMA_URL}/api/v1/embeddings`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ model: EMBED_MODEL, input: text }) + }); + + if (!res.ok) { + throw new Error(`Ollama error: ${res.status} ${res.statusText}`); + } + + const data = await res.json(); + return data.embedding; +} + + +/**** ROUTES ***** */ // Health check endpoint app.get('/health', (req,res) => { res.json({ service: 'Embedding Service', status: 'healthy' }); }) +// Single text embedding +app.post('/embed', async (req, res) => { + const { text } = req.body; + if (!text || typeof text !== 'string' || text.trim() === '') { + return res.status(400).json({ error: 'text is required and must be empty' }); + } + + try { + const embedding = await embedText(text.trim()); + res.json({ + embedding, + model: EMBED_MODEL, + dimensions: embedding.length + }); + } catch (err) { + res.status(502).json({ error: 'Embedding failed', detail: err.message }); + } +}); + +// Batch embedding endpoint +app.post('/embed/batch', async (req, res) => { + const { texts } = req.body; + if (!Array.isArray(texts) || texts.length ===0 ) { + return res.status(400).json({ error: 'texts array must not be empty' }); + } + + try { + //sequential embedding for now, Ollama doesn't natively parallize embeddings + const embeddings = []; + for (const text of texts) { + embeddings.push(await embedText(text.trim())); + } + res.json ({ + embeddings, + model: EMBED_MODEL, + dimensions: embeddings[0].length, + count: embeddings.length + }) + } catch (err) { + res.status(502).json({ error: 'Batch embedding failed', detail: err.message }); + } +}) + +/******* Start Server ********/ app.listen(PORT, () => { console.log(`Embedding Service listening on port ${PORT}`); }); \ No newline at end of file