3
3
* The source data is taken from llama.cpp
4
4
*/
5
5
6
+ import type { GGUFParseOutput } from "../../gguf/src/gguf" ;
6
7
import { gguf } from "../../gguf/src/gguf" ;
7
- import { appendFileSync , writeFileSync } from "node:fs" ;
8
+ import { appendFileSync , writeFileSync , existsSync } from "node:fs" ;
9
+ import path from "node:path" ;
8
10
11
+ const DEBUG = process . env . DEBUG ;
9
12
const RE_SPECIAL_TOKEN = / < [ | _ A - Z a - z 0 - 9 ] + > | \[ [ A - Z ] + \] | < \uFF5C [ \u2581 A - Z a - z ] + \uFF5C > / g;
10
13
const MAX_NUMBER_OF_TAGS_PER_MODEL = 5 ;
11
14
const N_WORKERS = 16 ;
15
+ const OUTPUT_FILE = path . join ( __dirname , "../src/chat-template-automap.ts" ) ;
16
+ const BLACKLISTED_MODELS = ( model : string , tag : string ) => {
17
+ // some models are know to give ServiceUnavailable
18
+ return model === "library/deepseek-r1" && tag === "7b" ;
19
+ } ;
12
20
13
21
interface OutputItem {
14
22
model : string ;
15
23
gguf : string ;
16
24
ollama : {
17
25
template : string ;
18
26
tokens : string [ ] ;
27
+ // eslint-disable-next-line
19
28
params ?: any ;
20
29
} ;
21
30
}
22
31
32
+ interface OllamaManifestLayer {
33
+ digest : string ;
34
+ mediaType : string ;
35
+ size : number ;
36
+ }
37
+
38
+ interface OllamaManifest {
39
+ layers : OllamaManifestLayer [ ] ;
40
+ }
41
+
23
42
const getSpecialTokens = ( tmpl : string ) : string [ ] => {
24
43
const matched = tmpl . match ( RE_SPECIAL_TOKEN ) ;
25
44
const tokens = Array . from ( matched || [ ] ) ;
26
45
return Array . from ( new Set ( tokens ) ) ; // deduplicate
27
46
} ;
28
47
29
48
( async ( ) => {
30
- writeFileSync ( "ollama_tmp.jsonl" , "" ) ; // clear the file
49
+ if ( DEBUG ) writeFileSync ( "ollama_tmp.jsonl" , "" ) ; // clear the file
31
50
32
51
const models : string [ ] = [ ] ;
33
52
const output : OutputItem [ ] = [ ] ;
@@ -73,11 +92,21 @@ const getSpecialTokens = (tmpl: string): string[] => {
73
92
) ;
74
93
console . log ( { modelsWithTag } ) ;
75
94
95
+ //////// merging with old file if necessary ////////
96
+
97
+ const seenGGUFTemplate = new Set < string > ( ) ;
98
+ if ( existsSync ( OUTPUT_FILE ) ) {
99
+ const oldOutput = await import ( OUTPUT_FILE ) ;
100
+ oldOutput . OLLAMA_CHAT_TEMPLATE_MAPPING . forEach ( ( item : OutputItem ) => {
101
+ seenGGUFTemplate . add ( item . gguf ) ;
102
+ output . push ( item ) ;
103
+ } ) ;
104
+ }
105
+
76
106
//////// Get template ////////
77
107
78
108
nDoing = 0 ;
79
109
nAll = modelsWithTag . length ;
80
- let seenTemplate = new Set ( ) ;
81
110
const workerGetTemplate = async ( ) => {
82
111
while ( true ) {
83
112
const modelWithTag = modelsWithTag . shift ( ) ;
@@ -86,22 +115,39 @@ const getSpecialTokens = (tmpl: string): string[] => {
86
115
nDoing ++ ;
87
116
const [ model , tag ] = modelWithTag . split ( ":" ) ;
88
117
console . log ( `Fetch template ${ nDoing } / ${ nAll } | model=${ model } tag=${ tag } ` ) ;
89
- const getBlobUrl = ( digest ) => `https://registry.ollama.com/v2/${ model } /blobs/${ digest } ` ;
90
- const manifest = await ( await fetch ( `https://registry.ollama.com/v2/${ model } /manifests/${ tag } ` ) ) . json ( ) ;
118
+ const getBlobUrl = ( digest : string ) => `https://registry.ollama.com/v2/${ model } /blobs/${ digest } ` ;
119
+ const manifest : OllamaManifest = await (
120
+ await fetch ( `https://registry.ollama.com/v2/${ model } /manifests/${ tag } ` )
121
+ ) . json ( ) ;
91
122
if ( ! manifest . layers ) {
92
123
console . log ( " --> [X] No layers" ) ;
93
124
continue ;
94
125
}
95
- const modelUrl = getBlobUrl ( manifest . layers . find ( ( l ) => l . mediaType . match ( / \. m o d e l / ) ) . digest ) ;
96
- const ggufData = await gguf ( modelUrl ) ;
126
+ const layerModelUrl = manifest . layers . find ( ( l ) => l . mediaType . match ( / \. m o d e l / ) ) ;
127
+ if ( ! layerModelUrl ) {
128
+ console . log ( " --> [X] No model is found" ) ;
129
+ continue ;
130
+ }
131
+ const modelUrl = getBlobUrl ( layerModelUrl . digest ) ;
132
+ let ggufData : GGUFParseOutput ;
133
+ if ( BLACKLISTED_MODELS ( model , tag ) ) {
134
+ console . log ( " --> [X] Blacklisted model, skip" ) ;
135
+ continue ;
136
+ }
137
+ try {
138
+ ggufData = await gguf ( modelUrl ) ;
139
+ } catch ( e ) {
140
+ console . log ( " --> [X] FATAL: GGUF error" , { model, tag, modelUrl } ) ;
141
+ throw e ; // rethrow
142
+ }
97
143
const { metadata } = ggufData ;
98
144
const ggufTmpl = metadata [ "tokenizer.chat_template" ] ;
99
145
if ( ggufTmpl ) {
100
- if ( seenTemplate . has ( ggufTmpl ) ) {
146
+ if ( seenGGUFTemplate . has ( ggufTmpl ) ) {
101
147
console . log ( " --> Already seen this GGUF template, skip..." ) ;
102
148
continue ;
103
149
}
104
- seenTemplate . add ( ggufTmpl ) ;
150
+ seenGGUFTemplate . add ( ggufTmpl ) ;
105
151
console . log ( " --> GGUF chat template OK" ) ;
106
152
const tmplBlob = manifest . layers . find ( ( l ) => l . mediaType . match ( / \. t e m p l a t e / ) ) ;
107
153
if ( ! tmplBlob ) continue ;
@@ -128,7 +174,7 @@ const getSpecialTokens = (tmpl: string): string[] => {
128
174
record . ollama . params = await ( await fetch ( ollamaParamsUrl ) ) . json ( ) ;
129
175
}
130
176
output . push ( record ) ;
131
- appendFileSync ( "ollama_tmp.jsonl" , JSON . stringify ( record ) + "\n" ) ;
177
+ if ( DEBUG ) appendFileSync ( "ollama_tmp.jsonl" , JSON . stringify ( record ) + "\n" ) ;
132
178
} else {
133
179
console . log ( " --> [X] No GGUF template" ) ;
134
180
continue ;
@@ -148,7 +194,7 @@ const getSpecialTokens = (tmpl: string): string[] => {
148
194
output . sort ( ( a , b ) => a . model . localeCompare ( b . model ) ) ;
149
195
150
196
writeFileSync (
151
- "./src/chat-template-automap.ts" ,
197
+ OUTPUT_FILE ,
152
198
`
153
199
// This file is auto generated, please do not modify manually
154
200
// To update it, run "pnpm run build:automap"
0 commit comments