renderers 0.1.8.dev56__tar.gz → 0.1.8.dev58__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 (74) hide show
  1. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/PKG-INFO +2 -2
  2. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/README.md +1 -1
  3. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/docs/renderer-config.md +2 -0
  4. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/__init__.py +4 -0
  5. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/_version.py +2 -2
  6. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/base.py +39 -2
  7. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/configs.py +9 -0
  8. renderers-0.1.8.dev58/renderers/prime_qwen3.py +694 -0
  9. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/conftest.py +1 -1
  10. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_bridge.py +1 -1
  11. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_build_helpers.py +28 -0
  12. renderers-0.1.8.dev58/tests/test_prime_qwen3_parity.py +196 -0
  13. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_roundtrip.py +1 -1
  14. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_tool_arg_type_preservation.py +1 -0
  15. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/.github/workflows/publish-dev.yml +0 -0
  16. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/.github/workflows/publish.yml +0 -0
  17. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/.github/workflows/style.yml +0 -0
  18. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/.github/workflows/test.yml +0 -0
  19. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/.gitignore +0 -0
  20. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/.pre-commit-config.yaml +0 -0
  21. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/LICENSE +0 -0
  22. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/examples/README.md +0 -0
  23. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/examples/sglang/multiturn_generate_sglang.py +0 -0
  24. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/examples/sglang/online_multiturn_sglang.py +0 -0
  25. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/examples/tinker/multiturn_generate_tinker.py +0 -0
  26. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/examples/transformers/multiturn_generate_transformers.py +0 -0
  27. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/examples/vllm/multiturn_generate_vllm.py +0 -0
  28. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/pyproject.toml +0 -0
  29. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/client.py +0 -0
  30. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/deepseek_r1.py +0 -0
  31. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/deepseek_v3.py +0 -0
  32. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/default.py +0 -0
  33. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/glm45.py +0 -0
  34. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/glm5.py +0 -0
  35. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/gpt_oss.py +0 -0
  36. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/hy3.py +0 -0
  37. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/kimi_k2.py +0 -0
  38. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/kimi_k25.py +0 -0
  39. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/laguna_xs2.py +0 -0
  40. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/llama_3.py +0 -0
  41. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/minimax_m2.py +0 -0
  42. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/nemotron3.py +0 -0
  43. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/parsers.py +0 -0
  44. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/parsing.py +0 -0
  45. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/qwen3.py +0 -0
  46. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/qwen35.py +0 -0
  47. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/qwen36.py +0 -0
  48. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/renderers/qwen3_vl.py +0 -0
  49. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_client.py +0 -0
  50. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_deepseek_r1.py +0 -0
  51. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_gpt_oss_harmony_parity.py +0 -0
  52. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_hy3.py +0 -0
  53. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_incremental.py +0 -0
  54. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_is_content.py +0 -0
  55. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_kimi_k25_tool_schema.py +0 -0
  56. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_laguna_xs21.py +0 -0
  57. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_llama_3.py +0 -0
  58. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_load_tokenizer.py +0 -0
  59. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_message_indices.py +0 -0
  60. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_message_tool_names.py +0 -0
  61. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_multimodal.py +0 -0
  62. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_nemotron3_parity.py +0 -0
  63. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_nemotron3_ultra.py +0 -0
  64. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_parse_response.py +0 -0
  65. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_parse_response_robustness.py +0 -0
  66. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_parsers.py +0 -0
  67. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_preserve_thinking.py +0 -0
  68. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_qwen35_size_coverage.py +0 -0
  69. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_render_ids.py +0 -0
  70. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_renderer_config.py +0 -0
  71. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_renderer_config_parity.py +0 -0
  72. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_sampled_mask.py +0 -0
  73. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/tests/test_tokens_per_message.py +0 -0
  74. {renderers-0.1.8.dev56 → renderers-0.1.8.dev58}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: renderers
