Skip to content

Creating Custom Templates

Overview

The Chat Template System is designed to be highly extensible, allowing you to create custom templates that fit your specific needs. This guide covers how to create, configure, and register custom templates.

Template Components

Core Template Fields

from chat_bricks import Template, register_template, Chat

register_template(
    Template(
        name="my-custom-template",           # Unique identifier
        system_template="System: {system_message}",  # System message format
        system_message="You are a helpful assistant.", # Default system message
        user_template="User: {content}",           # User message format
        assistant_template="Assistant: {content}</s>",      # Assistant message format
        observations_template="Tool: {observation}",  # Tool response format
        stop_words=["</s>"]                 # Stop generation tokens
    )
)

messages = [
    {"role": "user", "content": "What is the capital of France?"},
    {"role": "assistant", "content": "The capital of France is Paris."},
    {"role": "user", "content": "Tell me more about Paris."}
]

tools = [
    {
        "function": {
            "name": "get_weather",
            "description": "Get weather information for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name"}
                },
                "required": ["city"]
            }
        }
    }
]

chat = Chat(template="my-custom-template", messages=messages, tools=tools)
prompt = chat.prompt()
print(prompt)

Advanced Template Fields

template = Template(
    # ... core fields ...

    # Tool support — the {tools} slot in system_template is filled by tools_template
    system_template="System: {system_message}{tools}",
    tools_template="\n\nTools: {tools}",
    # Optional: wrap each individual tool entry before joining
    # single_tool_template="\n- {tool}",

    # Skill support — same two-pass pattern, but skills always live in the system message
    skills_template="\n\nSkills:\n{skills}",
    single_skill_template="- {name}: {description}",

    # Vision support
    vision_start="<vision>",
    vision_end="</vision>",
    image_token="<image>",
    video_token="<video>",

    # Custom chat template (Jinja)
    chat_template="{% for message in messages %}{{ message.content }}{% endfor %}"
)

Template Creation Examples

1. Simple Chat Template

register_template(
    Template(
        name="simple-chat",
        system_template="You are a helpful assistant.\n",
        system_message="You are a helpful assistant.",
        user_template="User: {content}\n",
        assistant_template="Assistant: {content}\n",
        stop_words=["\n"]
    )
)
chat = Chat(template="simple-chat", messages=messages, tools=tools)
print(chat.prompt())

2. XML-Style Template

register_template(
    Template(
        name="xml-style",
        system_template="<system>{system_message}</system>\n",
        system_message="You are an AI assistant.",
        user_template="<user>{content}</user>\n",
        assistant_template="<assistant>{content}</assistant>\n",
        stop_words=["</assistant>"]
    )
)
chat = Chat(template="xml-style", messages=messages, tools=tools)
print(chat.prompt())

3. Markdown-Style Template

register_template(
    Template(
        name="markdown-style",
        system_template="# System\n{system_message}\n\n",
        system_message="You are a helpful AI assistant.",
        user_template="## User\n{content}\n\n",
        assistant_template="## Assistant\n{content}\n\n",
        stop_words=["\n\n"]
    )
)
chat = Chat(template="markdown-style", messages=messages, tools=tools)
print(chat.prompt())

4. Tool-Enabled Template

register_template(
    Template(
        name="tool-enabled",
        # {tools} slot in system_template is filled by tools_template when tools are passed;
        # when no tools are passed it expands to "".
        system_template="System: {system_message}{tools}\n",
        tools_template="\n\nAvailable Tools:\n{tools}",
        system_message="You are an AI assistant with access to tools.",
        user_template="User: {content}\n",
        user_template_with_tools="User: {content}\n\nTools: {tools}\n",
        assistant_template="Assistant: {content}\n",
        observations_template="Tool Response: {observation}\n",
        stop_words=["\n"]
    )
)
messages = [
    {"role": "user", "content": "Find me the weather of Paris"},
    {"role": "assistant", "content": "Tool: get_weather Arguments: {'city': 'Paris'}"},
    {"role": "Tool", "content": "24 degrees, raining."}
]
tools = [
    {
        "function": {
            "name": "get_weather",
            "description": "Get weather information for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name"}
                },
                "required": ["city"]
            }
        }
    }
]
chat = Chat(template="tool-enabled", messages=messages, tools=tools)
print(chat.prompt())

5. Skill-Enabled Template

Skills are (name, description) pairs that get listed in the system prompt so the model knows which named skills it can load (typically via a load_skill tool). They use the same two-pass pattern as tools: each entry is wrapped by single_skill_template, joined, then wrapped by skills_template and inserted into the {skills} slot of system_template.

