feat: add optional API key support for Lemonade provider (#5281)

* add API key param to Lemonade LLM Provider and Embedding Provider

* add LEMONADE_LLM_API_KEY to .env.example

* add api key to aibitat provider

* fix api key from being sent to frontend

* fix tooltip id

* add null fallback for `apiKey`

* remove console log

* add missing api keys

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Marcello Fitton 2026-03-30 14:44:12 -07:00 committed by GitHub
parent 3f9eaa1a76
commit 0bfd27c6df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 94 additions and 10 deletions

View File

@ -62,6 +62,31 @@ export default function LemonadeEmbeddingOptions({ settings }) {
autoComplete="off" autoComplete="off"
/> />
</div> </div>
<div className="flex flex-col w-60">
<div
data-tooltip-place="top"
data-tooltip-id="lemonade-embedding-api-key"
className="flex gap-x-1 items-center mb-3"
>
<label className="text-white text-sm font-semibold block">
API Key (optional)
</label>
<Info
size={16}
className="text-theme-text-secondary cursor-pointer"
/>
<Tooltip id="lemonade-embedding-api-key">
The API key for your Lemonade instance
</Tooltip>
</div>
<input
type="password"
name="LemonadeLLMApiKey"
defaultValue={settings?.LemonadeLLMApiKey ? "*".repeat(20) : ""}
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
autoComplete="off"
/>
</div>
</div> </div>
<div className="flex justify-start mt-4"> <div className="flex justify-start mt-4">
<button <button

View File

@ -159,6 +159,43 @@ export default function LemonadeOptions({ settings }) {
autoComplete="off" autoComplete="off"
/> />
</div> </div>
<div className="flex flex-col w-60">
<div className="flex items-center gap-1 mb-3">
<label className="text-white text-sm font-semibold block">
API Key (optional)
</label>
<Tooltip
id="lemonade-api-key"
place="top"
delayShow={300}
delayHide={800}
clickable={true}
className="tooltip !text-xs !opacity-100 z-99"
style={{
maxWidth: "350px",
whiteSpace: "normal",
wordWrap: "break-word",
}}
>
The API key for your Lemonade server
</Tooltip>
<div
className="text-theme-text-secondary cursor-pointer hover:bg-theme-bg-primary flex items-center justify-center rounded-full"
data-tooltip-id="lemonade-api-key"
data-tooltip-place="top"
data-tooltip-delay-hide={800}
>
<Info size={18} className="text-theme-text-secondary" />
</div>
</div>
<input
type="password"
name="LemonadeLLMApiKey"
defaultValue={settings?.LemonadeLLMApiKey ? "*".repeat(20) : ""}
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
autoComplete="off"
/>
</div>
<LemonadeModelSelection <LemonadeModelSelection
selectedModelId={selectedModelId} selectedModelId={selectedModelId}
setSelectedModelId={setSelectedModelId} setSelectedModelId={setSelectedModelId}

View File

@ -180,6 +180,7 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long.
# LEMONADE_LLM_BASE_PATH='http://127.0.0.1:8000' # LEMONADE_LLM_BASE_PATH='http://127.0.0.1:8000'
# LEMONADE_LLM_MODEL_PREF='Llama-3.2-1B-Instruct-GGUF' # LEMONADE_LLM_MODEL_PREF='Llama-3.2-1B-Instruct-GGUF'
# LEMONADE_LLM_MODEL_TOKEN_LIMIT=8192 # LEMONADE_LLM_MODEL_TOKEN_LIMIT=8192
# LEMONADE_LLM_API_KEY=
########################################### ###########################################
######## Embedding API SElECTION ########## ######## Embedding API SElECTION ##########

View File

@ -34,6 +34,9 @@ function lemonadeUtilsEndpoints(app) {
const lemonadeResponse = await fetch(lemonadeUrl.toString(), { const lemonadeResponse = await fetch(lemonadeUrl.toString(), {
method: "POST", method: "POST",
headers: { headers: {
...(!!process.env.LEMONADE_LLM_API_KEY
? { Authorization: `Bearer ${process.env.LEMONADE_LLM_API_KEY}` }
: {}),
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
@ -129,6 +132,9 @@ function lemonadeUtilsEndpoints(app) {
const lemonadeResponse = await fetch(lemonadeUrl.toString(), { const lemonadeResponse = await fetch(lemonadeUrl.toString(), {
method: "POST", method: "POST",
headers: { headers: {
...(!!process.env.LEMONADE_LLM_API_KEY
? { Authorization: `Bearer ${process.env.LEMONADE_LLM_API_KEY}` }
: {}),
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({

View File

@ -714,6 +714,7 @@ const SystemSettings = {
// Lemonade Keys // Lemonade Keys
LemonadeLLMBasePath: process.env.LEMONADE_LLM_BASE_PATH, LemonadeLLMBasePath: process.env.LEMONADE_LLM_BASE_PATH,
LemonadeLLMApiKey: !!process.env.LEMONADE_LLM_API_KEY,
LemonadeLLMModelPref: process.env.LEMONADE_LLM_MODEL_PREF, LemonadeLLMModelPref: process.env.LEMONADE_LLM_MODEL_PREF,
LemonadeLLMModelTokenLimit: LemonadeLLMModelTokenLimit:
process.env.LEMONADE_LLM_MODEL_TOKEN_LIMIT || 8192, process.env.LEMONADE_LLM_MODEL_TOKEN_LIMIT || 8192,

View File

@ -22,7 +22,7 @@ class LemonadeLLM {
process.env.LEMONADE_LLM_BASE_PATH, process.env.LEMONADE_LLM_BASE_PATH,
"openai" "openai"
), ),
apiKey: null, apiKey: process.env.LEMONADE_LLM_API_KEY ?? null,
}); });
this.model = modelPreference || process.env.LEMONADE_LLM_MODEL_PREF; this.model = modelPreference || process.env.LEMONADE_LLM_MODEL_PREF;
@ -202,7 +202,7 @@ class LemonadeLLM {
process.env.LEMONADE_LLM_BASE_PATH, process.env.LEMONADE_LLM_BASE_PATH,
"openai" "openai"
), ),
apiKey: null, apiKey: process.env.LEMONADE_LLM_API_KEY ?? null,
}); });
const { labels = [] } = await client.models.retrieve(this.model); const { labels = [] } = await client.models.retrieve(this.model);
@ -233,14 +233,17 @@ class LemonadeLLM {
const endpoint = new URL(parseLemonadeServerEndpoint(basePath, "openai")); const endpoint = new URL(parseLemonadeServerEndpoint(basePath, "openai"));
endpoint.pathname += "/load"; endpoint.pathname += "/load";
console.log(endpoint.toString());
LemonadeLLM.slog( LemonadeLLM.slog(
`Loading model ${model} with context size ${this.promptWindowLimit()}` `Loading model ${model} with context size ${this.promptWindowLimit()}`
); );
await fetch(endpoint.toString(), { await fetch(endpoint.toString(), {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: {
...(process.env.LEMONADE_LLM_API_KEY
? { Authorization: `Bearer ${process.env.LEMONADE_LLM_API_KEY}` }
: {}),
"Content-Type": "application/json",
},
body: JSON.stringify({ body: JSON.stringify({
model_name: String(model), model_name: String(model),
ctx_size: Number(this.promptWindowLimit()), ctx_size: Number(this.promptWindowLimit()),
@ -343,7 +346,14 @@ async function getAllLemonadeModels(basePath = null, task = "chat") {
); );
lemonadeUrl.pathname += "/models"; lemonadeUrl.pathname += "/models";
lemonadeUrl.searchParams.append("show_all", "true"); lemonadeUrl.searchParams.append("show_all", "true");
await fetch(lemonadeUrl.toString())
await fetch(lemonadeUrl.toString(), {
headers: {
...(!!process.env.LEMONADE_LLM_API_KEY
? { Authorization: `Bearer ${process.env.LEMONADE_LLM_API_KEY}` }
: {}),
},
})
.then((res) => res.json()) .then((res) => res.json())
.then(({ data }) => { .then(({ data }) => {
data?.forEach((model) => { data?.forEach((model) => {

View File

@ -13,7 +13,7 @@ class LemonadeEmbedder {
process.env.EMBEDDING_BASE_PATH, process.env.EMBEDDING_BASE_PATH,
"openai" "openai"
), ),
apiKey: null, apiKey: process.env.LEMONADE_LLM_API_KEY ?? null,
}); });
this.model = process.env.EMBEDDING_MODEL_PREF; this.model = process.env.EMBEDDING_MODEL_PREF;

View File

@ -402,7 +402,7 @@ class Provider {
configuration: { configuration: {
baseURL: process.env.LEMONADE_LLM_BASE_PATH, baseURL: process.env.LEMONADE_LLM_BASE_PATH,
}, },
apiKey: null, apiKey: process.env.LEMONADE_LLM_API_KEY ?? null,
...config, ...config,
}); });
default: default:

View File

@ -27,7 +27,7 @@ class LemonadeProvider extends InheritMultiple([Provider, UnTooled]) {
process.env.LEMONADE_LLM_BASE_PATH, process.env.LEMONADE_LLM_BASE_PATH,
"openai" "openai"
), ),
apiKey: null, apiKey: process.env.LEMONADE_LLM_API_KEY ?? null,
maxRetries: 3, maxRetries: 3,
}); });

View File

@ -829,6 +829,10 @@ const KEY_MAPPING = {
envKey: "LEMONADE_LLM_BASE_PATH", envKey: "LEMONADE_LLM_BASE_PATH",
checks: [isValidURL], checks: [isValidURL],
}, },
LemonadeLLMApiKey: {
envKey: "LEMONADE_LLM_API_KEY",
checks: [],
},
LemonadeLLMModelPref: { LemonadeLLMModelPref: {
envKey: "LEMONADE_LLM_MODEL_PREF", envKey: "LEMONADE_LLM_MODEL_PREF",
checks: [isNotEmpty], checks: [isNotEmpty],