Skip to content

Commit 8cdadd8

Browse files
Sync models from remote for FireworksAI (#4475)
resolves #4474
1 parent 0b18ac6 commit 8cdadd8

File tree

8 files changed

+140
-211
lines changed

8 files changed

+140
-211
lines changed

frontend/src/components/LLMSelection/FireworksAiOptions/index.jsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import System from "@/models/system";
22
import { useState, useEffect } from "react";
33

44
export default function FireworksAiOptions({ settings }) {
5+
const [inputValue, setInputValue] = useState(settings?.FireworksAiLLMApiKey);
6+
const [fireworksAiApiKey, setFireworksAiApiKey] = useState(
7+
settings?.FireworksAiLLMApiKey
8+
);
9+
510
return (
611
<div className="flex gap-[36px] mt-1.5">
712
<div className="flex flex-col w-60">
@@ -17,22 +22,27 @@ export default function FireworksAiOptions({ settings }) {
1722
required={true}
1823
autoComplete="off"
1924
spellCheck={false}
25+
onChange={(e) => setInputValue(e.target.value)}
26+
onBlur={() => setFireworksAiApiKey(inputValue)}
2027
/>
2128
</div>
2229
{!settings?.credentialsOnly && (
23-
<FireworksAiModelSelection settings={settings} />
30+
<FireworksAiModelSelection
31+
apiKey={fireworksAiApiKey}
32+
settings={settings}
33+
/>
2434
)}
2535
</div>
2636
);
2737
}
28-
function FireworksAiModelSelection({ settings }) {
38+
function FireworksAiModelSelection({ apiKey, settings }) {
2939
const [groupedModels, setGroupedModels] = useState({});
3040
const [loading, setLoading] = useState(true);
3141

3242
useEffect(() => {
3343
async function findCustomModels() {
3444
setLoading(true);
35-
const { models } = await System.customModels("fireworksai");
45+
const { models } = await System.customModels("fireworksai", apiKey);
3646

3747
if (models?.length > 0) {
3848
const modelsByOrganization = models.reduce((acc, model) => {
@@ -47,7 +57,7 @@ function FireworksAiModelSelection({ settings }) {
4757
setLoading(false);
4858
}
4959
findCustomModels();
50-
}, []);
60+
}, [apiKey]);
5161

5262
if (loading || Object.keys(groupedModels).length === 0) {
5363
return (

server/storage/models/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ tesseract
1111
ppio
1212
context-windows/*
1313
MintplexLabs
14-
cometapi
14+
cometapi
15+
fireworks

server/utils/AiProviders/fireworksAi/index.js

Lines changed: 122 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const { safeJsonParse } = require("../../http");
14
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
25
const {
36
LLMPerformanceMonitor,
@@ -6,13 +9,16 @@ const {
69
handleDefaultStreamResponseV2,
710
} = require("../../helpers/chat/responses");
811

9-
function fireworksAiModels() {
10-
const { MODELS } = require("./models.js");
11-
return MODELS || {};
12-
}
12+
const cacheFolder = path.resolve(
13+
process.env.STORAGE_DIR
14+
? path.resolve(process.env.STORAGE_DIR, "models", "fireworks")
15+
: path.resolve(__dirname, `../../../storage/models/fireworks`)
16+
);
1317

1418
class FireworksAiLLM {
1519
constructor(embedder = null, modelPreference = null) {
20+
this.className = "FireworksAiLLM";
21+
1622
if (!process.env.FIREWORKS_AI_LLM_API_KEY)
1723
throw new Error("No FireworksAI API key was set.");
1824
const { OpenAI: OpenAIApi } = require("openai");
@@ -29,6 +35,51 @@ class FireworksAiLLM {
2935

3036
this.embedder = !embedder ? new NativeEmbedder() : embedder;
3137
this.defaultTemp = 0.7;
38+
39+
if (!fs.existsSync(cacheFolder))
40+
fs.mkdirSync(cacheFolder, { recursive: true });
41+
this.cacheModelPath = path.resolve(cacheFolder, "models.json");
42+
this.cacheAtPath = path.resolve(cacheFolder, ".cached_at");
43+
}
44+
45+
log(text, ...args) {
46+
console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
47+
}
48+
49+
// This checks if the .cached_at file has a timestamp that is more than 1Week (in millis)
50+
// from the current date. If it is, then we will refetch the API so that all the models are up
51+
// to date.
52+
#cacheIsStale() {
53+
const MAX_STALE = 6.048e8; // 1 Week in MS
54+
if (!fs.existsSync(this.cacheAtPath)) return true;
55+
const now = Number(new Date());
56+
const timestampMs = Number(fs.readFileSync(this.cacheAtPath));
57+
return now - timestampMs > MAX_STALE;
58+
}
59+
60+
// This function fetches the models from the ApiPie API and caches them locally.
61+
// We do this because the ApiPie API has a lot of models, and we need to get the proper token context window
62+
// for each model and this is a constructor property - so we can really only get it if this cache exists.
63+
// We used to have this as a chore, but given there is an API to get the info - this makes little sense.
64+
// This might slow down the first request, but we need the proper token context window
65+
// for each model and this is a constructor property - so we can really only get it if this cache exists.
66+
async #syncModels() {
67+
if (fs.existsSync(this.cacheModelPath) && !this.#cacheIsStale())
68+
return false;
69+
70+
this.log(
71+
"Model cache is not present or stale. Fetching from FireworksAI API."
72+
);
73+
await fireworksAiModels();
74+
return;
75+
}
76+
77+
models() {
78+
if (!fs.existsSync(this.cacheModelPath)) return {};
79+
return safeJsonParse(
80+
fs.readFileSync(this.cacheModelPath, { encoding: "utf-8" }),
81+
{}
82+
);
3283
}
3384

3485
#appendContext(contextTexts = []) {
@@ -43,28 +94,31 @@ class FireworksAiLLM {
4394
);
4495
}
4596

46-
allModelInformation() {
47-
return fireworksAiModels();
48-
}
49-
5097
streamingEnabled() {
5198
return "streamGetChatCompletion" in this;
5299
}
53100

54101
static promptWindowLimit(modelName) {
55-
const availableModels = fireworksAiModels();
102+
const cacheModelPath = path.resolve(cacheFolder, "models.json");
103+
const availableModels = fs.existsSync(cacheModelPath)
104+
? safeJsonParse(
105+
fs.readFileSync(cacheModelPath, { encoding: "utf-8" }),
106+
{}
107+
)
108+
: {};
56109
return availableModels[modelName]?.maxLength || 4096;
57110
}
58111

59112
// Ensure the user set a value for the token limit
60113
// and if undefined - assume 4096 window.
61114
promptWindowLimit() {
62-
const availableModels = this.allModelInformation();
115+
const availableModels = this.models();
63116
return availableModels[this.model]?.maxLength || 4096;
64117
}
65118

66119
async isValidChatCompletionModel(model = "") {
67-
const availableModels = this.allModelInformation();
120+
await this.#syncModels();
121+
const availableModels = this.models();
68122
return availableModels.hasOwnProperty(model);
69123
}
70124

@@ -151,6 +205,63 @@ class FireworksAiLLM {
151205
}
152206
}
153207

208+
async function fireworksAiModels(providedApiKey = null) {
209+
const apiKey = providedApiKey || process.env.FIREWORKS_AI_LLM_API_KEY || null;
210+
const { OpenAI: OpenAIApi } = require("openai");
211+
const client = new OpenAIApi({
212+
baseURL: "https://api.fireworks.ai/inference/v1",
213+
apiKey: apiKey,
214+
});
215+
216+
return await client.models
217+
.list()
218+
.then((res) => res.data)
219+
.then((models = []) => {
220+
const validModels = {};
221+
models.forEach((model) => {
222+
// There are many models - the ones without a context length are not chat models
223+
if (!model.hasOwnProperty("context_length")) return;
224+
225+
validModels[model.id] = {
226+
id: model.id,
227+
name: model.id.split("/").pop(),
228+
organization: model.owned_by,
229+
subtype: model.type,
230+
maxLength: model.context_length ?? 4096,
231+
};
232+
});
233+
234+
if (Object.keys(validModels).length === 0) {
235+
console.log("fireworksAi: No models found");
236+
return {};
237+
}
238+
239+
// Cache all response information
240+
if (!fs.existsSync(cacheFolder))
241+
fs.mkdirSync(cacheFolder, { recursive: true });
242+
fs.writeFileSync(
243+
path.resolve(cacheFolder, "models.json"),
244+
JSON.stringify(validModels),
245+
{
246+
encoding: "utf-8",
247+
}
248+
);
249+
fs.writeFileSync(
250+
path.resolve(cacheFolder, ".cached_at"),
251+
String(Number(new Date())),
252+
{
253+
encoding: "utf-8",
254+
}
255+
);
256+
257+
return validModels;
258+
})
259+
.catch((e) => {
260+
console.error(e);
261+
return {};
262+
});
263+
}
264+
154265
module.exports = {
155266
FireworksAiLLM,
156267
fireworksAiModels,

server/utils/AiProviders/fireworksAi/models.js

Lines changed: 0 additions & 124 deletions
This file was deleted.

server/utils/AiProviders/fireworksAi/scripts/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

server/utils/AiProviders/fireworksAi/scripts/chat_models.txt

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)