3
- Version: 0.1.8.dev56
3
+ Version: 0.1.8.dev58
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`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `nemotron-3`, `nemotron-3-ultra`, `llama-3`, `gpt-oss`, and `hy3`. 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`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `nemotron-3`, `nemotron-3-ultra`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
60
60
 
61
61
  ## API
62
62
 
@@ -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`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `nemotron-3`, `nemotron-3-ultra`, `llama-3`, `gpt-oss`, and `hy3`. 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`, `glm-5`, `glm-5.1`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `nemotron-3`, `nemotron-3-ultra`, `llama-3`, `gpt-oss`, `hy3`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper.
44
44
 
45
45
  ## API
46
46
 
@@ -26,6 +26,7 @@ chat-template kwargs. Those fields are covered by parity tests against
26
26
  | Renderer | Config class | Template fields | Renderer-only fields |
27
27
  | --- | --- | --- | --- |
28
28
  | Qwen3 | `Qwen3RendererConfig` | `enable_thinking` | - |
29
+ | PrimeIntellect Qwen3 | `PrimeQwen3RendererConfig` | - | - |
29
30
  | Qwen3.5 | `Qwen35RendererConfig` | `enable_thinking`, `add_vision_id` | `image_cache_max` |
30
31
  | Qwen3.6 | `Qwen36RendererConfig` | `enable_thinking`, `add_vision_id`, `preserve_thinking` | `image_cache_max` |
31
32
  | Qwen3-VL | `Qwen3VLRendererConfig` | `add_vision_id` | `image_cache_max` |
@@ -136,6 +137,7 @@ the knobs its template actually exposes:
136
137
  | DeepSeek R1 | `template` |
137
138
  | MiniMax M2 | `tool_cycle` |
138
139
  | DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / XS-2.1, Llama 3 | `all` |
140
+ | PrimeIntellect Qwen3 | `all` |
139
141
 
140
142
  Config construction raises when an explicit template knob directly contradicts
141
143
  an explicit generic bridge policy. For example:
