Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit e4c36dd

Browse files
committed
feat: pull model yaml from hf
1 parent 7e46c2a commit e4c36dd

File tree

3 files changed

+112
-3
lines changed

3 files changed

+112
-3
lines changed

cortex-js/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
},
2727
"dependencies": {
2828
"@huggingface/gguf": "^0.1.5",
29+
"@huggingface/hub": "^0.15.1",
2930
"@nestjs/axios": "^3.0.2",
3031
"@nestjs/common": "^10.0.0",
3132
"@nestjs/config": "^3.2.2",
@@ -47,7 +48,8 @@
4748
"sqlite": "^5.1.1",
4849
"sqlite3": "^5.1.7",
4950
"typeorm": "^0.3.20",
50-
"ulid": "^2.3.0"
51+
"ulid": "^2.3.0",
52+
"yaml": "^2.4.2"
5153
},
5254
"devDependencies": {
5355
"@nestjs/cli": "^10.0.0",
Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,97 @@
11
import { CommandRunner, SubCommand } from 'nest-commander';
22
import { exit } from 'node:process';
33
import { ModelsCliUsecases } from '../usecases/models.cli.usecases';
4+
import { RepoDesignation, listFiles } from '@huggingface/hub';
5+
import YAML from 'yaml';
46

57
@SubCommand({
68
name: 'pull',
79
aliases: ['download'],
810
description: 'Download a model. Working with HuggingFace model id.',
911
})
1012
export class ModelPullCommand extends CommandRunner {
13+
private metadataFileName = 'metadata.yaml';
14+
1115
constructor(private readonly modelsCliUsecases: ModelsCliUsecases) {
1216
super();
1317
}
1418

1519
async run(input: string[]) {
1620
if (input.length < 1) {
17-
console.error('Model ID is required');
21+
console.error('Model Id is required');
1822
exit(1);
1923
}
2024

21-
await this.modelsCliUsecases.pullModel(input[0]);
25+
// Check if metadata.yaml file exist
26+
const metadata = await this.getJanMetadata(input[0]);
27+
28+
if (!metadata) {
29+
await this.modelsCliUsecases.pullModel(input[0]);
30+
} else {
31+
await this.handleJanHqModel(input[0], metadata);
32+
}
33+
2234
console.log('\nDownload complete!');
2335
exit(0);
2436
}
37+
38+
private async getJanMetadata(input: string): Promise<any> {
39+
// try to append with janhq/ if it's not already
40+
const sanitizedInput = input.trim().startsWith('janhq/')
41+
? input
42+
: `janhq/${input}`;
43+
44+
const repo: RepoDesignation = { type: 'model', name: sanitizedInput };
45+
let isMetadataFileExist = false;
46+
for await (const fileInfo of listFiles({ repo })) {
47+
if (fileInfo.path === this.metadataFileName) {
48+
isMetadataFileExist = true;
49+
break;
50+
}
51+
}
52+
53+
if (!isMetadataFileExist) {
54+
return undefined;
55+
}
56+
57+
const path = `https://huggingface.co/${sanitizedInput}/raw/main/${this.metadataFileName}`;
58+
const res = await fetch(path);
59+
const metadataJson = await res.text();
60+
const parsedMetadata = YAML.parse(metadataJson);
61+
return parsedMetadata;
62+
}
63+
64+
private async handleJanHqModel(repoName: string, metadata: any) {
65+
// TODO: asking user to choose here
66+
const sanitizedRepoName = `janhq/${repoName.trim()}`;
67+
const branch = 'default';
68+
const engine = 'llamacpp'; // TODO: currently, we only support llamacpp
69+
70+
const revision = metadata.tags?.[branch]?.[engine];
71+
if (!revision) {
72+
console.error("Can't find model revision.");
73+
exit(1);
74+
}
75+
76+
const repo: RepoDesignation = { type: 'model', name: sanitizedRepoName };
77+
let ggufUrl: string | undefined = undefined;
78+
for await (const fileInfo of listFiles({
79+
repo: repo,
80+
revision: revision,
81+
})) {
82+
if (fileInfo.path.endsWith('.gguf')) {
83+
ggufUrl = `https://huggingface.co/${sanitizedRepoName}/resolve/${revision}/${fileInfo.path}`;
84+
break;
85+
}
86+
}
87+
88+
if (!ggufUrl) {
89+
console.error("Can't find model file.");
90+
exit(1);
91+
}
92+
await this.modelsCliUsecases.pullModelWithExactUrl(
93+
`${sanitizedRepoName}/${revision}`,
94+
ggufUrl,
95+
);
96+
}
2597
}

cortex-js/src/infrastructure/commanders/usecases/models.cli.usecases.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,41 @@ export class ModelsCliUsecases {
9898
return this.modelsUsecases.remove(modelId);
9999
}
100100

101+
async pullModelWithExactUrl(modelId: string, url: string) {
102+
const model: CreateModelDto = {
103+
sources: [
104+
{
105+
url: url,
106+
},
107+
],
108+
id: modelId,
109+
name: modelId,
110+
version: '', // TODO: get version from the file
111+
format: ModelFormat.GGUF,
112+
description: '',
113+
settings: {
114+
prompt_template: '', // TODO: get prompt template from the file
115+
},
116+
parameters: {
117+
stop: [],
118+
},
119+
metadata: {
120+
author: 'janhq', // TODO: get author from the file
121+
size: 0, // TODO: get size from the file
122+
tags: [],
123+
},
124+
engine: 'cortex',
125+
};
126+
if (!(await this.modelsUsecases.findOne(modelId)))
127+
await this.modelsUsecases.create(model);
128+
const bar = new SingleBar({}, Presets.shades_classic);
129+
bar.start(100, 0);
130+
const callback = (progress: number) => {
131+
bar.update(progress);
132+
};
133+
await this.modelsUsecases.downloadModel(modelId, callback);
134+
}
135+
101136
async pullModel(modelId: string) {
102137
if (modelId.includes('/')) {
103138
await this.pullHuggingFaceModel(modelId);

0 commit comments

Comments
 (0)