renderers 0.1.8.dev40__tar.gz → 0.1.8.dev42__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 (68) hide show
  1. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/PKG-INFO +1 -1
  2. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/__init__.py +4 -0
  3. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/_version.py +2 -2
  4. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/base.py +10 -0
  5. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/configs.py +28 -0
  6. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/kimi_k25.py +15 -3
  7. renderers-0.1.8.dev42/renderers/llama_3.py +516 -0
  8. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/parsing.py +134 -3
  9. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/qwen3.py +2 -0
  10. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/qwen3_vl.py +2 -0
  11. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/conftest.py +32 -0
  12. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_bridge.py +1 -0
  13. renderers-0.1.8.dev42/tests/test_llama_3.py +407 -0
  14. renderers-0.1.8.dev42/tests/test_parse_response.py +276 -0
  15. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_preserve_thinking.py +6 -0
  16. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_roundtrip.py +7 -0
  17. renderers-0.1.8.dev40/tests/test_parse_response.py +0 -137
  18. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/.github/workflows/publish-dev.yml +0 -0
  19. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/.github/workflows/publish.yml +0 -0
  20. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/.github/workflows/style.yml +0 -0
  21. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/.github/workflows/test.yml +0 -0
  22. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/.gitignore +0 -0
  23. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/.pre-commit-config.yaml +0 -0
  24. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/LICENSE +0 -0
  25. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/README.md +0 -0
  26. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/docs/renderer-config.md +0 -0
  27. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/examples/README.md +0 -0
  28. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/examples/sglang/multiturn_generate_sglang.py +0 -0
  29. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/examples/sglang/online_multiturn_sglang.py +0 -0
  30. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/examples/tinker/multiturn_generate_tinker.py +0 -0
  31. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/examples/transformers/multiturn_generate_transformers.py +0 -0
  32. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/examples/vllm/multiturn_generate_vllm.py +0 -0
  33. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/pyproject.toml +0 -0
  34. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/client.py +0 -0
  35. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/deepseek_v3.py +0 -0
  36. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/default.py +0 -0
  37. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/glm45.py +0 -0
  38. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/glm5.py +0 -0
  39. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/gpt_oss.py +0 -0
  40. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/kimi_k2.py +0 -0
  41. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/laguna_xs2.py +0 -0
  42. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/minimax_m2.py +0 -0
  43. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/nemotron3.py +0 -0
  44. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/parsers.py +0 -0
  45. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/qwen35.py +0 -0
  46. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/renderers/qwen36.py +0 -0
  47. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_build_helpers.py +0 -0
  48. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_client.py +0 -0
  49. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_gpt_oss_harmony_parity.py +0 -0
  50. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_incremental.py +0 -0
  51. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_is_content.py +0 -0
  52. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_kimi_k25_tool_schema.py +0 -0
  53. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_load_tokenizer.py +0 -0
  54. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_load_tokenizer_fastokens.py +0 -0
  55. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_message_indices.py +0 -0
  56. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_message_tool_names.py +0 -0
  57. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_multimodal.py +0 -0
  58. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_nemotron3_ultra.py +0 -0
  59. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_parse_response_robustness.py +0 -0
  60. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_parsers.py +0 -0
  61. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_qwen35_size_coverage.py +0 -0
  62. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_render_ids.py +0 -0
  63. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_renderer_config.py +0 -0
  64. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_renderer_config_parity.py +0 -0
  65. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_sampled_mask.py +0 -0
  66. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_tokens_per_message.py +0 -0
  67. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/tests/test_tool_arg_type_preservation.py +0 -0
  68. {renderers-0.1.8.dev40 → renderers-0.1.8.dev42}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: renderers