@@ -60,6 +60,7 @@ from renderers.configs import (
60
60
  MiniMaxM2RendererConfig,
61
61
  Nemotron3RendererConfig,
62
62
  Nemotron3UltraRendererConfig,
63
+ PrimeQwen3RendererConfig,
63
64
  Qwen35RendererConfig,
64
65
  Qwen36RendererConfig,
65
66
  Qwen3RendererConfig,
@@ -95,6 +96,7 @@ _LAZY_RENDERERS: dict[str, str] = {
95
96
  "MiniMaxM2Renderer": "renderers.minimax_m2",
96
97
  "Nemotron3Renderer": "renderers.nemotron3",
97
98
  "Nemotron3UltraRenderer": "renderers.nemotron3",
99
+ "PrimeQwen3Renderer": "renderers.prime_qwen3",
98
100
  "Qwen35Renderer": "renderers.qwen35",
99
101
  "Qwen36Renderer": "renderers.qwen36",
100
102
  "Qwen3Renderer": "renderers.qwen3",
@@ -163,6 +165,8 @@ __all__ = [
163
165
  "ParsedResponse",
164
166
  "ParsedToolCall",
165
167
  "PlaceholderRange",
168
+ "PrimeQwen3Renderer",
169
+ "PrimeQwen3RendererConfig",
166
170
  "Qwen35Renderer",
167
171
  "Qwen35RendererConfig",
168
172
  "Qwen36Renderer",
@@ -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.8.dev56'
22
- __version_tuple__ = version_tuple = (0, 1, 8, 'dev56')
21
+ __version__ = version = '0.1.8.dev58'
22
+ __version_tuple__ = version_tuple = (0, 1, 8, 'dev58')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -999,6 +999,10 @@ MODEL_RENDERER_MAP: dict[str, str] = {
999
999
  "Qwen/Qwen3-30B-A3B-Instruct-2507": "qwen3",
1000
1000
  "Qwen/Qwen3-30B-A3B-Thinking-2507": "qwen3",
1001
1001
  "Qwen/Qwen3-235B-A22B": "qwen3",
1002
+ # PrimeIntellect Qwen3 — both sizes share the same Qwen3-Coder-style
1003
+ # template with XML tool definitions and calls.
1004
+ "PrimeIntellect/Qwen3-0.6B": "prime-qwen3",
1005
+ "PrimeIntellect/Qwen3-1.7B": "prime-qwen3",
1002
1006
  # Qwen3.5. All seven sizes share the same renderer. The 4B / 9B /
1003
1007
  # 35B-A3B / 122B-A10B / 397B-A17B chat template defaults
1004
1008
  # ``enable_thinking=true`` (open ``<think>\n`` at the gen prompt);
@@ -1315,6 +1319,7 @@ def _populate_registry():
1315
1319
  from renderers.llama_3 import Llama3Renderer
1316
1320
  from renderers.minimax_m2 import MiniMaxM2Renderer
1317
1321
  from renderers.nemotron3 import Nemotron3Renderer, Nemotron3UltraRenderer
1322
+ from renderers.prime_qwen3 import PrimeQwen3Renderer
1318
1323
  from renderers.qwen3 import Qwen3Renderer
1319
1324
  from renderers.qwen3_vl import Qwen3VLRenderer
1320
1325
  from renderers.qwen35 import Qwen35Renderer
@@ -1324,6 +1329,7 @@ def _populate_registry():
1324
1329
  {
1325
1330
  "default": DefaultRenderer,
1326
1331
  "qwen3": Qwen3Renderer,
1332
+ "prime-qwen3": PrimeQwen3Renderer,
1327
1333
  "qwen3-vl": Qwen3VLRenderer,
1328
1334
  "qwen3.5": Qwen35Renderer,
1329
1335
  "qwen3.6": Qwen36Renderer,
@@ -1588,6 +1594,7 @@ def build_training_sample(
1588
1594
  role_to_mask: Callable[[Message], bool] | None = None,
1589
1595
  tools: list[ToolSpec] | None = None,
1590
1596
  content_sft_roles: "set[str] | frozenset[str] | None" = None,
1597
+ ensure_final_stop: bool = False,
1591
1598
  ) -> RenderedTrainingSample:
1592
1599
  """Build a :class:`RenderedTrainingSample` for supervised training.
1593
1600
 
@@ -1643,6 +1650,17 @@ def build_training_sample(
1643
1650
  or hand-coded renderers that haven't been wired up yet) ignore
1644
1651
  ``content_sft_roles`` silently — falling back to the original
1645
1652
  ``role_to_mask`` + ``sampled_mask`` behaviour.
1653
+
1654
+ ``ensure_final_stop`` appends the renderer's canonical stop token
1655
+ when the sample ends with an assistant message that the template
1656
+ leaves unterminated. Some templates close an assistant turn only
1657
+ via the *next* message's role marker (e.g. GLM's ``<|user|>`` /
1658
+ ``<|observation|>``), so a final assistant message renders with no
1659
+ stop token at all. No-op when the template already closes the turn
1660
+ in-message (ChatML ``<|im_end|>``, Llama ``<|eot_id|>``); where it
1661
+ fires, the output intentionally diverges from ``apply_chat_template``.
1662
+ Ignored for renderers without ``sampled_mask`` (``DefaultRenderer``) —
1663
+ the close of an opaque template can't be located reliably.
1646
1664
  """
1647
1665
  rendered = renderer.render(messages, tools=tools)
1648
1666
  has_sampled_info = len(rendered.sampled_mask) == len(rendered.token_ids)
@@ -1683,6 +1701,25 @@ def build_training_sample(
1683
1701
  else:
1684
1702
  loss_mask.append(role_to_mask(msg))
1685
1703
 
1704
+ token_ids = list(rendered.token_ids)
1705
+ # Requires sampled_mask (opaque templates hide the assistant close)
1706
+ # and a final assistant message the role filter trains.
1707
+ if (
1708
+ ensure_final_stop
1709
+ and has_sampled_info
1710
+ and messages[-1].get("role") == "assistant"
1711
+ and (role_to_mask is None or role_to_mask(messages[-1]))
1712
+ ):
1713
+ stop_ids = set(renderer.get_stop_token_ids())
1714
+ last_trainable = next(
1715
+ (k for k in range(len(loss_mask) - 1, -1, -1) if loss_mask[k]), None
1716
+ )
1717
+ if last_trainable is None or token_ids[last_trainable] not in stop_ids:
1718
+ token_ids.append(renderer.get_stop_token_ids()[0])
1719
+ # loss_mask=True marks the token as trainable — the appended
1720
+ # stop is a training target, like any sampled token.
1721
+ loss_mask.append(True)
1722
+
1686
1723
  # Surface the multimodal payload for VLM renderers. ``None`` for text
1687
1724
  # renderers and for text-only samples (empty media) so downstream
1688
1725
  # ``multi_modal_data is not None`` is a reliable "has media" check.
@@ -1690,12 +1727,12 @@ def build_training_sample(
1690
1727
  if mm is not None and mm.is_empty():
1691
1728
  mm = None
1692
1729
  mm_token_type_ids = (
1693
- _build_mm_token_type_ids(mm.mm_placeholders, len(rendered.token_ids))
1730
+ _build_mm_token_type_ids(mm.mm_placeholders, len(token_ids))
1694
1731
  if mm is not None and mm.mm_placeholders
1695
1732
  else None
1696
1733
  )
1697
1734
  return RenderedTrainingSample(
1698
- token_ids=rendered.token_ids,
1735
+ token_ids=token_ids,
1699
1736
  loss_mask=loss_mask,
1700
1737
  multi_modal_data=mm,
1701
1738
  mm_token_type_ids=mm_token_type_ids,
@@ -181,6 +181,12 @@ class Qwen3RendererConfig(BaseRendererConfig):
181
181
  ``enable_thinking`` kwarg."""
182
182
 
183
183
 
184
+ class PrimeQwen3RendererConfig(BaseRendererConfig):
185
+ """PrimeIntellect Qwen3 renderer config."""
186
+
187
+ name: Literal["prime-qwen3"] = "prime-qwen3"
188
+
189
+
184
190
  class Qwen35RendererConfig(BaseRendererConfig):
185
191
  """Qwen3.5 renderer config."""
186
192
 
@@ -649,6 +655,7 @@ RendererConfig = Annotated[
649
655
  AutoRendererConfig,
650
656
  DefaultRendererConfig,
651
657
  Qwen3RendererConfig,
658
+ PrimeQwen3RendererConfig,
652
659
  Qwen35RendererConfig,
653
660
  Qwen36RendererConfig,
654
661
  Qwen3VLRendererConfig,
@@ -688,6 +695,7 @@ _CONFIG_BY_NAME: dict[str, type[BaseRendererConfig]] = {
688
695
  "auto": AutoRendererConfig,
689
696
  "default": DefaultRendererConfig,
690
697
  "qwen3": Qwen3RendererConfig,
698
+ "prime-qwen3": PrimeQwen3RendererConfig,
691
699
  "qwen3.5": Qwen35RendererConfig,
692
700
  "qwen3.6": Qwen36RendererConfig,
693
701
  "qwen3-vl": Qwen3VLRendererConfig,
@@ -752,6 +760,7 @@ __all__ = [
752
760
  "MiniMaxM2RendererConfig",
753
761
  "Nemotron3RendererConfig",
754
762
  "Nemotron3UltraRendererConfig",
763
+ "PrimeQwen3RendererConfig",
755
764
  "Qwen35RendererConfig",
756
765
  "Qwen36RendererConfig",
757
766
  "Qwen3RendererConfig",