Renderer config

July 10, 2026 ยท View on GitHub

renderers.RendererConfig is the typed input to create_renderer and create_renderer_pool. It pins the renderer choice and its config at construction time.

from renderers import create_renderer, Qwen35RendererConfig

r = create_renderer(tokenizer, Qwen35RendererConfig(enable_thinking=False))
r = create_renderer(tokenizer, chat_template_kwargs={"enable_thinking": False})

RendererConfig is a pydantic discriminated union, one variant per renderer, dispatched on the name field. Most variants reject unknown fields at construction. A field can either mirror a chat-template kwarg or configure a renderer-only behavior such as parsing, image caching, or Harmony preamble construction.

Per-renderer configs

Use type(config).template_field_names() to inspect the fields that mirror chat-template kwargs. Those fields are covered by parity tests against apply_chat_template in tests/test_renderer_config_parity.py.

RendererConfig classTemplate fieldsRenderer-only fields
Qwen3Qwen3RendererConfigenable_thinking-
PrimeIntellect Qwen3PrimeQwen3RendererConfig--
Qwen3.5Qwen35RendererConfigenable_thinking, add_vision_idimage_cache_max
Qwen3.6Qwen36RendererConfigenable_thinking, add_vision_id, preserve_thinkingimage_cache_max
Qwen3-VLQwen3VLRendererConfigadd_vision_idimage_cache_max
GLM-5 / 5.1GLM5RendererConfig / GLM51RendererConfigenable_thinking, clear_thinking-
GLM-4.5GLM45RendererConfigenable_thinking-
gpt-ossGptOssRendererConfigreasoning_effort, conversation_start_dateuse_system_prompt, knowledge_cutoff, model_identity, auto_drop_analysis
Hy3Hy3RendererConfigreasoning_effort, preserved_thinking, is_training, raw_last_assistant, fallback_strategy-
Kimi K2KimiK2RendererConfig-enable_thinking
Kimi K2.5 / 2.6KimiK25RendererConfigthinkingimage_cache_max
Laguna XS.2LagunaXS2RendererConfigenable_thinking, render_assistant_messages_raw-
Laguna XS-2.1LagunaXS21RendererConfigenable_thinking-
Llama 3Llama3RendererConfigdate_string, tools_in_user_message-
MiniMax M2MiniMaxM2RendererConfigmodel_identity-
Nemotron-3 Nano / SuperNemotron3RendererConfigenable_thinking, truncate_history_thinking, low_effort-
Nemotron-3 UltraNemotron3UltraRendererConfigenable_thinking, truncate_history_thinking, medium_effort-
DeepSeek V3DeepSeekV3RendererConfig--
DeepSeek R1DeepSeekR1RendererConfig--

Configs are frozen value objects. To override a field, construct a new instance or call config.model_copy(update={...}).

Auto-resolution

create_renderer(tokenizer) resolves the renderer from tokenizer.name_or_path via MODEL_RENDERER_MAP:

from renderers import AutoRendererConfig, GLM5RendererConfig

r = create_renderer(tokenizer)
r = create_renderer(tokenizer, AutoRendererConfig(thinking_retention="all"))
r = create_renderer(tokenizer, GLM5RendererConfig(clear_thinking=False))

AutoRendererConfig carries only the shared thinking_retention override. Callers that receive run-scoped chat-template kwargs can pass them separately:

r = create_renderer(
    tokenizer,
    chat_template_kwargs={"enable_thinking": False},
)
pool = create_renderer_pool(
    "Qwen/Qwen3-8B",
    chat_template_kwargs={"enable_thinking": False},
)

Renderers resolves auto configs before applying chat_template_kwargs, so the kwargs validate against the concrete renderer config. Unknown kwargs, or kwargs that conflict with an explicit thinking_retention, fail at construction.

Auto-resolution fails loudly for VLMs without an exact registered renderer. Text-only unknown models fall back to DefaultRenderer, unless AutoRendererConfig(thinking_retention=...) was set. The default renderer cannot implement selective bridge retention, so that combination raises. AutoRendererConfig with chat_template_kwargs also raises for unknown models, because renderers cannot validate those kwargs without a concrete renderer. Use an explicit model-specific config, or DefaultRendererConfig(...) when you intentionally want opaque apply_chat_template kwargs.