3
- Version: 0.1.8.dev40
3
+ Version: 0.1.8.dev42
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
@@ -52,6 +52,7 @@ from renderers.configs import (
52
52
  KimiK25RendererConfig,
53
53
  KimiK2RendererConfig,
54
54
  LagunaXS2RendererConfig,
55
+ Llama3RendererConfig,
55
56
  MiniMaxM2RendererConfig,
56
57
  Nemotron3RendererConfig,
57
58
  Qwen35RendererConfig,
@@ -82,6 +83,7 @@ _LAZY_RENDERERS: dict[str, str] = {
82
83
  "KimiK25Renderer": "renderers.kimi_k25",
83
84
  "KimiK2Renderer": "renderers.kimi_k2",
84
85
  "LagunaXS2Renderer": "renderers.laguna_xs2",
86
+ "Llama3Renderer": "renderers.llama_3",
85
87
  "MiniMaxM2Renderer": "renderers.minimax_m2",
86
88
  "Nemotron3Renderer": "renderers.nemotron3",
87
89
  "Qwen35Renderer": "renderers.qwen35",
@@ -130,6 +132,8 @@ __all__ = [
130
132
  "KimiK2RendererConfig",
131
133
  "LagunaXS2Renderer",
132
134
  "LagunaXS2RendererConfig",
135
+ "Llama3Renderer",
136
+ "Llama3RendererConfig",
133
137
  "MULTIMODAL_MODELS",
134
138
  "Message",
135
139
  "MiniMaxM2Renderer",
@@ -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.dev40'
22
- __version_tuple__ = version_tuple = (0, 1, 8, 'dev40')
21
+ __version__ = version = '0.1.8.dev42'
22
+ __version_tuple__ = version_tuple = (0, 1, 8, 'dev42')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -1045,6 +1045,14 @@ MODEL_RENDERER_MAP: dict[str, str] = {
1045
1045
  "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16": "nemotron-3",
1046
1046
  "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16": "nemotron-3",
1047
1047
  "nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-FP8": "nemotron-3",
1048
+ # Llama 3.2 (Instruct). Tested against the gated meta-llama repos and
1049
+ # the unrestricted unsloth/... mirror, which ships a byte-identical
1050
+ # chat template. ``Llama3Renderer`` defaults ``date_string`` to
1051
+ # "26 Jul 2024" — matching the chat template's strftime fallback —
1052
+ # so the renderer is reproducible. Pass ``date_string=...`` at
1053
+ # construction to pin a different date.
1054
+ "meta-llama/Llama-3.2-1B-Instruct": "llama-3",
1055
+ "meta-llama/Llama-3.2-3B-Instruct": "llama-3",
1048
1056
  # Poolside Laguna.
1049
1057
  "poolside/Laguna-XS.2": "laguna-xs.2",
1050
1058
  # GPT-OSS.
@@ -1334,6 +1342,7 @@ def _populate_registry():
1334
1342
  from renderers.kimi_k2 import KimiK2Renderer
1335
1343
  from renderers.kimi_k25 import KimiK25Renderer
1336
1344
  from renderers.laguna_xs2 import LagunaXS2Renderer
1345
+ from renderers.llama_3 import Llama3Renderer
1337
1346
  from renderers.minimax_m2 import MiniMaxM2Renderer
1338
1347
  from renderers.nemotron3 import Nemotron3Renderer
1339
1348
  from renderers.qwen3 import Qwen3Renderer
@@ -1356,6 +1365,7 @@ def _populate_registry():
1356
1365
  "kimi-k2": KimiK2Renderer,
1357
1366
  "kimi-k2.5": KimiK25Renderer,
1358
1367
  "laguna-xs.2": LagunaXS2Renderer,
1368
+ "llama-3": Llama3Renderer,
1359
1369
  "nemotron-3": Nemotron3Renderer,
1360
1370
  "gpt-oss": GptOssRenderer,
1361
1371
  }
@@ -318,6 +318,31 @@ class LagunaXS2RendererConfig(BaseRendererConfig):
318
318
  chat template's ``render_assistant_messages_raw`` gate."""
319
319
 
320
320
 
321
+ class Llama3RendererConfig(BaseRendererConfig):
322
+ """Llama-3.x Instruct renderer config.
323
+
324
+ Llama-3 ships no reasoning channel, so the base ``preserve_*_thinking``
325
+ flags don't apply: ``Llama3Renderer`` raises ``NotImplementedError``
326
+ if either is set (matching ``DefaultRenderer``'s contract for the
327
+ same case). Both fields below mirror real ``apply_chat_template``
328
+ kwargs.
329
+ """
330
+
331
+ name: Literal["llama-3"] = "llama-3"
332
+
333
+ date_string: str = "26 Jul 2024"
334
+ """``Today Date`` value injected into the system preamble. Pinned to
335
+ the chat template's ``strftime`` fallback by default so output stays
336
+ deterministic; override per instance for production runs that want
337
+ today's date. Mirrors the chat template's ``date_string`` kwarg."""
338
+
339
+ tools_in_user_message: bool = True
340
+ """When ``True`` (default), tool descriptions + JSON signatures inject
341
+ into the first user message; ``False`` routes them into the system
342
+ block instead. Mirrors the chat template's ``tools_in_user_message``
343
+ kwarg."""
344
+
345
+
321
346
  class MiniMaxM2RendererConfig(BaseRendererConfig):
322
347
  """MiniMax M2 / M2.5 renderer config."""
323
348
 
@@ -410,6 +435,7 @@ RendererConfig = Annotated[
410
435
  KimiK2RendererConfig,
411
436
  KimiK25RendererConfig,
412
437
  LagunaXS2RendererConfig,
438
+ Llama3RendererConfig,
413
439
  MiniMaxM2RendererConfig,
414
440
  Nemotron3RendererConfig,
415
441
  DeepSeekV3RendererConfig,
@@ -444,6 +470,7 @@ _CONFIG_BY_NAME: dict[str, type[BaseRendererConfig]] = {
444
470
  "kimi-k2": KimiK2RendererConfig,
445
471
  "kimi-k2.5": KimiK25RendererConfig,
446
472
  "laguna-xs.2": LagunaXS2RendererConfig,
473
+ "llama-3": Llama3RendererConfig,
447
474
  "minimax-m2": MiniMaxM2RendererConfig,
448
475
  "nemotron-3": Nemotron3RendererConfig,
449
476
  "deepseek-v3": DeepSeekV3RendererConfig,
@@ -486,6 +513,7 @@ __all__ = [
486
513
  "KimiK25RendererConfig",
487
514
  "KimiK2RendererConfig",
488
515
  "LagunaXS2RendererConfig",
516
+ "Llama3RendererConfig",
489
517
  "MiniMaxM2RendererConfig",
490
518
  "Nemotron3RendererConfig",
491
519
  "Qwen35RendererConfig",
@@ -42,7 +42,7 @@ from renderers.base import (
42
42
  trim_to_turn_close,
43
43
  )
44
44
  from renderers.configs import KimiK25RendererConfig
45
- from renderers.parsing import parse_kimi_k2_section
45
+ from renderers.parsing import _reasoning_end_token_index, parse_kimi_k2_section
46
46
  from renderers.qwen3_vl import (
47
47
  _image_hash,
48
48
  _is_image_part,
@@ -452,6 +452,13 @@ def _parse_kimi_k2_response(
452
452
  ids = ids[:i]
453
453
  break
454
454
 
455
+ # Reasoning first: a tool-call section the model drafts *inside* its
456
+ # <think> trace must not be parsed as a real call (regression #78 — cf.
457
+ # parse_qwen3). K2.5 renders </think> as text, so locate the boundary by
458
+ # decoding; the section scan then starts past it. content_ids still begins
459
+ # at 0, so the </think> text-split below recovers reasoning unchanged.
460
+ reasoning_end = _reasoning_end_token_index(tokenizer, ids)
461
+
455
462
  # Token-ID path — produces spans. Only run if every relevant special
456
463
  # token resolved at init (i.e. is in the tokenizer's vocab).
457
464
  tool_calls: list[ParsedToolCall] = []
@@ -471,6 +478,7 @@ def _parse_kimi_k2_response(
471
478
  tool_call_begin_id=tool_call_begin_id,
472
479
  tool_call_argument_begin_id=tool_call_argument_begin_id,
473
480
  tool_call_end_id=tool_call_end_id,
481
+ scan_start=reasoning_end,
474
482
  )
475
483
  text = (
476
484
  tokenizer.decode(content_ids, skip_special_tokens=False)
@@ -481,9 +489,13 @@ def _parse_kimi_k2_response(
481
489
  text = tokenizer.decode(ids, skip_special_tokens=False) if ids else ""
482
490
 
483
491
  # Fallback path: model emitted literal-text section delimiters (singular
484
- # variant) rather than special tokens. Spans unavailable here.
492
+ # variant) rather than special tokens. Spans unavailable here. Start the
493
+ # search past the first </think> so a literal section drafted inside the
494
+ # reasoning trace isn't matched as a real call (regression #78).
485
495
  if not tool_calls:
486
- tc_match = _TOOL_CALLS_SECTION_RE.search(text)
496
+ think_close = text.find("</think>")
497
+ search_from = think_close + len("</think>") if think_close != -1 else 0
498
+ tc_match = _TOOL_CALLS_SECTION_RE.search(text, search_from)
487
499
  if tc_match:
488
500
  text = text[: tc_match.start()]
489
501
  tool_section = (