Support .jinja/.jinja2 instruction template files in the UI (closes #7517)

This commit is contained in:
oobabooga
2026-04-26 18:53:02 -07:00
parent 4b575a2fae
commit 54c4feeb4f
5 changed files with 59 additions and 49 deletions

View File

@@ -13,7 +13,7 @@ from .errors import InvalidRequestError
from .typing import ToolDefinition
from .utils import debug_msg
from modules.tool_parsing import get_tool_call_id, parse_tool_call, detect_tool_call_format
from modules import shared
from modules import shared, utils
from modules.reasoning import extract_reasoning
from modules.chat import (
generate_chat_prompt,
@@ -31,10 +31,9 @@ from modules.text_generation import decode, encode, generate_reply
def load_chat_template_file(filepath):
"""Load a chat template from a file path (.jinja, .jinja2, or .yaml/.yml)."""
filepath = Path(filepath)
ext = filepath.suffix.lower()
text = filepath.read_text(encoding='utf-8')
if ext in ['.yaml', '.yml']:
data = yaml.safe_load(text)
if filepath.suffix.lower() in utils.YAML_EXTENSIONS:
data = yaml.safe_load(text) or {}
return data.get('instruction_template', '')
return text

View File

@@ -2767,9 +2767,17 @@ def handle_save_template_click(instruction_template_str):
def handle_delete_template_click(template):
import gradio as gr
root = str(shared.user_data_dir / 'instruction-templates') + '/'
from modules.utils import TEMPLATE_EXTENSIONS
template_dir = shared.user_data_dir / 'instruction-templates'
filename = f"{template}.yaml"
for ext in TEMPLATE_EXTENSIONS:
if (template_dir / f"{template}{ext}").exists():
filename = f"{template}{ext}"
break
root = str(template_dir) + '/'
return [
f"{template}.yaml",
filename,
root,
root,
gr.update(visible=True)

View File

@@ -6,7 +6,7 @@ from pathlib import Path
import yaml
from modules import loaders, metadata_gguf, shared
from modules import loaders, metadata_gguf, shared, utils
from modules.logging_colors import logger
from modules.utils import resolve_model_path
@@ -397,28 +397,44 @@ def update_gpu_layers_and_vram(loader, model, gpu_layers, ctx_size, cache_type):
return f"<div id=\"vram-info\"'>Estimated VRAM to load the model: <span class=\"value\">{vram_usage:.0f} MiB</span></div>"
def load_template_by_name(name):
"""Find and load a single instruction template by name. Returns '' if not found."""
template_dir = shared.user_data_dir / 'instruction-templates'
for ext in utils.TEMPLATE_EXTENSIONS:
path = template_dir / f'{name}{ext}'
if path.is_file():
break
else:
return ''
file_contents = path.read_text(encoding='utf-8')
if path.suffix in utils.JINJA_EXTENSIONS:
return file_contents
try:
data = yaml.safe_load(file_contents) or {}
except yaml.YAMLError:
logger.warning(f"Failed to parse '{path.name}' as YAML. Treating it as a raw Jinja template. Consider renaming it to '{name}.jinja'.")
return file_contents
if 'instruction_template' in data:
return data['instruction_template']
elif 'turn_template' in data:
return _jinja_template_from_old_format(data)
else:
return ''
def load_instruction_template(template):
if template == 'None':
return ''
for name in (template, 'Alpaca'):
path = shared.user_data_dir / 'instruction-templates' / f'{name}.yaml'
try:
with open(path, 'r', encoding='utf-8') as f:
file_contents = f.read()
except FileNotFoundError:
if name == template:
logger.warning(f"Instruction template '{template}' not found, falling back to Alpaca")
continue
result = load_template_by_name(template)
if result:
return result
break
else:
return ''
data = yaml.safe_load(file_contents)
if 'instruction_template' in data:
return data['instruction_template']
else:
return _jinja_template_from_old_format(data)
logger.warning(f"Instruction template '{template}' not found, falling back to Alpaca")
return load_template_by_name('Alpaca')
def _jinja_template_from_old_format(params, verbose=False):

View File

@@ -14,7 +14,6 @@ import traceback
from datetime import datetime
from pathlib import Path
import yaml
import gradio as gr
from modules import shared, ui, utils
@@ -217,26 +216,8 @@ def clean_path(base_path: str, path: str):
def get_instruction_templates():
path = shared.user_data_dir / 'instruction-templates'
names = set()
for ext in ['yaml', 'yml', 'jinja', 'jinja2']:
for f in path.glob(f'*.{ext}'):
names.add(f.stem)
return ['None', 'Chat Template'] + sorted(names, key=utils.natural_keys)
def load_template(name):
"""Load a Jinja2 template string from {user_data_dir}/instruction-templates/."""
path = shared.user_data_dir / 'instruction-templates'
for ext in ['jinja', 'jinja2', 'yaml', 'yml']:
filepath = path / f'{name}.{ext}'
if filepath.exists():
if ext in ['jinja', 'jinja2']:
return filepath.read_text(encoding='utf-8')
else:
data = yaml.safe_load(filepath.read_text(encoding='utf-8'))
return data.get('instruction_template', '')
return ''
templates = utils.get_available_instruction_templates() # ['None', ...]
return [templates[0], 'Chat Template'] + templates[1:]
def backup_adapter(input_folder):
@@ -485,7 +466,8 @@ def do_train(lora_name: str, always_override: bool, all_linear: bool, q_proj_en:
return
else:
# Load custom instruction template and set on tokenizer
template_str = load_template(format)
from modules.models_settings import load_template_by_name
template_str = load_template_by_name(format)
if not template_str:
yield f"Error: could not load instruction template '{format}'."
return

View File

@@ -247,11 +247,16 @@ def get_available_users():
return sorted(set((k.stem for k in paths)), key=natural_keys)
YAML_EXTENSIONS = ('.yaml', '.yml')
JINJA_EXTENSIONS = ('.jinja', '.jinja2')
TEMPLATE_EXTENSIONS = JINJA_EXTENSIONS + YAML_EXTENSIONS
def get_available_instruction_templates():
path = str(shared.user_data_dir / "instruction-templates")
path = shared.user_data_dir / "instruction-templates"
paths = []
if os.path.exists(path):
paths = (x for x in Path(path).iterdir() if x.suffix in ('.json', '.yaml', '.yml'))
if path.exists():
paths = (x for x in path.iterdir() if x.suffix in TEMPLATE_EXTENSIONS)
return ['None'] + sorted(set((k.stem for k in paths)), key=natural_keys)