From 552dd7ae16b2c16e872881af2d86bb25cde855fd Mon Sep 17 00:00:00 2001 From: MCVelasquez45 <160294290+MCVelasquez45@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:00:08 -0700 Subject: [PATCH 1/2] chore: add model-service and update .gitignore --- .gitignore | 2 + model-service/.env.example | 5 +++ model-service/.gitignore | 5 +++ model-service/Procfile | 2 + model-service/ecosystem.config.js | 16 ++++++++ model-service/package.json | 20 ++++++++++ model-service/server.js | 64 +++++++++++++++++++++++++++++++ 7 files changed, 114 insertions(+) create mode 100644 model-service/.env.example create mode 100644 model-service/.gitignore create mode 100644 model-service/Procfile create mode 100644 model-service/ecosystem.config.js create mode 100644 model-service/package.json create mode 100644 model-service/server.js diff --git a/.gitignore b/.gitignore index 68bc17f9..4d5863b9 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,5 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +model-service/.env +model-service/.env diff --git a/model-service/.env.example b/model-service/.env.example new file mode 100644 index 00000000..6f572180 --- /dev/null +++ b/model-service/.env.example @@ -0,0 +1,5 @@ +OPENAI_BASE_URL=https://api.openai.com/v1 +OPENAI_API_KEY=sk-REPLACE_ME +MODEL_NAME=gpt-4o-mini +PORT=7071 + diff --git a/model-service/.gitignore b/model-service/.gitignore new file mode 100644 index 00000000..e2e4726c --- /dev/null +++ b/model-service/.gitignore @@ -0,0 +1,5 @@ +package-lock.json +node_modules/ +.env +.DS_Store + diff --git a/model-service/Procfile b/model-service/Procfile new file mode 100644 index 00000000..3e0e4456 --- /dev/null +++ b/model-service/Procfile @@ -0,0 +1,2 @@ +web: node server.js + diff --git a/model-service/ecosystem.config.js b/model-service/ecosystem.config.js new file mode 100644 index 00000000..141710e5 --- /dev/null +++ b/model-service/ecosystem.config.js @@ -0,0 +1,16 @@ +module.exports = { + apps: [ + { + name: 'reentry-model-service', + script: 'server.js', + instances: 1, + exec_mode: 'fork', + env: { + // server.js loads dotenv; these are optional overrides + PORT: process.env.PORT || 7071, + }, + watch: false, + }, + ], +}; + diff --git a/model-service/package.json b/model-service/package.json new file mode 100644 index 00000000..5a8fb84d --- /dev/null +++ b/model-service/package.json @@ -0,0 +1,20 @@ +{ + "name": "reentry-model-service", + "version": "1.0.0", + "private": true, + "main": "server.js", + "license": "UNLICENSED", + "scripts": { + "dev": "node server.js", + "start": "node server.js", + "pm2": "pm2 start ecosystem.config.js" + }, + "dependencies": { + "dotenv": "^16.4.5", + "express": "^4.19.2", + "node-fetch": "^2.7.0" + }, + "engines": { + "node": ">=18" + } +} diff --git a/model-service/server.js b/model-service/server.js new file mode 100644 index 00000000..fadf390c --- /dev/null +++ b/model-service/server.js @@ -0,0 +1,64 @@ +const express = require('express'); +const fetch = require('node-fetch'); +const dotenv = require('dotenv'); + +dotenv.config(); + +const app = express(); +app.use(express.json()); + +const PORT = process.env.PORT || 7071; +const OPENAI_BASE_URL = (process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1').replace(/\/+$/, ''); +const OPENAI_API_KEY = process.env.OPENAI_API_KEY || ''; +const MODEL_NAME = process.env.MODEL_NAME || 'gpt-4o-mini'; + +// Health endpoint +app.get('/health', (_req, res) => { + res.json({ ok: true, model: MODEL_NAME, base: OPENAI_BASE_URL }); +}); + +// Generate endpoint - forwards to provider-compatible chat/completions +app.post('/generate', async (req, res) => { + try { + if (!OPENAI_API_KEY) { + return res.status(400).json({ error: 'Missing OPENAI_API_KEY in environment' }); + } + + const body = req.body || {}; + const messages = body.messages; + const model = body.model || MODEL_NAME; + const extra = body.extra || {}; // allow optional pass-through params + + if (!Array.isArray(messages) || messages.length === 0) { + return res.status(400).json({ error: 'Body must include messages: [{ role, content }]' }); + } + + const url = `${OPENAI_BASE_URL}/chat/completions`; + + const providerResp = await fetch(url, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ model, messages, ...extra }) + }); + + const data = await providerResp.json(); + + if (!providerResp.ok) { + // Pass through provider error + return res.status(providerResp.status).json({ error: data.error || data }); + } + + const text = data?.choices?.[0]?.message?.content || ''; + return res.json({ text }); + } catch (err) { + return res.status(500).json({ error: 'Upstream error', detail: String(err && err.message || err) }); + } +}); + +app.listen(PORT, () => { + console.log(`model-service listening on http://localhost:${PORT}`); +}); + From 2bb7602da6e47ea393a4c15f1f424b0f4625a520 Mon Sep 17 00:00:00 2001 From: MCVelasquez45 <160294290+MCVelasquez45@users.noreply.github.com> Date: Wed, 27 Aug 2025 19:10:11 -0700 Subject: [PATCH 2/2] chore(gitignore): tidy ignore rules --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4d5863b9..f5e9b081 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ model-service/.env -model-service/.env +