renderers 0.1.10.dev11__tar.gz → 0.1.10.dev13__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/PKG-INFO +3 -3
  2. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/README.md +2 -2
  3. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/docs/renderer-config.md +14 -6
  4. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/pyproject.toml +4 -0
  5. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/__init__.py +4 -0
  6. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/_version.py +2 -2
  7. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/base.py +29 -2
  8. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/configs.py +136 -22
  9. renderers-0.1.10.dev13/renderers/inkling.py +997 -0
  10. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/parsing.py +132 -0
  11. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/conftest.py +2 -0
  12. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_bridge.py +2 -0
  13. renderers-0.1.10.dev13/tests/test_inkling.py +523 -0
  14. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_multimodal.py +142 -7
  15. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_renderer_config.py +49 -2
  16. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_renderer_config_parity.py +2 -0
  17. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_roundtrip.py +2 -0
  18. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/uv.lock +48 -50
  19. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/.github/workflows/publish-dev.yml +0 -0
  20. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/.github/workflows/publish.yml +0 -0
  21. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/.github/workflows/style.yml +0 -0
  22. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/.github/workflows/test.yml +0 -0
  23. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/.gitignore +0 -0
  24. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/.pre-commit-config.yaml +0 -0
  25. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/LICENSE +0 -0
  26. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/examples/README.md +0 -0
  27. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/examples/sglang/multiturn_generate_sglang.py +0 -0
  28. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/examples/sglang/online_multiturn_sglang.py +0 -0
  29. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/examples/tinker/multiturn_generate_tinker.py +0 -0
  30. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/examples/transformers/multiturn_generate_transformers.py +0 -0
  31. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/examples/vllm/multiturn_generate_vllm.py +0 -0
  32. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/client.py +0 -0
  33. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/deepseek_r1.py +0 -0
  34. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/deepseek_v3.py +0 -0
  35. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/default.py +0 -0
  36. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/gemma4.py +0 -0
  37. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/glm45.py +0 -0
  38. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/glm5.py +0 -0
  39. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/gpt_oss.py +0 -0
  40. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/hy3.py +0 -0
  41. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/kimi_k2.py +0 -0
  42. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/kimi_k25.py +0 -0
  43. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/laguna_s21.py +0 -0
  44. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/laguna_xs2.py +0 -0
  45. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/llama_3.py +0 -0
  46. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/minimax_m2.py +0 -0
  47. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/nemotron3.py +0 -0
  48. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/parsers.py +0 -0
  49. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/prime_qwen3.py +0 -0
  50. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/qwen3.py +0 -0
  51. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/qwen35.py +0 -0
  52. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/qwen36.py +0 -0
  53. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/renderers/qwen3_vl.py +0 -0
  54. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_build_helpers.py +0 -0
  55. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_client.py +0 -0
  56. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_deepseek_r1.py +0 -0
  57. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_disabled_thinking_stability.py +0 -0
  58. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_gemma4.py +0 -0
  59. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_glm_tool_name_validation.py +0 -0
  60. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_gpt_oss_harmony_parity.py +0 -0
  61. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_hy3.py +0 -0
  62. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_incremental.py +0 -0
  63. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_is_content.py +0 -0
  64. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_kimi_k25_tool_schema.py +0 -0
  65. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_laguna_m1.py +0 -0
  66. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_laguna_s21.py +0 -0
  67. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_laguna_xs21.py +0 -0
  68. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_llama_3.py +0 -0
  69. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_load_tokenizer.py +0 -0
  70. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_message_indices.py +0 -0
  71. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_message_tool_names.py +0 -0
  72. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_nemotron3_parity.py +0 -0
  73. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_nemotron3_ultra.py +0 -0
  74. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_parse_response.py +0 -0
  75. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_parse_response_robustness.py +0 -0
  76. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_parsers.py +0 -0
  77. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_preserve_thinking.py +0 -0
  78. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_prime_qwen3_parity.py +0 -0
  79. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_qwen35_size_coverage.py +0 -0
  80. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_render_ids.py +0 -0
  81. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_sampled_mask.py +0 -0
  82. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_tokens_per_message.py +0 -0
  83. {renderers-0.1.10.dev11 → renderers-0.1.10.dev13}/tests/test_tool_arg_type_preservation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: renderers
3
- Version: 0.1.10.dev11
3
+ Version: 0.1.10.dev13
4
4
  Summary: Chat template renderers — deterministic message-to-token conversion for LLM training
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -56,7 +56,7 @@ next_prompt_ids = r.bridge_to_next_turn(
56
56
  )
57
57
  ```
58
58
 
59
- Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
59
+ Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, `inkling` / `inkling-small`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper. `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `kimi-k2.5` / `kimi-k2.6`, and the Inkling checkpoints are multimodal (Inkling handles both image **and** audio).
60
60
 
61
61
  ## API
62
62
 
