θΏ™ζ˜―indexlocζδΎ›ηš„ζœεŠ‘οΌŒδΈθ¦θΎ“ε…₯任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ frontend/bundleinspector.html

#server
server/swagger/openapi.json
server/**/*.mjs

#embed
**/static/**
Expand Down
59 changes: 41 additions & 18 deletions server/utils/AiProviders/gemini/defaultModels.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,61 @@
const { MODEL_MAP } = require("../modelMap");

const stableModels = [
"gemini-pro",
"gemini-1.0-pro",
"gemini-1.5-pro-latest",
"gemini-1.5-flash-latest",
// %STABLE_MODELS% - updated 2025-04-07T20:29:49.276Z
"gemini-1.5-pro-001",
"gemini-1.5-pro-002",
"gemini-1.5-pro",
"gemini-1.5-flash-001",
"gemini-1.5-flash",
"gemini-1.5-flash-002",
"gemini-1.5-flash-8b",
"gemini-1.5-flash-8b-001",
"gemini-2.0-flash",
"gemini-2.0-flash-001",
"gemini-2.0-flash-lite-001",
"gemini-2.0-flash-lite",
// %EOC_STABLE_MODELS%
];

const experimentalModels = [
"gemini-1.5-pro-exp-0801",
"gemini-1.5-pro-exp-0827",
"gemini-1.5-flash-exp-0827",
// There are some models that are only available in the v1beta API
// and some models that are only available in the v1 API
// generally, v1beta models have `exp` in the name, but not always
// so we check for both against a static list as well via API.
const v1BetaModels = [
// %V1BETA_MODELS% - updated 2025-04-07T20:29:49.276Z
"gemini-1.5-pro-latest",
"gemini-1.5-flash-latest",
"gemini-1.5-flash-8b-latest",
"gemini-1.5-flash-8b-exp-0827",
"gemini-exp-1114",
"gemini-exp-1121",
"gemini-1.5-flash-8b-exp-0924",
"gemini-2.5-pro-exp-03-25",
"gemini-2.5-pro-preview-03-25",
"gemini-2.0-flash-exp",
"gemini-2.0-flash-exp-image-generation",
"gemini-2.0-flash-lite-preview-02-05",
"gemini-2.0-flash-lite-preview",
"gemini-2.0-pro-exp",
"gemini-2.0-pro-exp-02-05",
"gemini-exp-1206",
"gemini-2.0-flash-thinking-exp-01-21",
"gemini-2.0-flash-thinking-exp",
"gemini-2.0-flash-thinking-exp-1219",
"learnlm-1.5-pro-experimental",
"gemini-2.0-flash-exp",
"gemma-3-1b-it",
"gemma-3-4b-it",
"gemma-3-12b-it",
"gemma-3-27b-it",
// %EOC_V1BETA_MODELS%
];

// There are some models that are only available in the v1beta API
// and some models that are only available in the v1 API
// generally, v1beta models have `exp` in the name, but not always
// so we check for both against a static list as well.
const v1BetaModels = ["gemini-1.5-pro-latest", "gemini-1.5-flash-latest"];

const defaultGeminiModels = [
...stableModels.map((model) => ({
id: model,
name: model,
contextWindow: MODEL_MAP.gemini[model],
experimental: false,
})),
...experimentalModels.map((model) => ({
...v1BetaModels.map((model) => ({
id: model,
name: model,
contextWindow: MODEL_MAP.gemini[model],
Expand Down
210 changes: 142 additions & 68 deletions server/utils/AiProviders/gemini/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,11 @@ class GeminiLLM {
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
this.model =
modelPreference || process.env.GEMINI_LLM_MODEL_PREF || "gemini-pro";

const isExperimental = this.isExperimentalModel(this.model);
this.gemini = genAI.getGenerativeModel(
{ model: this.model },
{
apiVersion:
/**
* There are some models that are only available in the v1beta API
* and some models that are only available in the v1 API
* generally, v1beta models have `exp` in the name, but not always
* so we check for both against a static list as well.
* @see {v1BetaModels}
*/
this.model.includes("exp") || v1BetaModels.includes(this.model)
? "v1beta"
: "v1",
}
{ apiVersion: isExperimental ? "v1beta" : "v1" }
);
this.limits = {
history: this.promptWindowLimit() * 0.15,
Expand All @@ -59,7 +49,7 @@ class GeminiLLM {
this.cacheModelPath = path.resolve(cacheFolder, "models.json");
this.cacheAtPath = path.resolve(cacheFolder, ".cached_at");
this.#log(
`Initialized with model: ${this.model} (${this.promptWindowLimit()})`
`Initialized with model: ${this.model} ${isExperimental ? "[Experimental v1beta]" : "[Stable v1]"} - ctx: ${this.promptWindowLimit()}`
);
}

