Add NanoGPT embeddings support for Vector Storage (#5150) * Initial plan * Add NanoGPT embeddings support for Vector Storage Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com> * Fix models loading --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -71,6 +71,7 @@ const settings = { | ||
| 71 | 71 | webllm_model: '', |
| 72 | 72 | google_model: 'text-embedding-005', |
| 73 | 73 | chutes_model: 'chutes-qwen-qwen3-embedding-8b', |
| 74 | + nanogpt_model: 'text-embedding-3-small', | |
| 74 | 75 | summarize: false, |
| 75 | 76 | summarize_sent: false, |
| 76 | 77 | summary_source: 'main', |
| @@ -834,6 +835,9 @@ function getVectorsRequestBody(args = {}) { | ||
| 834 | 835 | case 'chutes': |
| 835 | 836 | body.model = extension_settings.vectors.chutes_model; |
| 836 | 837 | break; |
| 838 | + case 'nanogpt': | |
| 839 | + body.model = extension_settings.vectors.nanogpt_model; | |
| 840 | + break; | |
| 837 | 841 | default: |
| 838 | 842 | break; |
| 839 | 843 | } |
| @@ -919,6 +923,7 @@ function throwIfSourceInvalid() { | ||
| 919 | 923 | if (settings.source === 'openai' && !secret_state[SECRET_KEYS.OPENAI] || |
| 920 | 924 | settings.source === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB] || |
| 921 | 925 | settings.source === 'chutes' && !secret_state[SECRET_KEYS.CHUTES] || |
| 926 | + settings.source === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT] || | |
| 922 | 927 | settings.source === 'openrouter' && !secret_state[SECRET_KEYS.OPENROUTER] || |
| 923 | 928 | settings.source === 'palm' && !secret_state[SECRET_KEYS.MAKERSUITE] || |
| 924 | 929 | settings.source === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT] || |
| @@ -1134,6 +1139,7 @@ function toggleSettings() { | ||
| 1134 | 1139 | $('#openai_vectorsModel').toggle(settings.source === 'openai'); |
| 1135 | 1140 | $('#electronhub_vectorsModel').toggle(settings.source === 'electronhub'); |
| 1136 | 1141 | $('#chutes_vectorsModel').toggle(settings.source === 'chutes'); |
| 1142 | + $('#nanogpt_vectorsModel').toggle(settings.source === 'nanogpt'); | |
| 1137 | 1143 | $('#openrouter_vectorsModel').toggle(settings.source === 'openrouter'); |
| 1138 | 1144 | $('#cohere_vectorsModel').toggle(settings.source === 'cohere'); |
| 1139 | 1145 | $('#ollama_vectorsModel').toggle(settings.source === 'ollama'); |
| @@ -1157,6 +1163,9 @@ function toggleSettings() { | ||
| 1157 | 1163 | case 'chutes': |
| 1158 | 1164 | loadChutesModels(); |
| 1159 | 1165 | break; |
| 1166 | + case 'nanogpt': | |
| 1167 | + loadNanoGPTModels(); | |
| 1168 | + break; | |
| 1160 | 1169 | } |
| 1161 | 1170 | } |
| 1162 | 1171 | |
| @@ -1194,6 +1203,40 @@ function populateChutesModelSelect(models) { | ||
| 1194 | 1203 | $('#vectors_chutes_model').val(settings.chutes_model); |
| 1195 | 1204 | } |
| 1196 | 1205 | |
| 1206 | +async function loadNanoGPTModels() { | |
| 1207 | + try { | |
| 1208 | + const response = await fetch('/api/openai/nanogpt/models/embedding', { | |
| 1209 | + method: 'POST', | |
| 1210 | + headers: getRequestHeaders({ omitContentType: true }), | |
| 1211 | + }); | |
| 1212 | + if (!response.ok) { | |
| 1213 | + throw new Error(`HTTP ${response.status}`); | |
| 1214 | + } | |
| 1215 | + /** @type {Array<any>} */ | |
| 1216 | + const data = await response.json(); | |
| 1217 | + const models = Array.isArray(data) ? data : []; | |
| 1218 | + populateNanoGPTModelSelect(models); | |
| 1219 | + } catch (err) { | |
| 1220 | + console.warn('NanoGPT models fetch failed', err); | |
| 1221 | + populateNanoGPTModelSelect([]); | |
| 1222 | + } | |
| 1223 | +} | |
| 1224 | + | |
| 1225 | +function populateNanoGPTModelSelect(models) { | |
| 1226 | + const select = $('#vectors_nanogpt_model'); | |
| 1227 | + select.empty(); | |
| 1228 | + for (const m of models) { | |
| 1229 | + const option = document.createElement('option'); | |
| 1230 | + option.value = m.id; | |
| 1231 | + option.text = m.name || m.id; | |
| 1232 | + select.append(option); | |
| 1233 | + } | |
| 1234 | + if (!settings.nanogpt_model && models.length) { | |
| 1235 | + settings.nanogpt_model = models[0].id; | |
| 1236 | + } | |
| 1237 | + $('#vectors_nanogpt_model').val(settings.nanogpt_model); | |
| 1238 | +} | |
| 1239 | + | |
| 1197 | 1240 | async function loadElectronHubModels() { |
| 1198 | 1241 | try { |
| 1199 | 1242 | const response = await fetch('/api/openai/electronhub/models', { |
| @@ -1679,6 +1722,11 @@ jQuery(async () => { | ||
| 1679 | 1722 | Object.assign(extension_settings.vectors, settings); |
| 1680 | 1723 | saveSettingsDebounced(); |
| 1681 | 1724 | }); |
| 1725 | + $('#vectors_nanogpt_model').val(settings.nanogpt_model).on('change', () => { | |
| 1726 | + settings.nanogpt_model = String($('#vectors_nanogpt_model').val()); | |
| 1727 | + Object.assign(extension_settings.vectors, settings); | |
| 1728 | + saveSettingsDebounced(); | |
| 1729 | + }); | |
| 1682 | 1730 | $('#vectors_openrouter_model').val(settings.openrouter_model).on('change', () => { |
| 1683 | 1731 | settings.openrouter_model = String($('#vectors_openrouter_model').val()); |
| 1684 | 1732 | Object.assign(extension_settings.vectors, settings); |
| @@ -20,6 +20,7 @@ | ||
| 20 | 20 | <option value="llamacpp">llama.cpp</option> |
| 21 | 21 | <option value="transformers" data-i18n="Local (Transformers)">Local (Transformers)</option> |
| 22 | 22 | <option value="mistral">MistralAI</option> |
| 23 | + <option value="nanogpt">NanoGPT</option> | |
| 23 | 24 | <option value="nomicai">NomicAI</option> |
| 24 | 25 | <option value="ollama">Ollama</option> |
| 25 | 26 | <option value="openai">OpenAI</option> |
| @@ -38,6 +39,15 @@ | ||
| 38 | 39 | Hint: Set your Chutes API key in API Connections. |
| 39 | 40 | </i> |
| 40 | 41 | </div> |
| 42 | + <div class="flex-container flexFlowColumn" id="nanogpt_vectorsModel"> | |
| 43 | + <label for="vectors_nanogpt_model" data-i18n="Vectorization Model"> | |
| 44 | + Vectorization Model | |
| 45 | + </label> | |
| 46 | + <select id="vectors_nanogpt_model" class="text_pole"></select> | |
| 47 | + <i data-i18n="Hint: Set your NanoGPT API key in API Connections."> | |
| 48 | + Hint: Set your NanoGPT API key in API Connections. | |
| 49 | + </i> | |
| 50 | + </div> | |
| 41 | 51 | <div class="flex-container flexFlowColumn" id="electronhub_vectorsModel"> |
| 42 | 52 | <label for="vectors_electronhub_model" data-i18n="Vectorization Model"> |
| 43 | 53 | Vectorization Model |
| @@ -493,6 +493,43 @@ router.post('/chutes/models/embedding', async (request, response) => { | ||
| 493 | 493 | } |
| 494 | 494 | }); |
| 495 | 495 | |
| 496 | +router.post('/nanogpt/models/embedding', async (request, response) => { | |
| 497 | + try { | |
| 498 | + const key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); | |
| 499 | + | |
| 500 | + if (!key) { | |
| 501 | + console.warn('No NanoGPT key found'); | |
| 502 | + return response.sendStatus(400); | |
| 503 | + } | |
| 504 | + | |
| 505 | + const result = await fetch('https://nano-gpt.com/api/v1/embedding-models', { | |
| 506 | + method: 'GET', | |
| 507 | + headers: { | |
| 508 | + 'Authorization': `Bearer ${key}`, | |
| 509 | + 'Accept-Encoding': 'identity', | |
| 510 | + }, | |
| 511 | + }); | |
| 512 | + | |
| 513 | + if (!result.ok) { | |
| 514 | + const text = await result.text(); | |
| 515 | + console.warn('NanoGPT embedding models request failed', result.statusText, text); | |
| 516 | + return response.status(500).send(text); | |
| 517 | + } | |
| 518 | + | |
| 519 | + /** @type {any} */ | |
| 520 | + const data = await result.json(); | |
| 521 | + | |
| 522 | + if (!Array.isArray(data?.data)) { | |
| 523 | + console.warn('NanoGPT embedding models response invalid', data); | |
| 524 | + return response.sendStatus(500); | |
| 525 | + } | |
| 526 | + return response.json(data.data); | |
| 527 | + } catch (error) { | |
| 528 | + console.error('NanoGPT embedding models fetch failed', error); | |
| 529 | + response.sendStatus(500); | |
| 530 | + } | |
| 531 | +}); | |
| 532 | + | |
| 496 | 533 | router.post('/generate-image', async (request, response) => { |
| 497 | 534 | try { |
| 498 | 535 | const key = readSecret(request.user.directories, SECRET_KEYS.OPENAI); |
| @@ -37,6 +37,7 @@ const SOURCES = [ | ||
| 37 | 37 | 'electronhub', |
| 38 | 38 | 'openrouter', |
| 39 | 39 | 'chutes', |
| 40 | + 'nanogpt', | |
| 40 | 41 | ]; |
| 41 | 42 | |
| 42 | 43 | /** |
| @@ -82,6 +83,8 @@ async function getVector(source, sourceSettings, text, isQuery, directories) { | ||
| 82 | 83 | return sourceSettings.embeddings[text]; |
| 83 | 84 | case 'chutes': |
| 84 | 85 | return getOpenAIVector(text, source, directories, sourceSettings.model); |
| 86 | + case 'nanogpt': | |
| 87 | + return getOpenAIVector(text, source, directories, sourceSettings.model); | |
| 85 | 88 | } |
| 86 | 89 | |
| 87 | 90 | throw new Error(`Unknown vector source ${source}`); |
| @@ -150,6 +153,9 @@ async function getBatchVector(source, sourceSettings, texts, isQuery, directorie | ||
| 150 | 153 | case 'chutes': |
| 151 | 154 | results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model)); |
| 152 | 155 | break; |
| 156 | + case 'nanogpt': | |
| 157 | + results.push(...await getOpenAIBatchVector(batch, source, directories, sourceSettings.model)); | |
| 158 | + break; | |
| 153 | 159 | default: |
| 154 | 160 | throw new Error(`Unknown vector source ${source}`); |
| 155 | 161 | } |
| @@ -238,6 +244,10 @@ function getSourceSettings(source, request) { | ||
| 238 | 244 | return { |
| 239 | 245 | model: String(request.body.model || 'chutes-qwen-qwen3-embedding-8b'), |
| 240 | 246 | }; |
| 247 | + case 'nanogpt': | |
| 248 | + return { | |
| 249 | + model: String(request.body.model || 'text-embedding-3-small'), | |
| 250 | + }; | |
| 241 | 251 | default: |
| 242 | 252 | return {}; |
| 243 | 253 | } |
| @@ -47,6 +47,13 @@ const SOURCES = { | ||
| 47 | 47 | body.model = null; |
| 48 | 48 | }, |
| 49 | 49 | }, |
| 50 | + 'nanogpt': { | |
| 51 | + secretKey: SECRET_KEYS.NANOGPT, | |
| 52 | + url: 'https://nano-gpt.com/api/v1', | |
| 53 | + model: 'text-embedding-3-small', | |
| 54 | + headers: {}, | |
| 55 | + processBody: () => {}, | |
| 56 | + }, | |
| 50 | 57 | }; |
| 51 | 58 | |
| 52 | 59 | /** |