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.
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.
| Renderer | Config class | Template fields | Renderer-only fields |
|---|---|---|---|
| Qwen3 | Qwen3RendererConfig |
enable_thinking |
- |
| PrimeIntellect Qwen3 | PrimeQwen3RendererConfig |
- | - |
| Qwen3.5 | Qwen35RendererConfig |
enable_thinking, add_vision_id |
image_cache_max |
| Qwen3.6 | Qwen36RendererConfig |
enable_thinking, add_vision_id, preserve_thinking |
image_cache_max |
| Qwen3-VL | Qwen3VLRendererConfig |
add_vision_id |
image_cache_max |
| GLM-5 / 5.1 | GLM5RendererConfig / GLM51RendererConfig |
enable_thinking, clear_thinking |
- |
| GLM-4.5 | GLM45RendererConfig |
enable_thinking |
- |
| gpt-oss | GptOssRendererConfig |
reasoning_effort, conversation_start_date |
use_system_prompt, knowledge_cutoff, model_identity, auto_drop_analysis |
| Hy3 | Hy3RendererConfig |
reasoning_effort, preserved_thinking, is_training, raw_last_assistant, fallback_strategy |
- |
| Kimi K2 | KimiK2RendererConfig |
- | enable_thinking |
| Kimi K2.5 / 2.6 | KimiK25RendererConfig |
thinking |
image_cache_max |
| Laguna XS.2 | LagunaXS2RendererConfig |
enable_thinking, render_assistant_messages_raw |
- |
| Laguna XS-2.1 | LagunaXS21RendererConfig |
enable_thinking |
- |
| Llama 3 | Llama3RendererConfig |
date_string, tools_in_user_message |
- |
| MiniMax M2 | MiniMaxM2RendererConfig |
model_identity |
- |
| Nemotron-3 Nano / Super | Nemotron3RendererConfig |
enable_thinking, truncate_history_thinking, low_effort |
- |
| Nemotron-3 Ultra | Nemotron3UltraRendererConfig |
enable_thinking, truncate_history_thinking, medium_effort |
- |
| DeepSeek V3 | DeepSeekV3RendererConfig |
- | - |
| DeepSeek R1 | DeepSeekR1RendererConfig |
- | - |
Configs are frozen value objects. To override a field, construct a new instance
or call config.model_copy(update={...}).
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.
Every typed renderer config carries one shared optional bridge-policy override:
thinking_retention: Literal["tool_cycle", "all"] | None = None| Value | Meaning |
|---|---|
None |
Derive 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 policy | Bridge 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.
When thinking_retention is unset, each renderer derives its bridge policy from
the knobs its template actually exposes:
| Renderer | Derived policy |
|---|---|
| Qwen3 | enable_thinking=False -> all, else tool_cycle |
| Qwen3.5 | enable_thinking=False -> all, else tool_cycle |
| Qwen3.6 | preserve_thinking=True -> all; else enable_thinking=False -> all; else tool_cycle |
| GLM-5 / 5.1 | clear_thinking=False -> all; else enable_thinking=False -> all; else tool_cycle |
| GLM-4.5 | enable_thinking=False -> all, else tool_cycle |
| gpt-oss | auto_drop_analysis=False -> all, else tool_cycle |
| Hy3 | preserved_thinking=True -> all, else tool_cycle |
| Kimi K2.5 / 2.6 | thinking=False -> all, else tool_cycle |
| Nemotron-3 | truncate_history_thinking=False -> all; else enable_thinking=False -> all; else tool_cycle |
| DeepSeek R1 | template |
| MiniMax M2 | tool_cycle |
| DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / XS-2.1, Llama 3 | all |
| PrimeIntellect Qwen3 | all |
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.
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 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 formThe 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.