char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
92d1bb0c9
char/aliceai-t5-moe
1/** 2* Detects whether a model's chat template supports thinking/reasoning control. 3* 4* The server "/props" endpoint does NOT expose a supports_thinking flag. 5* It is computed internally by common_chat_templates_support_enable_thinking 6* in common/chat.cpp. A proper server flag would make this unnecessary. 7* 8* Detection order (most reliable first): 9* 1. Thinking-control Jinja2 variables === pass-through via chat_template_kwargs 10* 2. Thinking-control Jinja2 conditionals === template-native on/off logic 11* 3. Paired thinking-content tag pairs === models that output special tags 12*/ 13 14const THINKING_KWARG_VARS = [ 'enable_thinking' , 'reasoning_effort' , 'thinking_budget' ]; 15/** 16* Paired thinking-content tag patterns. 17* 18* Inspected: llama-cpp-deepseek-r1/v3, nim-nemotron-{3,4}-nano, qwen-qwq-32b, 19* qwen-3-32b, google-gemma-4-31b-it, kimikimi-k2-thinking, apertus-8b-instruct, 20* mistralai-Mistral-Small-3.2-24B, ByteDance-Seed-OSS. 21* 22* The self-closing entry is Kimi-K2, Gemma4 fixed-length pair, 23* where both tags always appear adjacent with no content between. 24*/ 25const THINKING_TAG_PATTERNS :Array <[ string , string | null ]> = [ 26[ '<think>' , '</think>' ], 27[ '<|channel>thought' , '<|channel|>' ], 28[ '<|think|>' , '</|think|>' ], 29[ '<seed:think|>' , '</seed:think|>' ], 30[ '<think></think>' , null ] 31]; 32const JINJA_THINKING_CONDITIONALS :RegExp [] = [ 33// Matches: {% if enable thinking %}, {% if enable_thinking %}, {% if (enable_thinking is defined) %} 34// Handles: underscore-separated (enable_thinking), space-separated (enable thinking), 35// and optional parens/brackets before enable (if (enable_thinking ) 36/ \{%-?\s*if\s+\(?\s*\w*enable[\s_]+\w*(thinking|think|reasoning) / i , 37/ \{%-?\s*if\s+\w*(thinking|reasoning)\s*(is not|==|!=) / i , 38/ \{%-?\s*if\s+not\s+\w*enable / i , 39/ \{%-?\s*if\s+ns\.enable_thinking / i 40]; 41 42/** Guards against false positives: 43* - Generic thought keyword (tool descriptions say "chain of thought") 44* - Qwen vertical-bar token (used for ALL tool calls, not thinking) 45*/ 46export function detectThinkingSupport ( t :string ) :boolean { 47if ( ! t ) return false ; 48 49for ( const kwarg of THINKING_KWARG_VARS ) { 50const regex = new RegExp ( 51`(\\{\\{[^{}]*\\b ${ kwarg } \\b[^{}]*\\}\\}|\\{%[^{}]*\\b ${ kwarg } \\b[^{}]*%\\})` , 52'i' 53); 54 55if ( regex . test ( t )) return true ; 56} 57for ( const p of JINJA_THINKING_CONDITIONALS ) { 58if ( p . test ( t )) return true ; 59} 60for ( const [ s , e ] of THINKING_TAG_PATTERNS ) { 61if ( t . includes ( s ) && ( ! e || t . includes ( e ))) return true ; 62} 63 64return false ; 65} 66 67export function detectThinkingSupportWithReason ( t :string ) :{ supported :boolean ; reason :string } { 68if ( ! t ) return { reason :'No chat template available' , supported :false }; 69 70for ( const kwarg of THINKING_KWARG_VARS ) { 71const regex = new RegExp ( 72`(\\{\\{[^{}]*\\b ${ kwarg } \\b[^{}]*\\}\\}|\\{%[^{}]*\\b ${ kwarg } \\b[^{}]*%\\})` , 73'i' 74); 75 76if ( regex . test ( t )) { 77return { reason :'Found: ' + kwarg , supported :true }; 78} 79} 80for ( const p of JINJA_THINKING_CONDITIONALS ) { 81if ( p . test ( t )) return { reason :'Found: thinking conditional' , supported :true }; 82} 83for ( const [ s , e ] of THINKING_TAG_PATTERNS ) { 84if ( t . includes ( s ) && ( ! e || t . includes ( e ))) { 85return { reason :'Found: ' + s + ( e ?' .. ' + e :' (self)' ), supported :true }; 86} 87} 88 89return { reason :'No thinking patterns found' , supported :false }; 90}