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
10 changed files with 94 additions and 10 deletions

View File

@@ -62,6 +62,31 @@ export default function LemonadeEmbeddingOptions({ settings }) {
autoComplete="off"
/>
</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 className="flex justify-start mt-4">
<button

View File

@@ -159,6 +159,43 @@ export default function LemonadeOptions({ settings }) {
autoComplete="off"
/>
</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
selectedModelId={selectedModelId}
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_MODEL_PREF='Llama-3.2-1B-Instruct-GGUF'
# LEMONADE_LLM_MODEL_TOKEN_LIMIT=8192
# LEMONADE_LLM_API_KEY=
###########################################
######## Embedding API SElECTION ##########
@@ -452,4 +453,4 @@ TTS_PROVIDER="native"
# Set to "true" to enable. This can reduce token costs by 80% when you have
# many tools/MCP servers enabled.
# AGENT_SKILL_RERANKER_ENABLED="true"
# AGENT_SKILL_RERANKER_TOP_N=15 # (optional) Number of top tools to keep after reranking (default: 15)
# AGENT_SKILL_RERANKER_TOP_N=15 # (optional) Number of top tools to keep after reranking (default: 15)

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ class LemonadeLLM {
process.env.LEMONADE_LLM_BASE_PATH,
"openai"
),
apiKey: null,
apiKey: process.env.LEMONADE_LLM_API_KEY ?? null,
});
this.model = modelPreference || process.env.LEMONADE_LLM_MODEL_PREF;
@@ -202,7 +202,7 @@ class LemonadeLLM {
process.env.LEMONADE_LLM_BASE_PATH,
"openai"
),
apiKey: null,
apiKey: process.env.LEMONADE_LLM_API_KEY ?? null,
});
const { labels = [] } = await client.models.retrieve(this.model);
@@ -233,14 +233,17 @@ class LemonadeLLM {
const endpoint = new URL(parseLemonadeServerEndpoint(basePath, "openai"));
endpoint.pathname += "/load";
console.log(endpoint.toString());
LemonadeLLM.slog(
`Loading model ${model} with context size ${this.promptWindowLimit()}`
);
await fetch(endpoint.toString(), {
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({
model_name: String(model),
ctx_size: Number(this.promptWindowLimit()),
@@ -343,7 +346,14 @@ async function getAllLemonadeModels(basePath = null, task = "chat") {
);
lemonadeUrl.pathname += "/models";
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(({ data }) => {
data?.forEach((model) => {

View File

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

View File

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

View File

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

View File

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