renderers 0.1.12.dev8__tar.gz → 0.1.12.dev9__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 (88) hide show
  1. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/PKG-INFO +1 -1
  2. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/_version.py +2 -2
  3. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/qwen3.py +18 -7
  4. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_bridge.py +18 -0
  5. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/.github/workflows/publish-dev.yml +0 -0
  6. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/.github/workflows/publish.yml +0 -0
  7. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/.github/workflows/style.yml +0 -0
  8. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/.github/workflows/test.yml +0 -0
  9. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/.gitignore +0 -0
  10. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/.pre-commit-config.yaml +0 -0
  11. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/LICENSE +0 -0
  12. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/README.md +0 -0
  13. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/docs/renderer-config.md +0 -0
  14. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/examples/README.md +0 -0
  15. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/examples/sglang/multiturn_generate_sglang.py +0 -0
  16. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/examples/sglang/online_multiturn_sglang.py +0 -0
  17. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/examples/tinker/multiturn_generate_tinker.py +0 -0
  18. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/examples/transformers/multiturn_generate_transformers.py +0 -0
  19. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/examples/vllm/multiturn_generate_vllm.py +0 -0
  20. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/pyproject.toml +0 -0
  21. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/__init__.py +0 -0
  22. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/base.py +0 -0
  23. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/client.py +0 -0
  24. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/configs.py +0 -0
  25. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/deepseek_r1.py +0 -0
  26. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/deepseek_v3.py +0 -0
  27. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/deepseek_v4.py +0 -0
  28. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/default.py +0 -0
  29. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/gemma4.py +0 -0
  30. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/glm45.py +0 -0
  31. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/glm5.py +0 -0
  32. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/gpt_oss.py +0 -0
  33. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/hy3.py +0 -0
  34. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/inkling.py +0 -0
  35. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/kimi_k2.py +0 -0
  36. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/kimi_k25.py +0 -0
  37. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/laguna_s21.py +0 -0
  38. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/laguna_xs2.py +0 -0
  39. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/llama_3.py +0 -0
  40. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/minimax_m2.py +0 -0
  41. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/nemotron3.py +0 -0
  42. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/parsers.py +0 -0
  43. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/parsing.py +0 -0
  44. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/prime_qwen3.py +0 -0
  45. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/qwen35.py +0 -0
  46. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/qwen36.py +0 -0
  47. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/qwen38.py +0 -0
  48. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/qwen3_vl.py +0 -0
  49. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/renderers/reasoning.py +0 -0
  50. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/conftest.py +0 -0
  51. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/parity.py +0 -0
  52. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/reference_rendering.py +0 -0
  53. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_build_helpers.py +0 -0
  54. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_client.py +0 -0
  55. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_deepseek_r1.py +0 -0
  56. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_deepseek_v4.py +0 -0
  57. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_disabled_thinking_stability.py +0 -0
  58. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_gemma4.py +0 -0
  59. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_glm_tool_name_validation.py +0 -0
  60. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_hy3.py +0 -0
  61. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_incremental.py +0 -0
  62. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_inkling.py +0 -0
  63. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_is_content.py +0 -0
  64. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_kimi_k25_tool_schema.py +0 -0
  65. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_laguna_m1.py +0 -0
  66. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_laguna_xs21.py +0 -0
  67. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_llama_3.py +0 -0
  68. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_load_tokenizer.py +0 -0
  69. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_message_indices.py +0 -0
  70. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_message_tool_names.py +0 -0
  71. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_multimodal.py +0 -0
  72. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_nemotron35_vlm.py +0 -0
  73. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_nemotron3_ultra.py +0 -0
  74. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_offsetless_tokenizers.py +0 -0
  75. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_parity.py +0 -0
  76. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_parse_response.py +0 -0
  77. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_parse_response_robustness.py +0 -0
  78. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_parsers.py +0 -0
  79. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_preserve_thinking.py +0 -0
  80. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_qwen35_size_coverage.py +0 -0
  81. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_qwen38.py +0 -0
  82. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_reasoning_boundaries.py +0 -0
  83. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_renderer_config.py +0 -0
  84. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_roundtrip.py +0 -0
  85. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_sampled_mask.py +0 -0
  86. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_tokens_per_message.py +0 -0
  87. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/tests/test_tool_arg_type_preservation.py +0 -0
  88. {renderers-0.1.12.dev8 → renderers-0.1.12.dev9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: renderers
3
- Version: 0.1.12.dev8
3
+ Version: 0.1.12.dev9
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.dev8'
22
- __version_tuple__ = version_tuple = (0, 1, 12, 'dev8')
21
+ __version__ = version = '0.1.12.dev9'
22
+ __version_tuple__ = version_tuple = (0, 1, 12, 'dev9')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -93,9 +93,16 @@ class Qwen3Renderer:
93
93
  return self._tokenizer.encode(text, add_special_tokens=False)
94
94
 
95
95
  @staticmethod
96
- def _query_boundary_text(content) -> str:
96
+ def _content_text(content) -> str:
97
+ """Return the text parts of a message in a renderer-safe form.
98
+
99
+ Qwen3's native template is text-only, but text-only messages can also
100
+ use OpenAI-style content lists: ``[{"type": "text", "text": ...}]``.
101
+ Treat both representations identically instead of silently dropping
102
+ the message body.
103
+ """
97
104
  if isinstance(content, str):
98
- return content.strip()
105
+ return content
99
106
  if isinstance(content, list):
100
107
  parts: list[str] = []
101
108
  for item in content:
@@ -103,9 +110,13 @@ class Qwen3Renderer:
103
110
  parts.append(item)
104
111
  elif isinstance(item, dict) and isinstance(item.get("text"), str):
105
112
  parts.append(item["text"])
106
- return "".join(parts).strip()
113
+ return "".join(parts)
107
114
  return ""
108
115
 
116
+ @staticmethod
117
+ def _query_boundary_text(content) -> str:
118
+ return Qwen3Renderer._content_text(content).strip()
119
+
109
120
  @staticmethod
110
121
  def _is_user_query_message(msg: Message) -> bool:
111
122
  if msg.get("role") != "user":
@@ -186,7 +197,7 @@ class Qwen3Renderer:
186
197
  # JSON as message body.
187
198
  segments: list[tuple[str, bool]] = [("system\n", False)]
188
199
  if first_is_system:
189
- sys_content = messages[0].get("content") or ""
200
+ sys_content = self._content_text(messages[0].get("content"))
190
201
  if sys_content:
191
202
  segments.append((sys_content, True))
192
203
  segments.append(("\n\n", False))
@@ -199,7 +210,7 @@ class Qwen3Renderer:
199
210
  emit_text("\n", sys_idx, is_sampled=False, is_content=False)
200
211
  elif first_is_system:
201
212
  emit_special(self._im_start, 0, is_sampled=False, is_content=False)
202
- sys_content = messages[0].get("content") or ""
213
+ sys_content = self._content_text(messages[0].get("content"))
203
214
  sys_segments: list[tuple[str, bool]] = [("system\n", False)]
204
215
  if sys_content:
205
216
  sys_segments.append((sys_content, True))
@@ -214,7 +225,7 @@ class Qwen3Renderer:
214
225
  num_messages = len(messages)
215
226
  for i, msg in enumerate(messages):
216
227
  role = msg["role"]
217
- content = msg.get("content") if isinstance(msg.get("content"), str) else ""
228
+ content = self._content_text(msg.get("content"))
218
229
 
219
230
  if role == "system":
220
231
  if i == 0:
@@ -415,7 +426,7 @@ class Qwen3Renderer:
415
426
 
416
427
  for i, msg in enumerate(new_messages):
417
428
  role = msg.get("role")
418
- content = msg.get("content") if isinstance(msg.get("content"), str) else ""
429
+ content = self._content_text(msg.get("content"))
419
430
  if role == "user":
420
431
  emit_special(self._im_start, i)
421
432
  user_segments: list[tuple[str, bool]] = [("user\n", False)]
@@ -205,6 +205,24 @@ def test_bridge_extension_includes_new_message_text(
205
205
  )
206
206
 
207
207
 
208
+ def test_qwen3_renders_openai_text_content_parts(br_renderer, br_tokenizer, br_model):
209
+ """Text content lists must not become an empty user turn."""
210
+ if br_model not in {"Qwen/Qwen3-8B", "Qwen/Qwen3-4B"}:
211
+ pytest.skip(f"{br_model}: not a Qwen3 text renderer")
212
+
213
+ text = "CONTENT_LIST_SENTINEL_XYZ"
214
+ string_ids = br_renderer.render_ids(
215
+ [{"role": "user", "content": text}], add_generation_prompt=True
216
+ )
217
+ list_ids = br_renderer.render_ids(
218
+ [{"role": "user", "content": [{"type": "text", "text": text}]}],
219
+ add_generation_prompt=True,
220
+ )
221
+
222
+ assert list_ids == string_ids
223
+ assert text in br_tokenizer.decode(list_ids, skip_special_tokens=False)
224
+
225
+
208
226
  def test_bridge_declines_across_user_query_when_template_drops_thinking():
209
227
  """Qwen3's template drops a past block's thinking once a new user turn
210
228
  arrives. The resolved ``tool_cycle`` bridge policy therefore treats a
File without changes
File without changes