register_template(
    Template(
        name="skill-enabled",
        system_template="<|im_start|>system\n{system_message}{tools}{skills}<|im_end|>\n",
        tools_template="\n\n# Tools\n<tools>\n{tools}\n</tools>",
        skills_template="\n\n# Skills\n<skills>\n{skills}\n</skills>",
        single_skill_template="- {name}: {description}",  # default
        system_message="You are an agent.",
        user_template="<|im_start|>user\n{content}<|im_end|>\n",
        assistant_template="<|im_start|>assistant\n{content}<|im_end|>\n",
        observations_template="<|im_start|>tool\n{observation}<|im_end|>\n",
        stop_words=["<|im_end|>"],
    )
)

skills = [
    {"name": "add-numbers", "description": "Adds two integers."},
    {"name": "word-count",  "description": "Counts words in text."},
]
chat = Chat(template="skill-enabled", messages=messages, skills=skills)
print(chat.prompt())

Skill entries may be dicts (as above) or any object that exposes .name and .description attributes (e.g. dataclasses, pydantic models). If a template has no skills_template, passing skills=... is silently ignored — making the argument safe to thread through generic code.

6. Vision-Enabled Template

register_template(
    Template(
        name="vision-enabled",
        system_template="You are a vision-capable AI assistant.\n",
        system_message="You are a vision-capable AI assistant.",
        user_template="User: {content}\n",
        assistant_template="Assistant: {content}\n",
        vision_start="<vision>",
        vision_end="</vision>",
        image_token="<image>",
        video_token="<video>",
        stop_words=["\n"]
    )
)

messages =    [
    {
        "role": "system",
        "content": "You are a multi-modal assistant that can answer questions about images.",
    },
    {
        "role": "user",
        "content": [
            {
                "type": "image",
                "image": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen-VL/assets/demo.jpeg",
            },
            {"type": "text", "text": "Describe this image."},
        ],
    },
    {
        "role": "assistant",
        "content": [
            {
                "type": "text",
                "text": "The image is a cat.",
            },
        ],
    }
]
chat = Chat(template="vision-enabled", messages=messages)
print(chat.prompt())

Policy Configuration

System Policy

from chat_bricks import SystemPolicy

# Basic system policy
system_policy = SystemPolicy(
    use_system=True,                           # Always include system message
    use_system_without_system_message=True,    # Include system even without explicit message
    content_processor=None                     # No content processing
)

# System policy with content processor
def add_timestamp(system_message):
    from datetime import datetime
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"[{timestamp}] {system_message}"

system_policy = SystemPolicy(
    use_system=True,
    use_system_without_system_message=True,
    content_processor=add_timestamp
)

Tool Policy

from chat_bricks import ToolPolicy, JsonFormatter, JsonIndentedFormatter
from chat_bricks import ToolPlacement

# Basic tool policy
tool_policy = ToolPolicy(
    placement=ToolPlacement.SYSTEM,           # Place tools in system message
    formatter=JsonFormatter(indent=2)         # Pretty-printed JSON
)

# Advanced tool policy
tool_policy = ToolPolicy(
    placement=ToolPlacement.FIRST_USER,       # Place tools with first user message
    formatter=JsonIndentedFormatter(indent=4), # Indented JSON
    content_processor=None                    # No content processing
)

Global Policy

from chat_bricks import GlobalPolicy

global_policy = GlobalPolicy(
    prefix="<|begin_of_text|>"               # Add prefix to all prompts
)

Complete Template Example

from chat_bricks import Template, GlobalPolicy
from chat_bricks import SystemPolicy
from chat_bricks import ToolPolicy, JsonIndentedFormatter
from chat_bricks import ToolPlacement

# Create a comprehensive template
comprehensive_template = Template(
    name="comprehensive-example",

    # Basic templates — {tools} and {skills} slots are filled by the section
    # templates below; they expand to "" when no tools/skills are passed.
    system_template="<|im_start|>system\n{system_message}{tools}{skills}<|im_end|>\n",
    system_message="You are a comprehensive AI assistant with multiple capabilities.",

    # Tool support
    tools_template="\n\nAvailable Tools:\n{tools}",
    user_template="<|im_start|>user\n{content}<|im_end|>\n",
    user_template_with_tools="<|im_start|>user\n{content}\n\nTools: {tools}<|im_end|>\n",
    assistant_template="<|im_start|>assistant\n{content}<|im_end|>\n",
    observations_template="<|im_start|>tool\n{observation}<|im_end|>\n",

    # Skill support
    skills_template="\n\nSkills:\n{skills}",

    # Vision support
    vision_start="<|vision_start|>",
    vision_end="<|vision_end|>",
    image_token="<|image_pad|>",
    video_token="<|video_pad|>",

    # Stop words
    stop_words=["<|im_end|>"],

    # Policies
    global_policy=GlobalPolicy(prefix="<|begin_of_text|>"),
    system_policy=SystemPolicy(
        use_system=True,
        use_system_without_system_message=True
    ),
    tool_policy=ToolPolicy(
        placement=ToolPlacement.SYSTEM,
        formatter=JsonIndentedFormatter(indent=2)
    )
)