@@ -159,7 +159,7 @@ renderer = create_renderer(
159
159
 
160
160
  Discriminated union: every per-renderer config is a variant of `RendererConfig`, dispatched on the `name` field. Bogus combinations (e.g. `add_vision_id` under `name="qwen3"`) error at construction with a `pydantic.ValidationError`. Downstream pydantic configs (prime-rl orchestrator, verifiers `ClientConfig`) hold a single field typed as `RendererConfig` and inherit the same strict-per-variant validation.
161
161
 
162
- When `chat_template_kwargs` is passed with `config=None` / `AutoRendererConfig`, renderers first resolves the concrete renderer from the model name, then validates those kwargs against that renderer's config. `Auto + unknown model + chat_template_kwargs` fails loudly; use an explicit typed config or explicit `DefaultRendererConfig` for opaque fallback templates.
162
+ When `chat_template_kwargs` is passed with `config=None` / `AutoRendererConfig`, renderers first resolves the concrete renderer from the model name, then validates each key against that renderer's explicit template-kwarg allowlist. Renderer-only fields such as `image_cache_max` must be passed through the typed config instead. `Auto + unknown model + chat_template_kwargs` fails loudly; use an explicit typed config or explicit `DefaultRendererConfig` for opaque fallback templates.
163
163
 
164
164
  One shared behaviour flag lives on typed renderer configs: `thinking_retention`, an optional bridge-policy override. Leave it unset to derive bridge behaviour from the chat template and its renderer-exposed kwargs.
165
165
 
@@ -40,7 +40,7 @@ next_prompt_ids = r.bridge_to_next_turn(
40
40
  )
41
41
  ```
42
42
 
43
- Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
43
+ Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, `inkling` / `inkling-small`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper. `qwen3-vl`, `qwen3.5`, `qwen3.6`, `gemma4`, `kimi-k2.5` / `kimi-k2.6`, and the Inkling checkpoints are multimodal (Inkling handles both image **and** audio).
44
44
 
45
45
  ## API
46
46
 
@@ -143,7 +143,7 @@ renderer = create_renderer(
143
143
 
144
144
  Discriminated union: every per-renderer config is a variant of `RendererConfig`, dispatched on the `name` field. Bogus combinations (e.g. `add_vision_id` under `name="qwen3"`) error at construction with a `pydantic.ValidationError`. Downstream pydantic configs (prime-rl orchestrator, verifiers `ClientConfig`) hold a single field typed as `RendererConfig` and inherit the same strict-per-variant validation.
145
145
 
146
- When `chat_template_kwargs` is passed with `config=None` / `AutoRendererConfig`, renderers first resolves the concrete renderer from the model name, then validates those kwargs against that renderer's config. `Auto + unknown model + chat_template_kwargs` fails loudly; use an explicit typed config or explicit `DefaultRendererConfig` for opaque fallback templates.
146
+ When `chat_template_kwargs` is passed with `config=None` / `AutoRendererConfig`, renderers first resolves the concrete renderer from the model name, then validates each key against that renderer's explicit template-kwarg allowlist. Renderer-only fields such as `image_cache_max` must be passed through the typed config instead. `Auto + unknown model + chat_template_kwargs` fails loudly; use an explicit typed config or explicit `DefaultRendererConfig` for opaque fallback templates.
147
147
 
148
148
  One shared behaviour flag lives on typed renderer configs: `thinking_retention`, an optional bridge-policy override. Leave it unset to derive bridge behaviour from the chat template and its renderer-exposed kwargs.
149
149
 
@@ -19,8 +19,10 @@ construction.
19
19
 
20
20
  ## Per-renderer configs
21
21
 
22
- Use `type(config).template_field_names()` to inspect the fields that mirror
23
- chat-template kwargs. Those fields are covered by parity tests against
22
+ Use `type(config).template_field_names()` to inspect the explicit allowlist of
23
+ fields accepted through `chat_template_kwargs`. Every renderer-specific field
24
+ is classified as either a template field or a renderer-only field at class
25
+ definition time. Template fields are covered by parity tests against
24
26
  `apply_chat_template` in `tests/test_renderer_config_parity.py`.
25
27
 
26
28
  | Renderer | Config class | Template fields | Renderer-only fields |
@@ -37,6 +39,7 @@ chat-template kwargs. Those fields are covered by parity tests against
37
39
  | Hy3 | `Hy3RendererConfig` | `reasoning_effort`, `preserved_thinking`, `is_training`, `raw_last_assistant`, `fallback_strategy` | - |
38
40
  | Kimi K2 | `KimiK2RendererConfig` | - | `enable_thinking` |
39
41
  | Kimi K2.5 / 2.6 | `KimiK25RendererConfig` | `thinking` | `image_cache_max` |
42
+ | Inkling / Inkling-Small | `InklingRendererConfig` | `reasoning_effort` | `image_cache_max`, `audio_cache_max` |
40
43
  | Laguna XS.2 | `LagunaXS2RendererConfig` | `enable_thinking`, `render_assistant_messages_raw` | - |
41
44
  | Laguna M.1 | `LagunaM1RendererConfig` | `enable_thinking`, `render_assistant_messages_raw` | - |
42
45
  | Laguna XS-2.1 | `LagunaXS21RendererConfig` | `enable_thinking` | - |
@@ -80,8 +83,10 @@ pool = create_renderer_pool(
80
83
  ```
81
84
 
82
85
  Renderers resolves auto configs before applying `chat_template_kwargs`, so the
83
- kwargs validate against the concrete renderer config. Unknown kwargs, or kwargs
84
- that conflict with an explicit `thinking_retention`, fail at construction.
86
+ kwargs validate against the concrete renderer's template-field allowlist.
87
+ Unknown kwargs, renderer-only fields, or kwargs that conflict with an explicit
88
+ `thinking_retention` fail at construction. Renderer-only options remain valid
89
+ when supplied through the typed config itself.
85
90
 
86
91
  Auto-resolution fails loudly for VLMs without an exact registered renderer.
87
92
  Text-only unknown models fall back to `DefaultRenderer`, unless
@@ -90,7 +95,10 @@ cannot implement selective bridge retention, so that combination raises.
90
95
  `AutoRendererConfig` with `chat_template_kwargs` also raises for unknown models,
91
96
  because renderers cannot validate those kwargs without a concrete renderer.
92
97
  Use an explicit model-specific config, or `DefaultRendererConfig(...)` when you
93
- intentionally want opaque `apply_chat_template` kwargs.
98
+ intentionally want opaque `apply_chat_template` kwargs. Even for the default
99
+ renderer, typed fields such as `tool_parser`, `reasoning_parser`, and
100
+ `thinking_retention` must be passed through the config rather than through the
101
+ opaque kwargs mapping.
94
102
 
95
103
  ## `thinking_retention`
96
104
 
@@ -141,7 +149,7 @@ the knobs its template actually exposes:
141
149
  | Nemotron-3 / 3.5 | `truncate_history_thinking=False -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
142
150
  | DeepSeek R1 | `template` |
143
151
  | MiniMax M2 | `tool_cycle` |
144
- | DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / M.1 / XS-2.1 / S-2.1, Llama 3 | `all` |
152
+ | DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / M.1 / XS-2.1 / S-2.1, Llama 3, Inkling | `all` |
145
153
  | PrimeIntellect Qwen3 | `all` |
146
154
 
147
155
  Config construction raises when an explicit template knob directly contradicts
@@ -19,6 +19,10 @@ dependencies = [
19
19
  "openai>=1.108.1",
20
20
  "tiktoken",
21
21
  "jinja2",
22
+ # Keep this floor compatible with prime-rl's transformers pin. Inkling's
23
+ # tokenizer and text-only renderer work on older releases; image/audio
24
+ # processing fails lazily with an upgrade message when InklingProcessor is
25
+ # unavailable (native support starts in transformers 5.14).
22
26
  "transformers>=4.50.0",
23
27
  # Used by GptOssRenderer to render and parse harmony tokens. Vendoring
24
28
  # OpenAI's reference implementation keeps us byte-identical with vLLM
@@ -53,6 +53,7 @@ from renderers.configs import (
53
53
  Gemma4RendererConfig,
54
54
  GptOssRendererConfig,
55
55
  Hy3RendererConfig,
56
+ InklingRendererConfig,
56
57
  KimiK25RendererConfig,
57
58
  KimiK2RendererConfig,
58
59
  LagunaM1RendererConfig,
@@ -93,6 +94,7 @@ _LAZY_RENDERERS: dict[str, str] = {
93
94
  "GptOssRenderer": "renderers.gpt_oss",
94
95
  "Gemma4Renderer": "renderers.gemma4",
95
96
  "Hy3Renderer": "renderers.hy3",
97
+ "InklingRenderer": "renderers.inkling",
96
98
  "KimiK25Renderer": "renderers.kimi_k25",
97
99
  "KimiK2Renderer": "renderers.kimi_k2",
98
100
  "LagunaM1Renderer": "renderers.laguna_xs2",
@@ -151,6 +153,8 @@ __all__ = [
151
153
  "Hy3Renderer",
152
154
  "Hy3RendererConfig",
153
155
  "ImagePart",
156
+ "InklingRenderer",
157
+ "InklingRendererConfig",
154
158
  "KimiK25Renderer",
155
159
  "KimiK25RendererConfig",
156
160
  "KimiK2Renderer",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.10.dev11'
22
- __version_tuple__ = version_tuple = (0, 1, 10, 'dev11')
21
+ __version__ = version = '0.1.10.dev13'
22
+ __version_tuple__ = version_tuple = (0, 1, 10, 'dev13')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1093,6 +1093,10 @@ MODEL_RENDERER_MAP: dict[str, str] = {
1093
1093
  # GPT-OSS.
1094
1094
  "openai/gpt-oss-20b": "gpt-oss",
1095
1095
  "openai/gpt-oss-120b": "gpt-oss",
1096
+ # Thinking Machines Inkling checkpoints share byte-identical tokenizer,
1097
+ # chat-template, and processor assets (vision + audio; transformers >= 5.14).
1098
+ "thinkingmachines/Inkling": "inkling",
1099
+ "thinkingmachines/Inkling-Small": "inkling",
1096
1100
  # Tencent Hunyuan Hy3 (295B-A21B MoE). The FP8 checkpoint shares the same
1097
1101
  # tokenizer and chat template. Hy3-preview is deliberately unmapped: it
1098
1102
  # ships an older, incompatible template (un-suffixed special tokens,
@@ -1141,6 +1145,8 @@ MULTIMODAL_MODELS: dict[str, set[str]] = {
1141
1145
  # ``grid_thws``.
1142
1146
  "moonshotai/Kimi-K2.5": {"image"},
1143
1147
  "moonshotai/Kimi-K2.6": {"image"},
1148
+ "thinkingmachines/Inkling": {"image", "audio"},
1149
+ "thinkingmachines/Inkling-Small": {"image", "audio"},
1144
1150
  }
1145
1151
 
1146
1152
 
@@ -1340,6 +1346,7 @@ def _populate_registry():
1340
1346
  from renderers.gpt_oss import GptOssRenderer
1341
1347
  from renderers.gemma4 import Gemma4Renderer
1342
1348
  from renderers.hy3 import Hy3Renderer
1349
+ from renderers.inkling import InklingRenderer
1343
1350
  from renderers.kimi_k2 import KimiK2Renderer
1344
1351
  from renderers.kimi_k25 import KimiK25Renderer
1345
1352
  from renderers.laguna_s21 import LagunaS21Renderer
@@ -1377,6 +1384,7 @@ def _populate_registry():
1377
1384
  "deepseek-v3": DeepSeekV3Renderer,
1378
1385
  "deepseek-r1": DeepSeekR1Renderer,
1379
1386
  "hy3": Hy3Renderer,
1387
+ "inkling": InklingRenderer,
1380
1388
  "kimi-k2": KimiK2Renderer,
1381
1389
  "kimi-k2.5": KimiK25Renderer,
1382
1390
  "laguna-xs.2": LagunaXS2Renderer,
@@ -1483,12 +1491,31 @@ def _merge_chat_template_kwargs(
1483
1491
  return config
1484
1492
  if not isinstance(chat_template_kwargs, Mapping):
1485
1493
  raise TypeError("chat_template_kwargs must be a mapping.")
1494
+ kwargs = dict(chat_template_kwargs)
1495
+ config_cls = type(config)
1496
+ allowed = config_cls.template_field_names()
1497
+ if config_cls._allow_opaque_template_kwargs:
1498
+ reserved = frozenset(config_cls.model_fields) - allowed - {"name"}
1499
+ unsupported = frozenset(kwargs) & reserved
1500
+ else:
1501
+ unsupported = frozenset(kwargs) - allowed
1502
+ if unsupported:
1503
+ allowed_text = (
1504
+ "opaque Jinja kwargs"
1505
+ if config_cls._allow_opaque_template_kwargs
1506
+ else ", ".join(sorted(allowed)) or "(none)"
1507
+ )
1508
+ raise ValueError(
1509
+ f"Unsupported chat_template_kwargs for {config.name!r}: "
1510
+ f"{sorted(unsupported)}. Allowed: {allowed_text}. Pass "
1511
+ "renderer-internal options through the typed config instead."
1512
+ )
1486
1513
  data: dict[str, Any] = {"name": config.name}
1487
1514
  for field_name in config.__pydantic_fields_set__:
1488
1515
  data[field_name] = getattr(config, field_name)
1489
1516
  data.update(getattr(config, "model_extra", None) or {})
1490
- data.update(dict(chat_template_kwargs))
1491
- return type(config).model_validate(data)
1517
+ data.update(kwargs)
1518
+ return config_cls.model_validate(data)
1492
1519
 
1493
1520
 
1494
1521
  def _resolve_renderer_config(
@@ -1,10 +1,11 @@
1
1
  """Typed renderer configs — one pydantic model per renderer, unified by a
2
2
  discriminated union (``RendererConfig``).
3
3
 
4
- Each renderer accepts its own typed config; bad combinations (e.g.
5
- ``add_vision_id`` under ``name="qwen3"``) fail at config-load time with a
6
- pydantic ``ValidationError`` rather than at runtime via an allowlist
7
- check. The shared ``thinking_retention`` flag is optional: ``None`` means
4
+ Each renderer accepts its own typed config and declares an explicit
5
+ ``_template_fields`` allowlist for fields that may arrive through
6
+ ``chat_template_kwargs``. Bad combinations (e.g. ``add_vision_id`` under
7
+ ``name="qwen3"``) fail before renderer construction. The shared
8
+ ``thinking_retention`` flag is optional: ``None`` means
8
9
  "derive bridge policy from this renderer's chat-template knobs"; an
9
10
  explicit value is a bridge-policy override.
10
11
 
@@ -87,31 +88,36 @@ class BaseRendererConfig(BaseConfig):
87
88
  to the Python chat-template implementation and its explicit template
88
89
  kwargs."""
89
90
 
90
- # Fields that are renderer-internal not forwarded to (or mirrored
91
- # by) ``apply_chat_template``. Override in subclasses that hold
92
- # non-template config (e.g. ``image_cache_max``, GptOss's
93
- # ``use_system_prompt`` / ``knowledge_cutoff`` / ``model_identity``,
94
- # or fields that exist as renderer conventions without a Jinja
95
- # analogue like DeepSeek V3 / Kimi K2 ``enable_thinking``).
96
- #
97
- # Used by parity tests to compute the field subset that, when
98
- # changed, must produce token streams matching
99
- # ``apply_chat_template`` — see :meth:`template_field_names`. The
100
- # renderer is the only end-to-end consumer of these fields, so this
101
- # is a renderer-side bookkeeping concern rather than a public API.
91
+ # Every renderer-specific field must be classified exactly once: either
92
+ # as a chat-template kwarg or as renderer-internal configuration.
93
+ _template_fields: ClassVar[frozenset[str]] = frozenset()
102
94
  _internal_fields: ClassVar[frozenset[str]] = frozenset()
95
+ _allow_opaque_template_kwargs: ClassVar[bool] = False
96
+
97
+ @classmethod
98
+ def __pydantic_init_subclass__(cls, **kwargs) -> None:
99
+ super().__pydantic_init_subclass__(**kwargs)
100
+ base_fields = frozenset(BaseRendererConfig.model_fields)
101
+ renderer_fields = frozenset(cls.model_fields) - base_fields - {"name"}
102
+ overlap = cls._template_fields & cls._internal_fields
103
+ missing = renderer_fields - cls._template_fields - cls._internal_fields
104
+ unknown = (cls._template_fields | cls._internal_fields) - renderer_fields
105
+ if overlap or missing or unknown:
106
+ raise TypeError(
107
+ f"{cls.__name__} has an invalid renderer-field classification: "
108
+ f"overlap={sorted(overlap)}, missing={sorted(missing)}, "
109
+ f"unknown={sorted(unknown)}"
110
+ )
103
111
 
104
112
  @classmethod
105
113
  def template_field_names(cls) -> frozenset[str]:
106
114
  """Subset of fields that mirror Jinja chat-template kwargs.
107
115
 
108
- Default: every non-base field except ``name`` and any field
109
- listed in ``_internal_fields``. Used by the parity test matrix
110
- (``tests/test_renderer_config_parity.py``) to discover the
111
- cells that must agree with ``apply_chat_template``.
116
+ Used both as the runtime allowlist for ``chat_template_kwargs`` and
117
+ by parity tests to discover the cells that must agree with
118
+ ``apply_chat_template``.
112
119
  """
113
- base = frozenset(BaseRendererConfig.model_fields)
114
- return frozenset(cls.model_fields) - base - {"name"} - cls._internal_fields
120
+ return cls._template_fields
115
121
 
116
122
 
117
123
  class AutoRendererConfig(BaseRendererConfig):
@@ -122,6 +128,7 @@ class AutoRendererConfig(BaseRendererConfig):
122
128
  at the call site."""
123
129
 
124
130
  name: Literal["auto"] = "auto"
131
+ _template_fields = frozenset()
125
132
 
126
133
 
127
134
  class DefaultRendererConfig(BaseRendererConfig):
@@ -148,6 +155,8 @@ class DefaultRendererConfig(BaseRendererConfig):
148
155
  # DefaultRenderer's parsing pipeline, not the underlying Jinja
149
156
  # template. Jinja kwargs live in ``model_extra`` (extra="allow").
150
157
  _internal_fields = frozenset({"tool_parser", "reasoning_parser"})
158
+ _template_fields = frozenset()
159
+ _allow_opaque_template_kwargs = True
151
160
 
152
161
  @model_validator(mode="after")
153
162
  def _reject_legacy_preserve_flags(self):
@@ -174,6 +183,7 @@ class Qwen3RendererConfig(BaseRendererConfig):
174
183
  """Qwen3 (text-only) renderer config."""
175
184
 
176
185
  name: Literal["qwen3"] = "qwen3"
186
+ _template_fields = frozenset({"enable_thinking"})
177
187
 
178
188
  enable_thinking: bool = True
179
189
  """When ``True``, the generation prompt includes ``<think>`` so the
@@ -191,12 +201,14 @@ class PrimeQwen3RendererConfig(BaseRendererConfig):
191
201
  """PrimeIntellect Qwen3 renderer config."""
192
202
 
193
203
  name: Literal["prime-qwen3"] = "prime-qwen3"
204
+ _template_fields = frozenset()
194
205
 
195
206
 
196
207
  class Qwen35RendererConfig(BaseRendererConfig):
197
208
  """Qwen3.5 renderer config."""
198
209
 
199
210
  name: Literal["qwen3.5"] = "qwen3.5"
211
+ _template_fields = frozenset({"enable_thinking", "add_vision_id"})
200
212
 
201
213
  enable_thinking: bool | None = None
202
214
  """When ``True``, the generation prompt includes ``<think>``. ``None``
@@ -228,6 +240,9 @@ class Qwen36RendererConfig(BaseRendererConfig):
228
240
  """Qwen3.6 renderer config. Inherits Qwen3.5's template surface."""
229
241
 
230
242
  name: Literal["qwen3.6"] = "qwen3.6"
243
+ _template_fields = frozenset(
244
+ {"enable_thinking", "add_vision_id", "preserve_thinking"}
245
+ )
231
246
 
232
247
  enable_thinking: bool | None = None
233
248
  """See :class:`Qwen35RendererConfig.enable_thinking`."""
@@ -260,6 +275,7 @@ class Qwen3VLRendererConfig(BaseRendererConfig):
260
275
  """Qwen3-VL renderer config."""
261
276
 
262
277
  name: Literal["qwen3-vl"] = "qwen3-vl"
278
+ _template_fields = frozenset({"add_vision_id"})
263
279
 
264
280
  add_vision_id: bool = False
265
281
  """See :class:`Qwen35RendererConfig.add_vision_id`."""
@@ -274,6 +290,7 @@ class Gemma4RendererConfig(BaseRendererConfig):
274
290
  """Gemma 4 renderer config."""
275
291
 
276
292
  name: Literal["gemma4"] = "gemma4"
293
+ _template_fields = frozenset({"enable_thinking", "preserve_thinking"})
277
294
 
278
295
  enable_thinking: bool = False
279
296
  """Enable Gemma 4's thinking mode. Mirrors the canonical template kwarg."""
@@ -301,6 +318,7 @@ class GLM5RendererConfig(BaseRendererConfig):
301
318
  """GLM-5 renderer config."""
302
319
 
303
320
  name: Literal["glm-5"] = "glm-5"
321
+ _template_fields = frozenset({"enable_thinking", "clear_thinking"})
304
322
 
305
323
  enable_thinking: bool = True
306
324
  """When ``True``, the generation prompt includes ``<think>``. Mirrors
@@ -328,6 +346,7 @@ class GLM51RendererConfig(BaseRendererConfig):
328
346
  discriminator so the registry can route to ``GLM51Renderer``."""
329
347
 
330
348
  name: Literal["glm-5.1"] = "glm-5.1"
349
+ _template_fields = frozenset({"enable_thinking", "clear_thinking"})
331
350
 
332
351
  enable_thinking: bool = True
333
352
  """See :class:`GLM5RendererConfig.enable_thinking`."""
@@ -350,6 +369,7 @@ class GLM45RendererConfig(BaseRendererConfig):
350
369
  """GLM-4.5 Air renderer config."""
351
370
 
352
371
  name: Literal["glm-4.5"] = "glm-4.5"
372
+ _template_fields = frozenset({"enable_thinking"})
353
373
 
354
374
  enable_thinking: bool = True
355
375
  """When ``True``, the generation prompt includes ``<think>``. Mirrors
@@ -377,6 +397,15 @@ class Hy3RendererConfig(BaseRendererConfig):
377
397
  """
378
398
 
379
399
  name: Literal["hy3"] = "hy3"
400
+ _template_fields = frozenset(
401
+ {
402
+ "reasoning_effort",
403
+ "preserved_thinking",
404
+ "is_training",
405
+ "raw_last_assistant",
406
+ "fallback_strategy",
407
+ }
408
+ )
380
409
 
381
410
  reasoning_effort: Literal["no_think", "low", "high"] = "no_think"
382
411
  """Reasoning gate. Mirrors the chat template's ``reasoning_effort`` kwarg.
@@ -420,6 +449,69 @@ class Hy3RendererConfig(BaseRendererConfig):
420
449
  return self
421
450
 
422
451
 
452
+ # Inkling ``reasoning_effort`` label → float (the template's own effort_map).
453
+ INKLING_EFFORT_MAP: dict[str, float] = {
454
+ "none": 0.0,
455
+ "minimal": 0.1,
456
+ "low": 0.2,
457
+ "medium": 0.7,
458
+ "high": 0.9,
459
+ "max": 0.99,
460
+ }
461
+
462
+
463
+ class InklingRendererConfig(BaseRendererConfig):
464
+ """Renderer config for Inkling and Inkling-Small.
465
+
466
+ Inkling gates reasoning depth via a ``reasoning_effort`` knob rather
467
+ than a boolean ``enable_thinking``. It accepts either a string label
468
+ from :data:`INKLING_EFFORT_MAP` (``none`` … ``max``) or a raw float in
469
+ ``[0.0, 0.99]``; the renderer emits ``Thinking effort level: {N}`` in a
470
+ leading system message exactly as the chat template does (label mapped
471
+ to its float, ``0.0`` printed as ``"0"``). The template default —
472
+ applied when no ``reasoning_effort`` is passed — is ``0.9``, which this
473
+ config mirrors.
474
+
475
+ Reasoning is preserved on every historical assistant turn (the template
476
+ has no history-dropping knob), so the effective bridge policy is
477
+ ``"all"``.
478
+ """
479
+
480
+ name: Literal["inkling"] = "inkling"
481
+ _template_fields = frozenset({"reasoning_effort"})
482
+
483
+ reasoning_effort: str | float = 0.9
484
+ """Reasoning-effort gate. Mirrors the chat template's ``reasoning_effort``
485
+ kwarg: a label in :data:`INKLING_EFFORT_MAP` or a float in ``[0.0, 0.99]``.
486
+ Default ``0.9`` matches the template's own default (equivalent to
487
+ ``"high"``)."""
488
+
489
+ image_cache_max: int = 256
490
+ """FIFO bound on the per-renderer image-processor cache. Renderer-
491
+ internal — not a Jinja chat-template kwarg."""
492
+
493
+ audio_cache_max: int = 256
494
+ """FIFO bound on the per-renderer audio-processor cache. Renderer-
495
+ internal — not a Jinja chat-template kwarg."""
496
+
497
+ _internal_fields = frozenset({"image_cache_max", "audio_cache_max"})
498
+
499
+ @model_validator(mode="after")
500
+ def _check_reasoning_effort(self):
501
+ eff = self.reasoning_effort
502
+ if isinstance(eff, str):
503
+ if eff.strip() not in INKLING_EFFORT_MAP:
504
+ raise ValueError(
505
+ f"reasoning_effort={eff!r} is not a known label. "
506
+ f"Use one of {sorted(INKLING_EFFORT_MAP)} or a float in [0.0, 0.99]."
507
+ )
508
+ else:
509
+ num = float(eff)
510
+ if num < 0.0 or num > 0.99:
511
+ raise ValueError(f"reasoning_effort={eff!r} must be in [0.0, 0.99].")
512
+ return self
513
+
514
+
423
515
  class GptOssRendererConfig(BaseRendererConfig):
424
516
  """OpenAI gpt-oss (harmony) renderer config.
425
517
 
@@ -433,6 +525,7 @@ class GptOssRendererConfig(BaseRendererConfig):
433
525
  """
434
526
 
435
527
  name: Literal["gpt-oss"] = "gpt-oss"
528
+ _template_fields = frozenset({"reasoning_effort", "conversation_start_date"})
436
529
 
437
530
  reasoning_effort: Literal["low", "medium", "high"] = "medium"
438
531
  """Harmony reasoning-effort tag. Mirrors the ``apply_chat_template``
@@ -489,6 +582,7 @@ class KimiK2RendererConfig(BaseRendererConfig):
489
582
  """
490
583
 
491
584
  name: Literal["kimi-k2"] = "kimi-k2"
585
+ _template_fields = frozenset()
492
586
 
493
587
  enable_thinking: bool = True
494
588
  """No-op for Kimi K2 (template doesn't gate on it). Stored for
@@ -501,6 +595,7 @@ class KimiK25RendererConfig(BaseRendererConfig):
501
595
  """Kimi K2.5 renderer config."""
502
596
 
503
597
  name: Literal["kimi-k2.5"] = "kimi-k2.5"
598
+ _template_fields = frozenset({"thinking"})
504
599
 
505
600
  thinking: bool = True
506
601
  """When ``True``, the generation prompt prefills ``<think>``; when
@@ -518,6 +613,7 @@ class LagunaXS2RendererConfig(BaseRendererConfig):
518
613
  """Laguna XS.2 renderer config."""
519
614
 
520
615
  name: Literal["laguna-xs.2"] = "laguna-xs.2"
616
+ _template_fields = frozenset({"enable_thinking", "render_assistant_messages_raw"})
521
617
 
522
618
  enable_thinking: bool = False
523
619
  """When ``True``, the generation prompt includes ``<think>``. Mirrors
@@ -543,6 +639,7 @@ class LagunaM1RendererConfig(BaseRendererConfig):
543
639
  """
544
640
 
545
641
  name: Literal["laguna-m.1"] = "laguna-m.1"
642
+ _template_fields = frozenset({"enable_thinking", "render_assistant_messages_raw"})
546
643
 
547
644
  enable_thinking: bool = False
548
645
  """When ``True``, the generation prompt includes ``<think>``. Mirrors
@@ -564,6 +661,7 @@ class LagunaXS21RendererConfig(BaseRendererConfig):
564
661
  """
565
662
 
566
663
  name: Literal["laguna-xs-2.1"] = "laguna-xs-2.1"
664
+ _template_fields = frozenset({"enable_thinking"})
567
665
 
568
666
  enable_thinking: bool = False
569
667
  """When ``True``, the generation prompt ends with ``<think>`` and
@@ -587,6 +685,7 @@ class LagunaS21RendererConfig(BaseRendererConfig):
587
685
  """
588
686
 
589
687
  name: Literal["laguna-s-2.1"] = "laguna-s-2.1"
688
+ _template_fields = frozenset({"enable_thinking", "preserve_thinking"})
590
689
 
591
690
  enable_thinking: bool = True
592
691
  """When ``True``, the generation prompt ends with ``<think>`` and every
@@ -616,6 +715,7 @@ class Llama3RendererConfig(BaseRendererConfig):
616
715
  """
617
716
 
618
717
  name: Literal["llama-3"] = "llama-3"
718
+ _template_fields = frozenset({"date_string", "tools_in_user_message"})
619
719
 
620
720
  date_string: str = "26 Jul 2024"
621
721
  """``Today Date`` value injected into the system preamble. Pinned to
@@ -634,6 +734,7 @@ class MiniMaxM2RendererConfig(BaseRendererConfig):
634
734
  """MiniMax M2 / M2.5 renderer config."""
635
735
 
636
736
  name: Literal["minimax-m2"] = "minimax-m2"
737
+ _template_fields = frozenset({"model_identity"})
637
738
 
638
739
  model_identity: str = "You are a helpful assistant. Your name is MiniMax-M2.5 and is built by MiniMax."
639
740
  """Fallback persona used when no system message is supplied. Mirrors
@@ -651,6 +752,9 @@ class Nemotron3RendererConfig(BaseRendererConfig):
651
752
  """
652
753
 
653
754
  name: Literal["nemotron-3"] = "nemotron-3"
755
+ _template_fields = frozenset(
756
+ {"enable_thinking", "truncate_history_thinking", "low_effort"}
757
+ )
654
758
 
655
759
  enable_thinking: bool = True
656
760
  """When ``True``, the generation prompt includes ``<think>``. Mirrors
@@ -693,6 +797,9 @@ class Nemotron3UltraRendererConfig(BaseRendererConfig):
693
797
  """
694
798
 
695
799
  name: Literal["nemotron-3-ultra"] = "nemotron-3-ultra"
800
+ _template_fields = frozenset(
801
+ {"enable_thinking", "truncate_history_thinking", "medium_effort"}
802
+ )
696
803
 
697
804
  enable_thinking: bool = True
698
805
  """See :class:`Nemotron3RendererConfig.enable_thinking`."""
@@ -728,6 +835,7 @@ class Nemotron35RendererConfig(BaseRendererConfig):
728
835
  """
729
836
 
730
837
  name: Literal["nemotron-3.5"] = "nemotron-3.5"
838
+ _template_fields = frozenset({"enable_thinking", "truncate_history_thinking"})
731
839
 
732
840
  enable_thinking: bool = True
733
841
  """See :class:`Nemotron3RendererConfig.enable_thinking`."""
@@ -755,6 +863,7 @@ class DeepSeekV3RendererConfig(BaseRendererConfig):
755
863
  """
756
864
 
757
865
  name: Literal["deepseek-v3"] = "deepseek-v3"
866
+ _template_fields = frozenset()
758
867
 
759
868
 
760
869
  class DeepSeekR1RendererConfig(BaseRendererConfig):
@@ -772,6 +881,7 @@ class DeepSeekR1RendererConfig(BaseRendererConfig):
772
881
  """
773
882
 
774
883
  name: Literal["deepseek-r1"] = "deepseek-r1"
884
+ _template_fields = frozenset()
775
885
 
776
886
 
777
887
  RendererConfig = Annotated[
@@ -789,6 +899,7 @@ RendererConfig = Annotated[
789
899
  GLM45RendererConfig,
790
900
  GptOssRendererConfig,
791
901
  Hy3RendererConfig,
902
+ InklingRendererConfig,
792
903
  KimiK2RendererConfig,
793
904
  KimiK25RendererConfig,
794
905
  LagunaXS2RendererConfig,
@@ -833,6 +944,7 @@ _CONFIG_BY_NAME: dict[str, type[BaseRendererConfig]] = {
833
944
  "glm-4.5": GLM45RendererConfig,
834
945
  "gpt-oss": GptOssRendererConfig,
835
946
  "hy3": Hy3RendererConfig,
947
+ "inkling": InklingRendererConfig,
836
948
  "kimi-k2": KimiK2RendererConfig,
837
949
  "kimi-k2.5": KimiK25RendererConfig,
838
950
  "laguna-xs.2": LagunaXS2RendererConfig,
@@ -885,6 +997,8 @@ __all__ = [
885
997
  "Gemma4RendererConfig",
886
998
  "GptOssRendererConfig",
887
999
  "Hy3RendererConfig",
1000
+ "INKLING_EFFORT_MAP",
1001
+ "InklingRendererConfig",
888
1002
  "KimiK25RendererConfig",
889
1003
  "KimiK2RendererConfig",
890
1004
  "LagunaM1RendererConfig",