char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Linting & Formatting scripts (#26819)92d1bb0c9

char/aliceai-t5-moe
3.3 KiB90 linesraw
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 {
47	if (!t) return false;
48
49	for (const kwarg of THINKING_KWARG_VARS) {
50		const regex = new RegExp(
51			`(\\{\\{[^{}]*\\b${kwarg}\\b[^{}]*\\}\\}|\\{%[^{}]*\\b${kwarg}\\b[^{}]*%\\})`,
52			'i'
53		);
54
55		if (regex.test(t)) return true;
56	}
57	for (const p of JINJA_THINKING_CONDITIONALS) {
58		if (p.test(t)) return true;
59	}
60	for (const [s, e] of THINKING_TAG_PATTERNS) {
61		if (t.includes(s) && (!e || t.includes(e))) return true;
62	}
63
64	return false;
65}
66
67export function detectThinkingSupportWithReason(t: string): { supported: boolean; reason: string } {
68	if (!t) return { reason: 'No chat template available', supported: false };
69
70	for (const kwarg of THINKING_KWARG_VARS) {
71		const regex = new RegExp(
72			`(\\{\\{[^{}]*\\b${kwarg}\\b[^{}]*\\}\\}|\\{%[^{}]*\\b${kwarg}\\b[^{}]*%\\})`,
73			'i'
74		);
75
76		if (regex.test(t)) {
77			return { reason: 'Found: ' + kwarg, supported: true };
78		}
79	}
80	for (const p of JINJA_THINKING_CONDITIONALS) {
81		if (p.test(t)) return { reason: 'Found: thinking conditional', supported: true };
82	}
83	for (const [s, e] of THINKING_TAG_PATTERNS) {
84		if (t.includes(s) && (!e || t.includes(e))) {
85			return { reason: 'Found: ' + s + (e ? ' .. ' + e : ' (self)'), supported: true };
86		}
87	}
88
89	return { reason: 'No thinking patterns found', supported: false };
90}