Template Registration

Registering a Template

from chat_bricks import register_template

# Register the template
register_template(comprehensive_template)

# Now you can use it
from chat_bricks import get_template
template = get_template("comprehensive-example")

Overriding Existing Templates

# Override an existing template
register_template(comprehensive_template, override=True)

Template Registry Management

from chat_bricks import TEMPLATES

# List all registered templates
print("Available templates:", list(TEMPLATES.keys()))

# Check if template exists
if "my-template" in TEMPLATES:
    print("Template exists")

# Remove a template
if "my-template" in TEMPLATES:
    del TEMPLATES["my-template"]

Advanced Template Features

Custom Jinja Templates

# Use a custom Jinja template
custom_jinja_template = Template(
    name="custom-jinja",
    chat_template="""
    {% for message in messages %}
        {% if message.role == 'system' %}
            System: {{ message.content }}
        {% elif message.role == 'user' %}
            User: {{ message.content }}
        {% elif message.role == 'assistant' %}
            Assistant: {{ message.content }}
        {% endif %}
    {% endfor %}
    """,
    system_message="You are a helpful assistant."
)

Template Inheritance

# Create a base template
base_template = Template(
    name="base",
    system_template="Base: {system_message}\n",
    user_template="User: {content}\n",
    assistant_template="Assistant: {content}\n"
)

# Create a specialized template
specialized_template = Template(
    name="specialized",
    system_template="Specialized: {system_message}\n",
    user_template=base_template.user_template,      # Inherit user template
    assistant_template=base_template.assistant_template,  # Inherit assistant template
    system_message="You are a specialized assistant."
)

Template Copying

# Copy an existing template
original = get_template("qwen2.5")
modified = original.copy()
modified.name = "qwen2.5-modified"
modified.system_message = "Modified system message"

# Register the modified version
register_template(modified)

Best Practices

1. Template Naming

  • Use descriptive, unique names
  • Include version information when appropriate
  • Use consistent naming conventions across your project

2. Template Structure

  • Keep templates focused and single-purpose
  • Use consistent formatting patterns
  • Document special tokens and their meanings

3. Policy Configuration

  • Start with default policies and customize as needed
  • Use appropriate tool placement strategies
  • Consider the impact of content processors

4. Vision Support

  • Only add vision tokens when needed
  • Use appropriate vision start/end markers
  • Consider token expansion implications

5. Testing

  • Test templates with various message types
  • Verify tool integration works correctly
  • Test vision processing if applicable
  • Validate Jinja template generation

Example: Complete Custom Template

Here's a complete example of creating a custom template for a specific use case:

from chat_bricks import Template, register_template
from chat_bricks import ToolPolicy, JsonCompactFormatter
from chat_bricks import ToolPlacement

# Create a coding assistant template
coding_template = Template(
    name="coding-assistant",

    # System message — the {tools} slot stays empty when no tools are passed,
    # so a single template handles both the tool-free and tool-enabled cases.
    system_template="""<|im_start|>system
You are an expert coding assistant. You help users write, debug, and understand code.
Always provide clear explanations and follow best practices.
{system_message}{tools}<|im_end|>
""",
    system_message="You are an expert coding assistant.",

    # User and assistant templates
    user_template="<|im_start|>user\n{content}<|im_end|>\n",
    assistant_template="<|im_start|>assistant\n{content}<|im_end|>\n",

    # Tool support for code execution
    tools_template="\n\nAvailable Tools:\n{tools}",
    user_template_with_tools="<|im_start|>user\n{content}\n\nTools: {tools}<|im_end|>\n",
    observations_template="<|im_start|>tool\n{observation}<|im_end|>\n",

    # Stop words
    stop_words=["<|im_end|>"],

    # Tool policy - place tools with first user message
    tool_policy=ToolPolicy(
        placement=ToolPlacement.FIRST_USER,
        formatter=JsonCompactFormatter()
    )
)

# Register the template
register_template(coding_template)

# Test the template
from chat_bricks import Chat

chat = Chat(template="coding-assistant", messages=[
    {"role": "user", "content": "Write a Python function to calculate fibonacci numbers"}
])

prompt = chat.prompt()
print(prompt)

This comprehensive guide should help you create custom templates that meet your specific requirements. Remember to test thoroughly and follow the best practices outlined above.