Fix: Validate GGUF files before loading (#6238)
This commit adds a GGUF validation check for both the main model file and the `mmproj` file (if present) before they are loaded. This prevents the extension from crashing if an invalid GGUF file is provided. The `GgufMetadata` interface and `loadMetadata` function were removed as the `readGgufMetadata` is now invoked directly. The code has also been refactored to be more readable, with clearer variable names and more descriptive comments.
This commit is contained in:
parent
b0eec07a01
commit
0fc3dc6841
@ -104,12 +104,6 @@ interface DeviceList {
|
|||||||
free: number
|
free: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GgufMetadata {
|
|
||||||
version: number
|
|
||||||
tensor_count: number
|
|
||||||
metadata: Record<string, string>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the default app.log function to use Jan's logging system.
|
* Override the default app.log function to use Jan's logging system.
|
||||||
* @param args
|
* @param args
|
||||||
@ -1062,13 +1056,34 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check if files are valid GGUF files
|
// Validate GGUF files
|
||||||
// NOTE: modelPath and mmprojPath can be either relative to Jan's data folder (if they are downloaded)
|
|
||||||
// or absolute paths (if they are provided as local files)
|
|
||||||
const janDataFolderPath = await getJanDataFolderPath()
|
const janDataFolderPath = await getJanDataFolderPath()
|
||||||
let size_bytes = (
|
const fullModelPath = await joinPath([janDataFolderPath, modelPath])
|
||||||
await fs.fileStat(await joinPath([janDataFolderPath, modelPath]))
|
|
||||||
).size
|
try {
|
||||||
|
// Validate main model file
|
||||||
|
const modelMetadata = await readGgufMetadata(fullModelPath)
|
||||||
|
logger.info(
|
||||||
|
`Model GGUF validation successful: version ${modelMetadata.version}, tensors: ${modelMetadata.tensor_count}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate mmproj file if present
|
||||||
|
if (mmprojPath) {
|
||||||
|
const fullMmprojPath = await joinPath([janDataFolderPath, mmprojPath])
|
||||||
|
const mmprojMetadata = await readGgufMetadata(fullMmprojPath)
|
||||||
|
logger.info(
|
||||||
|
`Mmproj GGUF validation successful: version ${mmprojMetadata.version}, tensors: ${mmprojMetadata.tensor_count}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('GGUF validation failed:', error)
|
||||||
|
throw new Error(
|
||||||
|
`Invalid GGUF file(s): ${error.message || 'File format validation failed'}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate file sizes
|
||||||
|
let size_bytes = (await fs.fileStat(fullModelPath)).size
|
||||||
if (mmprojPath) {
|
if (mmprojPath) {
|
||||||
size_bytes += (
|
size_bytes += (
|
||||||
await fs.fileStat(await joinPath([janDataFolderPath, mmprojPath]))
|
await fs.fileStat(await joinPath([janDataFolderPath, mmprojPath]))
|
||||||
@ -1204,7 +1219,7 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
// disable llama-server webui
|
// disable llama-server webui
|
||||||
args.push('--no-webui')
|
args.push('--no-webui')
|
||||||
const api_key = await this.generateApiKey(modelId, String(port))
|
const api_key = await this.generateApiKey(modelId, String(port))
|
||||||
envs["LLAMA_API_KEY"] = api_key
|
envs['LLAMA_API_KEY'] = api_key
|
||||||
|
|
||||||
// model option is required
|
// model option is required
|
||||||
// NOTE: model_path and mmproj_path can be either relative to Jan's data folder or absolute path
|
// NOTE: model_path and mmproj_path can be either relative to Jan's data folder or absolute path
|
||||||
@ -1293,12 +1308,15 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: add LIBRARY_PATH
|
// TODO: add LIBRARY_PATH
|
||||||
const sInfo = await invoke<SessionInfo>('plugin:llamacpp|load_llama_model', {
|
const sInfo = await invoke<SessionInfo>(
|
||||||
backendPath,
|
'plugin:llamacpp|load_llama_model',
|
||||||
libraryPath,
|
{
|
||||||
args,
|
backendPath,
|
||||||
envs,
|
libraryPath,
|
||||||
})
|
args,
|
||||||
|
envs,
|
||||||
|
}
|
||||||
|
)
|
||||||
return sInfo
|
return sInfo
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in load command:\n', error)
|
logger.error('Error in load command:\n', error)
|
||||||
@ -1389,7 +1407,10 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
connectTimeout: 600000, // 10 minutes
|
connectTimeout: 600000, // 10 minutes
|
||||||
signal: AbortSignal.any([AbortSignal.timeout(600000), abortController?.signal]),
|
signal: AbortSignal.any([
|
||||||
|
AbortSignal.timeout(600000),
|
||||||
|
abortController?.signal,
|
||||||
|
]),
|
||||||
})
|
})
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => null)
|
const errorData = await response.json().catch(() => null)
|
||||||
@ -1670,18 +1691,4 @@ export default class llamacpp_extension extends AIEngine {
|
|||||||
'tokenizer.chat_template'
|
'tokenizer.chat_template'
|
||||||
]?.includes('tools')
|
]?.includes('tools')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadMetadata(path: string): Promise<GgufMetadata> {
|
|
||||||
try {
|
|
||||||
const data = await invoke<GgufMetadata>(
|
|
||||||
'plugin:llamacpp|read_gguf_metadata',
|
|
||||||
{
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
} catch (err) {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user