From ec6f53e29837440da799afe3b51e9d03ef56262a Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 24 Feb 2024 17:56:53 -0800 Subject: [PATCH] feat: litellm proxy frontend integration --- backend/apps/images/main.py | 2 + package.json | 2 +- src/lib/apis/litellm/index.ts | 108 ++- src/lib/components/chat/Settings/About.svelte | 16 +- .../chat/Settings/Connections.svelte | 164 ++-- .../components/chat/Settings/Models.svelte | 891 ++++++++++++------ 6 files changed, 823 insertions(+), 360 deletions(-) diff --git a/backend/apps/images/main.py b/backend/apps/images/main.py index 3ea8b171..9322ea7f 100644 --- a/backend/apps/images/main.py +++ b/backend/apps/images/main.py @@ -109,6 +109,7 @@ def get_models(user=Depends(get_current_user)): models = r.json() return models except Exception as e: + app.state.ENABLED = False raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e)) @@ -120,6 +121,7 @@ async def get_default_model(user=Depends(get_admin_user)): return {"model": options["sd_model_checkpoint"]} except Exception as e: + app.state.ENABLED = False raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e)) diff --git a/package.json b/package.json index 30549fdd..7938558d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-webui", - "version": "0.1.102", + "version": "0.1.103", "private": true, "scripts": { "dev": "vite dev --host", diff --git a/src/lib/apis/litellm/index.ts b/src/lib/apis/litellm/index.ts index 87d2654e..6466ee35 100644 --- a/src/lib/apis/litellm/index.ts +++ b/src/lib/apis/litellm/index.ts @@ -17,7 +17,7 @@ export const getLiteLLMModels = async (token: string = '') => { }) .catch((err) => { console.log(err); - error = `OpenAI: ${err?.error?.message ?? 'Network Problem'}`; + error = `LiteLLM: ${err?.error?.message ?? 'Network Problem'}`; return []; }); @@ -40,3 +40,109 @@ export const getLiteLLMModels = async (token: string = '') => { }) : models; }; + +export const getLiteLLMModelInfo = async (token: string = '') => { + let error = null; + + const res = await fetch(`${LITELLM_API_BASE_URL}/model/info`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = `LiteLLM: ${err?.error?.message ?? 'Network Problem'}`; + return []; + }); + + if (error) { + throw error; + } + + const models = Array.isArray(res) ? res : res?.data ?? null; + + return models; +}; + +type AddLiteLLMModelForm = { + name: string; + model: string; + api_base: string; + api_key: string; + rpm: string; +}; + +export const addLiteLLMModel = async (token: string = '', payload: AddLiteLLMModelForm) => { + let error = null; + + const res = await fetch(`${LITELLM_API_BASE_URL}/model/new`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + model_name: payload.name, + litellm_params: { + model: payload.model, + ...(payload.api_base === '' ? {} : { api_base: payload.api_base }), + ...(payload.api_key === '' ? {} : { api_key: payload.api_key }), + ...(isNaN(parseInt(payload.rpm)) ? {} : { rpm: parseInt(payload.rpm) }) + } + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = `LiteLLM: ${err?.error?.message ?? 'Network Problem'}`; + return []; + }); + + if (error) { + throw error; + } + + return res; +}; + +export const deleteLiteLLMModel = async (token: string = '', id: string) => { + let error = null; + + const res = await fetch(`${LITELLM_API_BASE_URL}/model/delete`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + id: id + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = `LiteLLM: ${err?.error?.message ?? 'Network Problem'}`; + return []; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/components/chat/Settings/About.svelte b/src/lib/components/chat/Settings/About.svelte index 6933ed3b..60c0d716 100644 --- a/src/lib/components/chat/Settings/About.svelte +++ b/src/lib/components/chat/Settings/About.svelte @@ -38,16 +38,18 @@ -
+ {#if ollamaVersion} +
-
-
Ollama Version
-
-
- {ollamaVersion ?? 'N/A'} +
+
Ollama Version
+
+
+ {ollamaVersion ?? 'N/A'} +
-
+ {/if}
diff --git a/src/lib/components/chat/Settings/Connections.svelte b/src/lib/components/chat/Settings/Connections.svelte index 486fd9b7..db24c8a0 100644 --- a/src/lib/components/chat/Settings/Connections.svelte +++ b/src/lib/components/chat/Settings/Connections.svelte @@ -15,6 +15,9 @@ let OPENAI_API_KEY = ''; let OPENAI_API_BASE_URL = ''; + let showOpenAI = false; + let showLiteLLM = false; + const updateOpenAIHandler = async () => { OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL); OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY); @@ -45,7 +48,7 @@
{ updateOpenAIHandler(); dispatch('save'); @@ -56,81 +59,100 @@ // }); }} > -
-
Ollama API URL
-
-
- +
+
+
+
+
OpenAI API
+ +
+ + {#if showOpenAI} +
+
API Key
+
+
+ +
+
+
+ +
+
API Base URL
+
+
+ +
+
+
+ WebUI will make requests to '{OPENAI_API_BASE_URL}/chat' +
+
+ {/if}
- -
- -
- Trouble accessing Ollama? - - Click here for help. - -
-
- -
- -
-
-
OpenAI API Key
-
-
- -
+ + + +
-
-
-
OpenAI API Base URL
-
-
- -
-
- WebUI will make requests to '{OPENAI_API_BASE_URL}/chat' + Trouble accessing Ollama? + + Click here for help. +
diff --git a/src/lib/components/chat/Settings/Models.svelte b/src/lib/components/chat/Settings/Models.svelte index c7ca7908..455927df 100644 --- a/src/lib/components/chat/Settings/Models.svelte +++ b/src/lib/components/chat/Settings/Models.svelte @@ -2,14 +2,33 @@ import queue from 'async/queue'; import toast from 'svelte-french-toast'; - import { createModel, deleteModel, pullModel } from '$lib/apis/ollama'; + import { createModel, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama'; import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants'; import { WEBUI_NAME, models, user } from '$lib/stores'; import { splitStream } from '$lib/utils'; + import { onMount } from 'svelte'; + import { addLiteLLMModel, deleteLiteLLMModel, getLiteLLMModelInfo } from '$lib/apis/litellm'; export let getModels: Function; + let showLiteLLM = false; + let showLiteLLMParams = false; + + let liteLLMModelInfo = []; + + let liteLLMModel = ''; + let liteLLMModelName = ''; + let liteLLMAPIBase = ''; + let liteLLMAPIKey = ''; + let liteLLMRPM = ''; + + let deleteLiteLLMModelId = ''; + + $: liteLLMModelName = liteLLMModel; + // Models + let showExperimentalOllama = false; + let ollamaVersion = ''; const MAX_PARALLEL_DOWNLOADS = 3; const modelDownloadQueue = queue( (task: { modelName: string }, cb) => @@ -286,256 +305,184 @@ opts.callback({ success: true, modelName: opts.modelName }); } }; + + const addLiteLLMModelHandler = async () => { + if (!liteLLMModelInfo.find((info) => info.model_name === liteLLMModelName)) { + const res = await addLiteLLMModel(localStorage.token, { + name: liteLLMModelName, + model: liteLLMModel, + api_base: liteLLMAPIBase, + api_key: liteLLMAPIKey, + rpm: liteLLMRPM + }).catch((error) => { + toast.error(error); + return null; + }); + + if (res) { + if (res.message) { + toast.success(res.message); + } + } + } else { + toast.error(`Model ${liteLLMModelName} already exists.`); + } + + liteLLMModelName = ''; + liteLLMModel = ''; + liteLLMAPIBase = ''; + liteLLMAPIKey = ''; + liteLLMRPM = ''; + + liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token); + models.set(await getModels()); + }; + + const deleteLiteLLMModelHandler = async () => { + const res = await deleteLiteLLMModel(localStorage.token, deleteLiteLLMModelId).catch( + (error) => { + toast.error(error); + return null; + } + ); + + if (res) { + if (res.message) { + toast.success(res.message); + } + } + + deleteLiteLLMModelId = ''; + liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token); + models.set(await getModels()); + }; + + onMount(async () => { + ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false); + liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token); + });
-
-
-
Pull a model from Ollama.com
-
-
- -
- -
- -
- To access the available model names for downloading, click here. -
- - {#if Object.keys(modelDownloadStatus).length > 0} - {#each Object.keys(modelDownloadStatus) as model} -
-
{model}
-
-
- {modelDownloadStatus[model].pullProgress ?? 0}% -
-
- {modelDownloadStatus[model].digest} -
-
-
- {/each} - {/if} -
- -
- -
-
Delete a model
-
-
- -
- -
-
- -
- - { - uploadModelHandler(); - }} - > -
-
- Upload a GGUF model (Experimental) -
- - -
- -
-
- {#if modelUploadMode === 'file'} -
- { - console.log(modelInputFile); - }} - accept=".gguf" - required - hidden - /> - - -
- {:else} -
- -
- {/if} -
- - {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} - +
+ +
+ To access the available model names for downloading, click here. +
+ + {#if Object.keys(modelDownloadStatus).length > 0} + {#each Object.keys(modelDownloadStatus) as model} +
+
{model}
+
+
+ {modelDownloadStatus[model].pullProgress ?? 0}% +
+
+ {modelDownloadStatus[model].digest} +
+
- {:else} + {/each} + {/if} +
+ +
+
Delete a model
+
+
+ +
+ +
+
+ +
+
+
Experimental
+ +
+
+ + {#if showExperimentalOllama} + { + uploadModelHandler(); + }} + > +
+
Upload a GGUF model
+ + +
+ +
+
+ {#if modelUploadMode === 'file'} +
+ { + console.log(modelInputFile); + }} + accept=".gguf" + required + hidden + /> + + +
+ {:else} +
+ +
+ {/if} +
+ + {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} + + {/if} +
+ + {#if (modelUploadMode === 'file' && modelInputFile && modelInputFile.length > 0) || (modelUploadMode === 'url' && modelFileUrl !== '')} +
+
+
Modelfile Content
+