Expand All @@ -71,7 +61,7 @@ class GeminiLLM {
// from the current date. If it is, then we will refetch the API so that all the models are up
// to date.
static cacheIsStale() {
const MAX_STALE = 6.048e8; // 1 Week in MS
const MAX_STALE = 8.64e7; // 1 day in MS
if (!fs.existsSync(path.resolve(cacheFolder, ".cached_at"))) return true;
const now = Number(new Date());
const timestampMs = Number(
Expand Down Expand Up @@ -168,6 +158,28 @@ class GeminiLLM {
}
}

/**
* Checks if a model is experimental by reading from the cache if available, otherwise it will perform
* a blind check against the v1BetaModels list - which is manually maintained and updated.
* @param {string} modelName - The name of the model to check
* @returns {boolean} A boolean indicating if the model is experimental
*/
isExperimentalModel(modelName) {
if (
fs.existsSync(cacheFolder) &&
fs.existsSync(path.resolve(cacheFolder, "models.json"))
) {
const models = safeJsonParse(
fs.readFileSync(path.resolve(cacheFolder, "models.json"))
);
const model = models.find((model) => model.id === modelName);
if (!model) return false;
return model.experimental;
}

return modelName.includes("exp") || v1BetaModels.includes(modelName);
}

/**
* Fetches Gemini models from the Google Generative AI API
* @param {string} apiKey - The API key to use for the request
Expand All @@ -186,63 +198,125 @@ class GeminiLLM {
);
}

const url = new URL(
"https://generativelanguage.googleapis.com/v1beta/models"
);
url.searchParams.set("pageSize", limit);
url.searchParams.set("key", apiKey);
if (pageToken) url.searchParams.set("pageToken", pageToken);
let success = false;

const models = await fetch(url.toString(), {
method: "GET",
headers: { "Content-Type": "application/json" },
})
.then((res) => res.json())
.then((data) => {
if (data.error) throw new Error(data.error.message);
return data.models ?? [];
})
.then((models) => {
success = true;
return models
.filter(
(model) => !model.displayName.toLowerCase().includes("tuning")
)
.filter((model) =>
model.supportedGenerationMethods.includes("generateContent")
) // Only generateContent is supported
.map((model) => {
return {
id: model.name.split("/").pop(),
name: model.displayName,
contextWindow: model.inputTokenLimit,
experimental: model.name.includes("exp"),
};
});
})
.catch((e) => {
console.error(`Gemini:getGeminiModels`, e.message);
success = false;
return defaultGeminiModels;
});
const stableModels = [];
const allModels = [];

if (success) {
console.log(
`\x1b[32m[GeminiLLM]\x1b[0m Writing cached models API response to disk.`
);
if (!fs.existsSync(cacheFolder))
fs.mkdirSync(cacheFolder, { recursive: true });
fs.writeFileSync(
path.resolve(cacheFolder, "models.json"),
JSON.stringify(models)
// Fetch from v1
try {
const url = new URL(
"https://generativelanguage.googleapis.com/v1/models"
);
fs.writeFileSync(
path.resolve(cacheFolder, ".cached_at"),
new Date().getTime().toString()
url.searchParams.set("pageSize", limit);
url.searchParams.set("key", apiKey);
if (pageToken) url.searchParams.set("pageToken", pageToken);
await fetch(url.toString(), {
method: "GET",
headers: { "Content-Type": "application/json" },
})
.then((res) => res.json())
.then((data) => {
if (data.error) throw new Error(data.error.message);
return data.models ?? [];
})
.then((models) => {
return models
.filter(
(model) => !model.displayName?.toLowerCase()?.includes("tuning")
) // remove tuning models
.filter(
(model) =>
!model.description?.toLowerCase()?.includes("deprecated")
) // remove deprecated models (in comment)
.filter((model) =>
// Only generateContent is supported
model.supportedGenerationMethods.includes("generateContent")
)
.map((model) => {
stableModels.push(model.name);
allModels.push({
id: model.name.split("/").pop(),
name: model.displayName,
contextWindow: model.inputTokenLimit,
experimental: false,
});
});
})
.catch((e) => {
console.error(`Gemini:getGeminiModelsV1`, e.message);
return;
});
} catch (e) {
console.error(`Gemini:getGeminiModelsV1`, e.message);
}

// Fetch from v1beta
try {
const url = new URL(
"https://generativelanguage.googleapis.com/v1beta/models"
);
url.searchParams.set("pageSize", limit);
url.searchParams.set("key", apiKey);
if (pageToken) url.searchParams.set("pageToken", pageToken);
await fetch(url.toString(), {
method: "GET",
headers: { "Content-Type": "application/json" },
})
.then((res) => res.json())
.then((data) => {
if (data.error) throw new Error(data.error.message);
return data.models ?? [];
})
.then((models) => {
return models
.filter((model) => !stableModels.includes(model.name)) // remove stable models that are already in the v1 list
.filter(
(model) => !model.displayName?.toLowerCase()?.includes("tuning")
) // remove tuning models
.filter(
(model) =>
!model.description?.toLowerCase()?.includes("deprecated")
) // remove deprecated models (in comment)
.filter((model) =>
// Only generateContent is supported
model.supportedGenerationMethods.includes("generateContent")
)
.map((model) => {
allModels.push({
id: model.name.split("/").pop(),
name: model.displayName,
contextWindow: model.inputTokenLimit,
experimental: true,
});
});
})
.catch((e) => {
console.error(`Gemini:getGeminiModelsV1beta`, e.message);
return;
});
} catch (e) {
console.error(`Gemini:getGeminiModelsV1beta`, e.message);
}

if (allModels.length === 0) {
console.error(`Gemini:getGeminiModels - No models found`);
return defaultGeminiModels;
}
return models;

console.log(
`\x1b[32m[GeminiLLM]\x1b[0m Writing cached models API response to disk.`
);
if (!fs.existsSync(cacheFolder))
fs.mkdirSync(cacheFolder, { recursive: true });
fs.writeFileSync(
path.resolve(cacheFolder, "models.json"),
JSON.stringify(allModels)
);
fs.writeFileSync(
path.resolve(cacheFolder, ".cached_at"),
new Date().getTime().toString()
);

return allModels;
}

/**
Expand Down
Loading