renderers 0.1.12.dev19__tar.gz → 0.1.13.dev4__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 (97) hide show
  1. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/PKG-INFO +1 -1
  2. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/_version.py +2 -2
  3. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/base.py +3 -2
  4. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/client.py +21 -1
  5. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/deepseek_v4.py +13 -18
  6. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/glm45.py +5 -18
  7. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/glm5.py +4 -17
  8. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/parsing.py +183 -298
  9. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/reasoning.py +18 -7
  10. renderers-0.1.13.dev4/renderers/tagged.py +192 -0
  11. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/conftest.py +12 -0
  12. renderers-0.1.13.dev4/tests/reference_continuations.py +164 -0
  13. renderers-0.1.13.dev4/tests/replay.py +48 -0
  14. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_bridge.py +5 -0
  15. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_client.py +27 -0
  16. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_deepseek_r1.py +6 -0
  17. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_deepseek_v4.py +12 -7
  18. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_disabled_thinking_stability.py +5 -0
  19. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_gemma4.py +5 -0
  20. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_glm_tool_name_validation.py +5 -0
  21. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_hy3.py +5 -0
  22. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_inkling.py +5 -0
  23. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_laguna_m1.py +5 -0
  24. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_laguna_xs21.py +5 -0
  25. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_multimodal.py +5 -0
  26. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_parity.py +10 -2
  27. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_parse_response.py +11 -3
  28. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_parse_response_robustness.py +4 -5
  29. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_qwen38.py +5 -0
  30. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_reasoning_boundaries.py +26 -8
  31. renderers-0.1.13.dev4/tests/test_roundtrip.py +211 -0
  32. renderers-0.1.13.dev4/tests/test_tagged_rollout.py +258 -0
  33. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_tool_arg_type_preservation.py +5 -0
  34. renderers-0.1.12.dev19/tests/test_roundtrip.py +0 -323
  35. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/.github/workflows/publish-dev.yml +0 -0
  36. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/.github/workflows/publish.yml +0 -0
  37. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/.github/workflows/style.yml +0 -0
  38. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/.github/workflows/test.yml +0 -0
  39. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/.gitignore +0 -0
  40. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/.pre-commit-config.yaml +0 -0
  41. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/LICENSE +0 -0
  42. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/README.md +0 -0
  43. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/docs/renderer-config.md +0 -0
  44. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/examples/README.md +0 -0
  45. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/examples/sglang/multiturn_generate_sglang.py +0 -0
  46. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/examples/sglang/online_multiturn_sglang.py +0 -0
  47. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/examples/tinker/multiturn_generate_tinker.py +0 -0
  48. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/examples/transformers/multiturn_generate_transformers.py +0 -0
  49. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/examples/vllm/multiturn_generate_vllm.py +0 -0
  50. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/pyproject.toml +0 -0
  51. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/__init__.py +0 -0
  52. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/configs.py +0 -0
  53. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/custom.py +0 -0
  54. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/deepseek_r1.py +0 -0
  55. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/deepseek_v3.py +0 -0
  56. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/default.py +0 -0
  57. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/gemma4.py +0 -0
  58. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/gpt_oss.py +0 -0
  59. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/hy3.py +0 -0
  60. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/inkling.py +0 -0
  61. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/kimi_k2.py +0 -0
  62. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/kimi_k25.py +0 -0
  63. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/laguna_s21.py +0 -0
  64. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/laguna_xs2.py +0 -0
  65. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/llama_3.py +0 -0
  66. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/minimax_m2.py +0 -0
  67. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/nemotron3.py +0 -0
  68. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/parsers.py +0 -0
  69. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/prime_qwen3.py +0 -0
  70. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/qwen3.py +0 -0
  71. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/qwen35.py +0 -0
  72. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/qwen36.py +0 -0
  73. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/qwen38.py +0 -0
  74. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/renderers/qwen3_vl.py +0 -0
  75. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/audit_prefix_stability.py +0 -0
  76. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/fixtures/prefix_stability.json +0 -0
  77. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/parity.py +0 -0
  78. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/reference_rendering.py +0 -0
  79. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_build_helpers.py +0 -0
  80. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_custom.py +0 -0
  81. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_incremental.py +0 -0
  82. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_is_content.py +0 -0
  83. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_kimi_k25_tool_schema.py +0 -0
  84. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_llama_3.py +0 -0
  85. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_load_tokenizer.py +0 -0
  86. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_message_indices.py +0 -0
  87. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_message_tool_names.py +0 -0
  88. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_nemotron35_vlm.py +0 -0
  89. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_nemotron3_ultra.py +0 -0
  90. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_offsetless_tokenizers.py +0 -0
  91. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_parsers.py +0 -0
  92. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_preserve_thinking.py +0 -0
  93. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_qwen35_size_coverage.py +0 -0
  94. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_renderer_config.py +0 -0
  95. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_sampled_mask.py +0 -0
  96. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/tests/test_tokens_per_message.py +0 -0
  97. {renderers-0.1.12.dev19 → renderers-0.1.13.dev4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: renderers
3
- Version: 0.1.12.dev19
3
+ Version: 0.1.13.dev4
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
@@ -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.12.dev19'
22
- __version_tuple__ = version_tuple = (0, 1, 12, 'dev19')
21
+ __version__ = version = '0.1.13.dev4'
22
+ __version_tuple__ = version_tuple = (0, 1, 13, 'dev4')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -782,8 +782,9 @@ class Renderer(Protocol):
782
782
  Pass the exact ``prompt_ids`` used for sampling whenever available.
783
783
  This identifies reasoning prefilled by the prompt, including Gemma's
784
784
  post-tool thought channel and partial assistant prefixes. Omitting it,
785
- passing None, and passing [] all parse a self-contained completion;
786
- generation settings do not imply an initial reasoning channel.
785
+ passing None, and passing [] are equivalent: the format's completion
786
+ grammar applies without prompt context. GLM and DeepSeek V4 also use
787
+ the configured generation mode to determine the initial channel.
787
788
 
788
789
  ``tools`` is the same list passed to ``render`` for this turn.
789
790
  XML-style formats (Qwen3.5, GLM, MiniMax, Laguna) render argument
@@ -7,6 +7,7 @@ messages → Renderer.render_ids() → token IDs → POST /inference/v1/generate
7
7
  from __future__ import annotations
8
8
 
9
9
  import asyncio
10
+ import base64
10
11
  import json
11
12
  import logging
12
13
  import math
@@ -14,6 +15,7 @@ from collections.abc import Mapping
14
15
  from typing import Any, cast
15
16
 
16
17
  import httpx
18
+ import numpy as np
17
19
  from openai import AsyncOpenAI
18
20
 
19
21
  from renderers.base import (
@@ -134,9 +136,26 @@ def parse_generate_response(raw: bytes) -> dict[str, Any]:
134
136
  return payload
135
137
 
136
138
 
139
+ def _parse_packed_completion_logprobs(
140
+ packed: Mapping[str, Any], completion_ids: list[int]
141
+ ) -> list[float]:
142
+ """prime-rl's packed form: base64 float32 ``{data, shape, dtype}``, one per token."""
143
+ values = np.frombuffer(base64.b64decode(packed["data"]), dtype=packed["dtype"])
144
+ valid = np.isfinite(values) & (values > VLLM_LOGPROB_SENTINEL)
145
+ if len(values) != len(completion_ids) or not valid.all():
146
+ raise MalformedGenerateResponseError(
147
+ f"Engine response choice.completion_logprobs: {len(values)} values for "
148
+ f"{len(completion_ids)} completion tokens, or a non-finite or missing (-9999) logprob."
149
+ )
150
+ return values.astype(np.float64).tolist()
151
+
152
+
137
153
  def _parse_completion_logprobs(
138
154
  choice: Mapping[str, Any], completion_ids: list[int]
139
155
  ) -> list[float]:
156
+ packed = choice.get("completion_logprobs")
157
+ if packed is not None:
158
+ return _parse_packed_completion_logprobs(packed, completion_ids)
140
159
  raw_logprobs = choice.get("logprobs")
141
160
  if not isinstance(raw_logprobs, Mapping):
142
161
  raise MalformedGenerateResponseError(
@@ -376,7 +395,8 @@ async def generate(
376
395
 
377
396
  routed_experts = choice.get("routed_experts")
378
397
  # vLLM's native kept-set sampling masks (``--return-sampling-mask``):
379
- # one list of surviving vocab ids per completion token.
398
+ # one list of surviving vocab ids per completion token, or (prime-rl's
399
+ # server) packed CSR ``{"ids", "counts"}`` base64 int32 arrays.
380
400
  sampling_mask = choice.get("sampling_mask")
381
401
 
382
402
  # /inference/v1/generate returns finish_reason in {"stop","length",...} —
@@ -22,8 +22,6 @@ import json
22
22
  from dataclasses import dataclass, field
23
23
  from typing import Any, Mapping
24
24
 
25
- from renderers.reasoning import scan_reasoning, prompt_ends_in_reasoning
26
-
27
25
  from renderers.base import (
28
26
  Message,
29
27
  ParsedResponse,
@@ -41,7 +39,7 @@ from renderers.base import (
41
39
  )
42
40
  from renderers.configs import DeepSeekV4RendererConfig
43
41
  from renderers.parsing import parse_deepseek_v4
44
-
42
+ from renderers.reasoning import prompt_ends_in_reasoning
45
43
 
46
44
  _BOS = "<|begin▁of▁sentence|>"
47
45
  _EOS = "<|end▁of▁sentence|>"
@@ -798,15 +796,18 @@ class DeepSeekV4Renderer:
798
796
  self._tokenizer,
799
797
  token_ids,
800
798
  stop_ids={self._eos},
801
- thinking_enabled=prompt_ends_in_reasoning(
802
- self._tokenizer,
803
- prompt_ids,
804
- stop_ids=set(self.get_stop_token_ids()),
805
- assistant_prefix="<|Assistant|>",
799
+ thinking_enabled=(
800
+ prompt_ends_in_reasoning(
801
+ self._tokenizer,
802
+ prompt_ids,
803
+ stop_ids=set(self.get_stop_token_ids()),
804
+ assistant_prefix="<|Assistant|>",
805
+ )
806
+ if prompt_ids
807
+ else self.config.enable_thinking
806
808
  ),
807
809
  think_end_id=self._think_end,
808
810
  think_start_id=self._think_start,
809
- dsml_id=self._dsml,
810
811
  )
811
812
 
812
813
  def get_stop_token_ids(self) -> list[int]:
@@ -827,16 +828,10 @@ class DeepSeekV4Renderer:
827
828
  ):
828
829
  return None
829
830
 
830
- boundary = scan_reasoning(
831
- self._tokenizer,
832
- previous_completion_ids,
833
- prompt_ids=previous_prompt_ids,
834
- stop_ids=set(self.get_stop_token_ids()),
835
- tool_start_id=self._dsml,
836
- tool_start_closes_reasoning=True,
837
- assistant_prefix="<|Assistant|>",
831
+ boundary = self.parse_response(
832
+ previous_completion_ids, prompt_ids=previous_prompt_ids, tools=tools
838
833
  )
839
- if boundary.is_open:
834
+ if boundary.reasoning_complete is False:
840
835
  if any(t in self.get_stop_token_ids() for t in previous_completion_ids):
841
836
  return None
842
837
  previous_completion_ids = [*previous_completion_ids, self._think_end]
@@ -13,14 +13,12 @@ from __future__ import annotations
13
13
  import json
14
14
  from typing import Any
15
15
 
16
- from renderers.reasoning import scan_reasoning, prompt_ends_in_reasoning
17
-
18
16
  from renderers.base import (
19
17
  Message,
20
18
  ParsedResponse,
21
19
  RenderedTokens,
22
- ToolSpec,
23
20
  Tokenizer,
21
+ ToolSpec,
24
22
  _content_mask_or_empty,
25
23
  attribute_text_segments,
26
24
  extract_message_tool_names,
@@ -296,12 +294,7 @@ class GLM45Renderer:
296
294
  arg_value_id=self._arg_value,
297
295
  arg_value_end_id=self._arg_value_end,
298
296
  tools=tools,
299
- prefilled_thinking=prompt_ends_in_reasoning(
300
- self._tokenizer,
301
- prompt_ids,
302
- stop_ids=set(self.get_stop_token_ids()),
303
- assistant_prefix="<|assistant|>",
304
- ),
297
+ thinking_enabled=self.config.enable_thinking,
305
298
  )
306
299
 
307
300
  def get_stop_token_ids(self) -> list[int]:
@@ -322,16 +315,10 @@ class GLM45Renderer:
322
315
  ):
323
316
  return None
324
317
 
325
- boundary = scan_reasoning(
326
- self._tokenizer,
327
- previous_completion_ids,
328
- prompt_ids=previous_prompt_ids,
329
- stop_ids=set(self.get_stop_token_ids()),
330
- tool_start_id=self._tool_call_tok,
331
- tool_start_closes_reasoning=True,
332
- assistant_prefix="<|assistant|>",
318
+ boundary = self.parse_response(
319
+ previous_completion_ids, prompt_ids=previous_prompt_ids, tools=tools
333
320
  )
334
- if boundary.is_open:
321
+ if boundary.reasoning_complete is False:
335
322
  if any(t in self.get_stop_token_ids() for t in previous_completion_ids):
336
323
  return None
337
324
  previous_completion_ids = [*previous_completion_ids, self._think_end]
@@ -14,8 +14,6 @@ from __future__ import annotations
14
14
  import json
15
15
  from typing import Any
16
16
 
17
- from renderers.reasoning import scan_reasoning, prompt_ends_in_reasoning
18
-
19
17
  from renderers.base import (
20
18
  Message,
21
19
  ParsedResponse,
@@ -351,12 +349,7 @@ class GLM5Renderer:
351
349
  arg_value_id=self._arg_value,
352
350
  arg_value_end_id=self._arg_value_end,
353
351
  tools=tools,
354
- prefilled_thinking=prompt_ends_in_reasoning(
355
- self._tokenizer,
356
- prompt_ids,
357
- stop_ids=set(self.get_stop_token_ids()),
358
- assistant_prefix="<|assistant|>",
359
- ),
352
+ thinking_enabled=getattr(self.config, "enable_thinking", True),
360
353
  )
361
354
 
362
355
  def get_stop_token_ids(self) -> list[int]:
@@ -377,16 +370,10 @@ class GLM5Renderer:
377
370
  ):
378
371
  return None
379
372
 
380
- boundary = scan_reasoning(
381
- self._tokenizer,
382
- previous_completion_ids,
383
- prompt_ids=previous_prompt_ids,
384
- stop_ids=set(self.get_stop_token_ids()),
385
- tool_start_id=self._tool_call_tok,
386
- tool_start_closes_reasoning=True,
387
- assistant_prefix="<|assistant|>",
373
+ boundary = self.parse_response(
374
+ previous_completion_ids, prompt_ids=previous_prompt_ids, tools=tools
388
375
  )
389
- if boundary.is_open:
376
+ if boundary.reasoning_complete is False:
390
377
  if any(t in self.get_stop_token_ids() for t in previous_completion_ids):
391
378
  return None
392
379
  previous_completion_ids = [*previous_completion_ids, self._think_end]