thinking_retention

Every typed renderer config carries one shared optional bridge-policy override:

thinking_retention: Literal["tool_cycle", "all"] | None = None
ValueMeaning
NoneDerive the effective bridge policy from the renderer's template knobs and defaults.
"tool_cycle"Bridge within the current tool cycle; re-render when the extension opens a new user query.
"all"Allow bridging across user-query boundaries when the bridge is otherwise structurally valid.

thinking_retention affects bridge_to_next_turn, not full render(). A full render always follows the Python chat-template implementation. Only real template fields, such as clear_thinking, preserve_thinking, or truncate_history_thinking, can change full-render historical thinking.

Internally, renderers resolve an effective_thinking_retention at construction:

Internal policyBridge behavior
"template"Decline bridging; caller falls back to a full re-render.
"tool_cycle"Bridge unless new_messages introduces a user query.
"all"Do not block bridging for thinking retention.

"template" is not a public config value. Leave thinking_retention unset to get template-derived behavior.

Derived retention defaults

When thinking_retention is unset, each renderer derives its bridge policy from the knobs its template actually exposes:

RendererDerived policy
Qwen3enable_thinking=False -> all, else tool_cycle
Qwen3.5enable_thinking=False -> all, else tool_cycle
Qwen3.6preserve_thinking=True -> all; else enable_thinking=False -> all; else tool_cycle
GLM-5 / 5.1clear_thinking=False -> all; else enable_thinking=False -> all; else tool_cycle
GLM-4.5enable_thinking=False -> all, else tool_cycle
gpt-ossauto_drop_analysis=False -> all, else tool_cycle
Hy3preserved_thinking=True -> all, else tool_cycle
Kimi K2.5 / 2.6thinking=False -> all, else tool_cycle
Nemotron-3truncate_history_thinking=False -> all; else enable_thinking=False -> all; else tool_cycle
DeepSeek R1template
MiniMax M2tool_cycle
DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / XS-2.1, Llama 3all
PrimeIntellect Qwen3all

Config construction raises when an explicit template knob directly contradicts an explicit generic bridge policy. For example:

GLM5RendererConfig(clear_thinking=False, thinking_retention="tool_cycle")
# ValueError: clear_thinking=False implies thinking_retention="all"

Generation-only no-thinking knobs, such as enable_thinking=False, do not conflict with an explicit conservative thinking_retention="tool_cycle". They only change the derived default when thinking_retention is unset.

DefaultRendererConfig

DefaultRenderer wraps tokenizer.apply_chat_template for unsupported text-only models. Its config sets extra="allow" so unknown fields are forwarded as Jinja kwargs:

from renderers import create_renderer, DefaultRendererConfig

r = create_renderer(
    tokenizer,
    DefaultRendererConfig(
        tool_parser="qwen3",
        reasoning_parser="think",
        enable_thinking=False,
        custom_jinja_kwarg=True,
    ),
)

tool_parser and reasoning_parser configure DefaultRenderer itself. Every other extra field lands in model_extra and is forwarded to apply_chat_template.

DefaultRenderer rejects explicit thinking_retention and the removed preserve_* flags. Its bridge always returns None, because the template's turn-close structure is opaque to the renderer.

Downstream integration

Downstream pydantic configs can hold a single field typed as RendererConfig:

from pydantic import BaseModel, Field
from renderers import AutoRendererConfig, RendererConfig


class ClientConfig(BaseModel):
    renderer: RendererConfig = Field(default_factory=AutoRendererConfig)

In TOML or YAML, the name discriminator selects the variant:

[client.renderer]
name = "qwen3.5"
enable_thinking = false
add_vision_id = true
thinking_retention = "all"

Bogus combinations, such as add_vision_id under name = "qwen3", raise at config load with a pydantic validation error.

To construct a config from a renderer name string:

from renderers import config_from_name

cfg = config_from_name("glm-5")  # GLM5RendererConfig()
cfg = config_from_name("auto")  # None, the implicit auto form

Renaming a renderer is a breaking change

The discriminator key is the renderer name string. Renaming "qwen3.5" to something else would break downstream configs that reference it by name. Add new renderers instead of renaming existing ones.