33 * The source data is taken from llama.cpp
44 */
55
6+ import type { GGUFParseOutput } from "../../gguf/src/gguf" ;
67import { 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" ;
810
11+ const DEBUG = process . env . DEBUG ;
912const RE_SPECIAL_TOKEN = / < [ | _ A - Z a - z 0 - 9 ] + > | \[ [ A - Z ] + \] | < \uFF5C [ \u2581 A - Z a - z ] + \uFF5C > / g;
1013const MAX_NUMBER_OF_TAGS_PER_MODEL = 5 ;
1114const 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+ } ;
1220
1321interface OutputItem {
1422 model : string ;
1523 gguf : string ;
1624 ollama : {
1725 template : string ;
1826 tokens : string [ ] ;
27+ // eslint-disable-next-line
1928 params ?: any ;
2029 } ;
2130}
2231
32+ interface OllamaManifestLayer {
33+ digest : string ;
34+ mediaType : string ;
35+ size : number ;
36+ }
37+
38+ interface OllamaManifest {
39+ layers : OllamaManifestLayer [ ] ;
40+ }
41+
2342const getSpecialTokens = ( tmpl : string ) : string [ ] => {
2443 const matched = tmpl . match ( RE_SPECIAL_TOKEN ) ;
2544 const tokens = Array . from ( matched || [ ] ) ;
2645 return Array . from ( new Set ( tokens ) ) ; // deduplicate
2746} ;
2847
2948( async ( ) => {
30- writeFileSync ( "ollama_tmp.jsonl" , "" ) ; // clear the file
49+ if ( DEBUG ) writeFileSync ( "ollama_tmp.jsonl" , "" ) ; // clear the file
3150
3251 const models : string [ ] = [ ] ;
3352 const output : OutputItem [ ] = [ ] ;
@@ -73,11 +92,21 @@ const getSpecialTokens = (tmpl: string): string[] => {
7392 ) ;
7493 console . log ( { modelsWithTag } ) ;
7594
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+
76106 //////// Get template ////////
77107
78108 nDoing = 0 ;
79109 nAll = modelsWithTag . length ;
80- let seenTemplate = new Set ( ) ;
81110 const workerGetTemplate = async ( ) => {
82111 while ( true ) {
83112 const modelWithTag = modelsWithTag . shift ( ) ;
@@ -86,22 +115,39 @@ const getSpecialTokens = (tmpl: string): string[] => {
86115 nDoing ++ ;
87116 const [ model , tag ] = modelWithTag . split ( ":" ) ;
88117 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 ( ) ;
91122 if ( ! manifest . layers ) {
92123 console . log ( " --> [X] No layers" ) ;
93124 continue ;
94125 }
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+ }
97143 const { metadata } = ggufData ;
98144 const ggufTmpl = metadata [ "tokenizer.chat_template" ] ;
99145 if ( ggufTmpl ) {
100- if ( seenTemplate . has ( ggufTmpl ) ) {
146+ if ( seenGGUFTemplate . has ( ggufTmpl ) ) {
101147 console . log ( " --> Already seen this GGUF template, skip..." ) ;
102148 continue ;
103149 }
104- seenTemplate . add ( ggufTmpl ) ;
150+ seenGGUFTemplate . add ( ggufTmpl ) ;
105151 console . log ( " --> GGUF chat template OK" ) ;
106152 const tmplBlob = manifest . layers . find ( ( l ) => l . mediaType . match ( / \. t e m p l a t e / ) ) ;
107153 if ( ! tmplBlob ) continue ;
@@ -128,7 +174,7 @@ const getSpecialTokens = (tmpl: string): string[] => {
128174 record . ollama . params = await ( await fetch ( ollamaParamsUrl ) ) . json ( ) ;
129175 }
130176 output . push ( record ) ;
131- appendFileSync ( "ollama_tmp.jsonl" , JSON . stringify ( record ) + "\n" ) ;
177+ if ( DEBUG ) appendFileSync ( "ollama_tmp.jsonl" , JSON . stringify ( record ) + "\n" ) ;
132178 } else {
133179 console . log ( " --> [X] No GGUF template" ) ;
134180 continue ;
@@ -148,7 +194,7 @@ const getSpecialTokens = (tmpl: string): string[] => {
148194 output . sort ( ( a , b ) => a . model . localeCompare ( b . model ) ) ;
149195
150196 writeFileSync (
151- "./src/chat-template-automap.ts" ,
197+ OUTPUT_FILE ,
152198 `
153199// This file is auto generated, please do not modify manually
154200// To update it, run "pnpm run build:automap"
0 commit comments