renderers 0.1.12.dev12__tar.gz → 0.1.12.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.
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/PKG-INFO +1 -1
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/_version.py +2 -2
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/qwen35.py +10 -5
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/qwen38.py +31 -5
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_qwen38.py +74 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/.github/workflows/style.yml +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/.github/workflows/test.yml +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/.gitignore +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/LICENSE +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/README.md +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/docs/renderer-config.md +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/README.md +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/pyproject.toml +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/__init__.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/base.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/client.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/configs.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/custom.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/deepseek_v4.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/default.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/gemma4.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/glm45.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/glm5.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/hy3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/inkling.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/laguna_s21.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/laguna_xs2.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/llama_3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/nemotron3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/parsers.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/parsing.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/prime_qwen3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/qwen3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/qwen36.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/renderers/reasoning.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/conftest.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/parity.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/reference_rendering.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_bridge.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_build_helpers.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_client.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_custom.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_deepseek_v4.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_disabled_thinking_stability.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_gemma4.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_glm_tool_name_validation.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_hy3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_incremental.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_inkling.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_is_content.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_laguna_m1.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_llama_3.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_message_indices.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_multimodal.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_nemotron35_vlm.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_nemotron3_ultra.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_offsetless_tokenizers.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_parity.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_parse_response.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_parsers.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_reasoning_boundaries.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_roundtrip.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/tests/test_tool_arg_type_preservation.py +0 -0
- {renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/uv.lock +0 -0
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 12, '
|
|
21
|
+
__version__ = version = '0.1.12.dev13'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 12, 'dev13')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -23,6 +23,7 @@ path and remain byte-identical to ``apply_chat_template``.
|
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
|
|
25
25
|
import json
|
|
26
|
+
from collections.abc import Iterable
|
|
26
27
|
from typing import Any
|
|
27
28
|
|
|
28
29
|
from renderers.reasoning import scan_reasoning, prompt_ends_in_reasoning
|
|
@@ -252,7 +253,11 @@ class Qwen35Renderer:
|
|
|
252
253
|
# ------------------------------------------------------------------
|
|
253
254
|
|
|
254
255
|
@staticmethod
|
|
255
|
-
def
|
|
256
|
+
def _iter_content_parts(content: list[Any]) -> Iterable[Any]:
|
|
257
|
+
return content
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def _render_content(cls, content: Any) -> str:
|
|
256
261
|
"""Render message content to a text string (before tokenization).
|
|
257
262
|
|
|
258
263
|
Handles string, list of text parts, and None. Image / video parts
|
|
@@ -268,7 +273,7 @@ class Qwen35Renderer:
|
|
|
268
273
|
return content
|
|
269
274
|
if isinstance(content, list):
|
|
270
275
|
parts: list[str] = []
|
|
271
|
-
for item in content:
|
|
276
|
+
for item in cls._iter_content_parts(content):
|
|
272
277
|
if isinstance(item, str):
|
|
273
278
|
parts.append(item)
|
|
274
279
|
elif isinstance(item, dict):
|
|
@@ -489,7 +494,7 @@ class Qwen35Renderer:
|
|
|
489
494
|
emit_text_segments(buf_segments, msg_idx, is_sampled=False)
|
|
490
495
|
buf_segments.clear()
|
|
491
496
|
|
|
492
|
-
for item in content_list:
|
|
497
|
+
for item in self._iter_content_parts(content_list):
|
|
493
498
|
if isinstance(item, str):
|
|
494
499
|
if item:
|
|
495
500
|
buf_segments.append((item, True))
|
|
@@ -864,7 +869,7 @@ class Qwen35Renderer:
|
|
|
864
869
|
emit_text_segments(buf_segments, msg_idx)
|
|
865
870
|
buf_segments.clear()
|
|
866
871
|
|
|
867
|
-
for item in content_list:
|
|
872
|
+
for item in self._iter_content_parts(content_list):
|
|
868
873
|
if isinstance(item, str):
|
|
869
874
|
if item:
|
|
870
875
|
buf_segments.append((item, True))
|
|
@@ -1192,7 +1197,7 @@ class Qwen35Renderer:
|
|
|
1192
1197
|
emit_text_segments(buf_segments, msg_idx, is_sampled=False)
|
|
1193
1198
|
buf_segments.clear()
|
|
1194
1199
|
|
|
1195
|
-
for item in raw_content:
|
|
1200
|
+
for item in self._iter_content_parts(raw_content):
|
|
1196
1201
|
if isinstance(item, str):
|
|
1197
1202
|
if item:
|
|
1198
1203
|
buf_segments.append((item, True))
|
|
@@ -14,10 +14,13 @@ argument serialization, with three template changes:
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
from collections.abc import Iterable
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
17
20
|
from renderers.base import Message
|
|
18
21
|
from renderers.configs import Qwen38RendererConfig
|
|
19
|
-
from renderers.qwen35 import Qwen35Renderer
|
|
20
22
|
from renderers.qwen36 import Qwen36Renderer
|
|
23
|
+
from renderers.qwen3_vl import _is_image_part, _is_video_part
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
_REASONING_INSTRUCTIONS = {
|
|
@@ -48,12 +51,35 @@ class Qwen38Renderer(Qwen36Renderer):
|
|
|
48
51
|
def _omit_empty_system_message(self) -> bool:
|
|
49
52
|
return True
|
|
50
53
|
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _iter_content_parts(content: list[Any]) -> Iterable[Any]:
|
|
56
|
+
"""Keep media and string-valued text in every rendering path."""
|
|
57
|
+
for item in content:
|
|
58
|
+
if isinstance(item, str):
|
|
59
|
+
yield item
|
|
60
|
+
elif isinstance(item, dict) and (
|
|
61
|
+
_is_image_part(item)
|
|
62
|
+
or _is_video_part(item)
|
|
63
|
+
or isinstance(item.get("text"), str)
|
|
64
|
+
):
|
|
65
|
+
yield item
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _is_user_query_message(msg: Message) -> bool:
|
|
69
|
+
if msg.get("role") != "user":
|
|
70
|
+
return False
|
|
71
|
+
content = Qwen38Renderer._render_content(msg.get("content")).strip()
|
|
72
|
+
return not (
|
|
73
|
+
content.startswith("<tool_response>")
|
|
74
|
+
and content.endswith("</tool_response>")
|
|
75
|
+
)
|
|
76
|
+
|
|
51
77
|
@staticmethod
|
|
52
78
|
def _last_query_index(messages: list[Message]) -> int:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
for i in range(len(messages) - 1, -1, -1):
|
|
80
|
+
if Qwen38Renderer._is_user_query_message(messages[i]):
|
|
81
|
+
return i
|
|
82
|
+
raise ValueError("No user query found in messages.")
|
|
57
83
|
|
|
58
84
|
@staticmethod
|
|
59
85
|
def _extract_assistant_parts(msg: Message, content: str) -> tuple[str, str]:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from functools import lru_cache
|
|
6
|
+
from copy import deepcopy
|
|
6
7
|
|
|
7
8
|
import pytest
|
|
8
9
|
from pydantic import TypeAdapter
|
|
@@ -176,3 +177,76 @@ def test_qwen38_requires_a_real_user_query(qwen38_model):
|
|
|
176
177
|
|
|
177
178
|
with pytest.raises(ValueError, match="No user query found"):
|
|
178
179
|
renderer.render_ids([{"role": "system", "content": "System only."}])
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@pytest.mark.parametrize(
|
|
183
|
+
"role,with_image",
|
|
184
|
+
[
|
|
185
|
+
("system", False),
|
|
186
|
+
("user", False),
|
|
187
|
+
("assistant", False),
|
|
188
|
+
("tool", False),
|
|
189
|
+
("user", True),
|
|
190
|
+
("tool", True),
|
|
191
|
+
],
|
|
192
|
+
)
|
|
193
|
+
def test_qwen38_structured_content_render_and_bridge(role, with_image):
|
|
194
|
+
_, renderer = _qwen38(QWEN38_MODELS[0])
|
|
195
|
+
parts = ["Hello", {"type": "text", "text": " world"}]
|
|
196
|
+
if with_image:
|
|
197
|
+
parts.append({"type": "image", "image": "unused", "text": 123})
|
|
198
|
+
parts.append({"type": "text", "text": "After image"})
|
|
199
|
+
noisy_parts = [
|
|
200
|
+
{"type": "unsupported"},
|
|
201
|
+
*parts,
|
|
202
|
+
{"type": "text", "text": None},
|
|
203
|
+
{"type": "text", "text": 123},
|
|
204
|
+
None,
|
|
205
|
+
]
|
|
206
|
+
prior = [
|
|
207
|
+
{"role": "user", "content": "First question"},
|
|
208
|
+
{"role": "assistant", "content": "First answer"},
|
|
209
|
+
]
|
|
210
|
+
clean = {"role": role, "content": parts}
|
|
211
|
+
noisy = {"role": role, "content": noisy_parts}
|
|
212
|
+
original = deepcopy(noisy)
|
|
213
|
+
messages = [noisy, *prior] if role == "system" else [*prior, noisy]
|
|
214
|
+
expected_messages = [clean, *prior] if role == "system" else [*prior, clean]
|
|
215
|
+
assert renderer.render(messages, process_multimodal=False) == renderer.render(
|
|
216
|
+
expected_messages, process_multimodal=False
|
|
217
|
+
)
|
|
218
|
+
if role in {"user", "tool"}:
|
|
219
|
+
previous = renderer.render_ids(prior)
|
|
220
|
+
actual = renderer.bridge_to_next_turn(
|
|
221
|
+
previous, [], [noisy], process_multimodal=False
|
|
222
|
+
)
|
|
223
|
+
assert actual is not None
|
|
224
|
+
assert actual == renderer.bridge_to_next_turn(
|
|
225
|
+
previous, [], [clean], process_multimodal=False
|
|
226
|
+
)
|
|
227
|
+
assert noisy == original
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_qwen38_structured_tool_response_keeps_current_reasoning():
|
|
231
|
+
tokenizer, _ = _qwen38(QWEN38_MODELS[0])
|
|
232
|
+
renderer = Qwen38Renderer(tokenizer, Qwen38RendererConfig(preserve_thinking=False))
|
|
233
|
+
prior = [
|
|
234
|
+
{"role": "user", "content": "First question"},
|
|
235
|
+
{"role": "assistant", "reasoning_content": "Keep me", "content": ""},
|
|
236
|
+
]
|
|
237
|
+
content = [
|
|
238
|
+
{"type": "text", "text": " <tool_response>"},
|
|
239
|
+
{"type": "unsupported"},
|
|
240
|
+
{"type": "text", "text": None},
|
|
241
|
+
"result",
|
|
242
|
+
{"type": "text", "text": "</tool_response> "},
|
|
243
|
+
]
|
|
244
|
+
messages = [*prior, {"role": "user", "content": content}]
|
|
245
|
+
expected = [
|
|
246
|
+
*prior,
|
|
247
|
+
{"role": "user", "content": "<tool_response>result</tool_response>"},
|
|
248
|
+
]
|
|
249
|
+
assert renderer.render(messages) == renderer.render(expected)
|
|
250
|
+
assert "Keep me" in tokenizer.decode(renderer.render_ids(messages))
|
|
251
|
+
with pytest.raises(ValueError, match="No user query found"):
|
|
252
|
+
renderer.render([messages[-1]])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/sglang/multiturn_generate_sglang.py
RENAMED
|
File without changes
|
{renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/sglang/online_multiturn_sglang.py
RENAMED
|
File without changes
|
{renderers-0.1.12.dev12 → renderers-0.1.12.dev13}/examples/tinker/multiturn_generate_tinker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|