renderers 0.1.9.dev9__tar.gz → 0.1.10.dev1__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.9.dev9 → renderers-0.1.10.dev1}/PKG-INFO +2 -2
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/README.md +1 -1
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/docs/renderer-config.md +2 -1
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/__init__.py +4 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/_version.py +2 -2
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/base.py +10 -3
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/client.py +12 -17
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/configs.py +25 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/laguna_xs2.py +74 -19
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/conftest.py +1 -0
- renderers-0.1.10.dev1/tests/test_laguna_m1.py +232 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_renderer_config_parity.py +2 -1
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_roundtrip.py +1 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/.github/workflows/style.yml +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/.github/workflows/test.yml +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/.gitignore +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/LICENSE +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/examples/README.md +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/pyproject.toml +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/default.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/glm45.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/glm5.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/hy3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/llama_3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/nemotron3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/parsers.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/parsing.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/prime_qwen3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/qwen3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/qwen35.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/qwen36.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_bridge.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_build_helpers.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_client.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_disabled_thinking_stability.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_glm_tool_name_validation.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_gpt_oss_harmony_parity.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_hy3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_incremental.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_is_content.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_llama_3.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_message_indices.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_multimodal.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_nemotron3_parity.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_nemotron3_ultra.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_parse_response.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_parsers.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_prime_qwen3_parity.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_render_ids.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/tests/test_tool_arg_type_preservation.py +0 -0
- {renderers-0.1.9.dev9 → renderers-0.1.10.dev1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: renderers
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10.dev1
|
|
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`, `hy3`, and `prime-qwen3`. 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`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-m.1`, `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`, `hy3`, and `prime-qwen3`. 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`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-m.1`, `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
|
|
|
@@ -37,6 +37,7 @@ chat-template kwargs. Those fields are covered by parity tests against
|
|
|
37
37
|
| Kimi K2 | `KimiK2RendererConfig` | - | `enable_thinking` |
|
|
38
38
|
| Kimi K2.5 / 2.6 | `KimiK25RendererConfig` | `thinking` | `image_cache_max` |
|
|
39
39
|
| Laguna XS.2 | `LagunaXS2RendererConfig` | `enable_thinking`, `render_assistant_messages_raw` | - |
|
|
40
|
+
| Laguna M.1 | `LagunaM1RendererConfig` | `enable_thinking`, `render_assistant_messages_raw` | - |
|
|
40
41
|
| Laguna XS-2.1 | `LagunaXS21RendererConfig` | `enable_thinking` | - |
|
|
41
42
|
| Llama 3 | `Llama3RendererConfig` | `date_string`, `tools_in_user_message` | - |
|
|
42
43
|
| MiniMax M2 | `MiniMaxM2RendererConfig` | `model_identity` | - |
|
|
@@ -136,7 +137,7 @@ the knobs its template actually exposes:
|
|
|
136
137
|
| Nemotron-3 | `truncate_history_thinking=False -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
137
138
|
| DeepSeek R1 | `template` |
|
|
138
139
|
| MiniMax M2 | `tool_cycle` |
|
|
139
|
-
| DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / XS-2.1, Llama 3 | `all` |
|
|
140
|
+
| DeepSeek V3, Qwen3-VL, Kimi K2, Laguna XS.2 / M.1 / XS-2.1, Llama 3 | `all` |
|
|
140
141
|
| PrimeIntellect Qwen3 | `all` |
|
|
141
142
|
|
|
142
143
|
Config construction raises when an explicit template knob directly contradicts
|
|
@@ -54,6 +54,7 @@ from renderers.configs import (
|
|
|
54
54
|
Hy3RendererConfig,
|
|
55
55
|
KimiK25RendererConfig,
|
|
56
56
|
KimiK2RendererConfig,
|
|
57
|
+
LagunaM1RendererConfig,
|
|
57
58
|
LagunaXS2RendererConfig,
|
|
58
59
|
LagunaXS21RendererConfig,
|
|
59
60
|
Llama3RendererConfig,
|
|
@@ -90,6 +91,7 @@ _LAZY_RENDERERS: dict[str, str] = {
|
|
|
90
91
|
"Hy3Renderer": "renderers.hy3",
|
|
91
92
|
"KimiK25Renderer": "renderers.kimi_k25",
|
|
92
93
|
"KimiK2Renderer": "renderers.kimi_k2",
|
|
94
|
+
"LagunaM1Renderer": "renderers.laguna_xs2",
|
|
93
95
|
"LagunaXS21Renderer": "renderers.laguna_xs2",
|
|
94
96
|
"LagunaXS2Renderer": "renderers.laguna_xs2",
|
|
95
97
|
"Llama3Renderer": "renderers.llama_3",
|
|
@@ -145,6 +147,8 @@ __all__ = [
|
|
|
145
147
|
"KimiK25RendererConfig",
|
|
146
148
|
"KimiK2Renderer",
|
|
147
149
|
"KimiK2RendererConfig",
|
|
150
|
+
"LagunaM1Renderer",
|
|
151
|
+
"LagunaM1RendererConfig",
|
|
148
152
|
"LagunaXS2Renderer",
|
|
149
153
|
"LagunaXS2RendererConfig",
|
|
150
154
|
"LagunaXS21Renderer",
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.10.dev1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 10, 'dev1')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -117,6 +117,7 @@ class Message(TypedDict, total=False):
|
|
|
117
117
|
tool_calls: list[ToolCall]
|
|
118
118
|
tool_call_id: str
|
|
119
119
|
name: str
|
|
120
|
+
reasoning: str
|
|
120
121
|
reasoning_content: str
|
|
121
122
|
|
|
122
123
|
|
|
@@ -1072,9 +1073,10 @@ MODEL_RENDERER_MAP: dict[str, str] = {
|
|
|
1072
1073
|
# construction to pin a different date.
|
|
1073
1074
|
"meta-llama/Llama-3.2-1B-Instruct": "llama-3",
|
|
1074
1075
|
"meta-llama/Llama-3.2-3B-Instruct": "llama-3",
|
|
1075
|
-
# Poolside Laguna.
|
|
1076
|
-
# each mirrored by its own renderer class.
|
|
1076
|
+
# Poolside Laguna. These checkpoints ship distinct chat templates,
|
|
1077
|
+
# each mirrored by its own renderer class/config discriminator.
|
|
1077
1078
|
"poolside/Laguna-XS.2": "laguna-xs.2",
|
|
1079
|
+
"poolside/Laguna-M.1": "laguna-m.1",
|
|
1078
1080
|
"poolside/Laguna-XS-2.1": "laguna-xs-2.1",
|
|
1079
1081
|
# GPT-OSS.
|
|
1080
1082
|
"openai/gpt-oss-20b": "gpt-oss",
|
|
@@ -1323,7 +1325,11 @@ def _populate_registry():
|
|
|
1323
1325
|
from renderers.hy3 import Hy3Renderer
|
|
1324
1326
|
from renderers.kimi_k2 import KimiK2Renderer
|
|
1325
1327
|
from renderers.kimi_k25 import KimiK25Renderer
|
|
1326
|
-
from renderers.laguna_xs2 import
|
|
1328
|
+
from renderers.laguna_xs2 import (
|
|
1329
|
+
LagunaM1Renderer,
|
|
1330
|
+
LagunaXS2Renderer,
|
|
1331
|
+
LagunaXS21Renderer,
|
|
1332
|
+
)
|
|
1327
1333
|
from renderers.llama_3 import Llama3Renderer
|
|
1328
1334
|
from renderers.minimax_m2 import MiniMaxM2Renderer
|
|
1329
1335
|
from renderers.nemotron3 import Nemotron3Renderer, Nemotron3UltraRenderer
|
|
@@ -1351,6 +1357,7 @@ def _populate_registry():
|
|
|
1351
1357
|
"kimi-k2": KimiK2Renderer,
|
|
1352
1358
|
"kimi-k2.5": KimiK25Renderer,
|
|
1353
1359
|
"laguna-xs.2": LagunaXS2Renderer,
|
|
1360
|
+
"laguna-m.1": LagunaM1Renderer,
|
|
1354
1361
|
"laguna-xs-2.1": LagunaXS21Renderer,
|
|
1355
1362
|
"llama-3": Llama3Renderer,
|
|
1356
1363
|
"nemotron-3": Nemotron3Renderer,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Renderer-based generate client for vLLM
|
|
1
|
+
"""Renderer-based generate client for vLLM's /inference/v1/generate.
|
|
2
2
|
|
|
3
3
|
messages → Renderer.render_ids() → token IDs → POST /inference/v1/generate
|
|
4
4
|
→ completion tokens → Renderer.parse_response() → structured message
|
|
@@ -58,8 +58,7 @@ class OverlongPromptError(Exception):
|
|
|
58
58
|
self.prompt_len = prompt_len
|
|
59
59
|
self.max_prompt_len = max_prompt_len
|
|
60
60
|
super().__init__(
|
|
61
|
-
f"Prompt length ({prompt_len}) exceeds maximum "
|
|
62
|
-
f"context length ({max_prompt_len})."
|
|
61
|
+
f"Prompt length ({prompt_len}) exceeds maximum context length ({max_prompt_len})."
|
|
63
62
|
)
|
|
64
63
|
|
|
65
64
|
|
|
@@ -187,32 +186,26 @@ def _parse_completion_logprobs(
|
|
|
187
186
|
expected_token = f"token_id:{completion_ids[index]}"
|
|
188
187
|
if entry.get("token") != expected_token:
|
|
189
188
|
raise MalformedGenerateResponseError(
|
|
190
|
-
"Engine response "
|
|
191
|
-
f"choice.logprobs.content[{index}].token must be {expected_token!r}."
|
|
189
|
+
f"Engine response choice.logprobs.content[{index}].token must be {expected_token!r}."
|
|
192
190
|
)
|
|
193
191
|
raw_logprob = entry.get("logprob")
|
|
194
192
|
if isinstance(raw_logprob, bool) or not isinstance(raw_logprob, (int, float)):
|
|
195
193
|
raise MalformedGenerateResponseError(
|
|
196
|
-
"Engine response "
|
|
197
|
-
f"choice.logprobs.content[{index}].logprob must be a number."
|
|
194
|
+
f"Engine response choice.logprobs.content[{index}].logprob must be a number."
|
|
198
195
|
)
|
|
199
196
|
try:
|
|
200
197
|
logprob = float(raw_logprob)
|
|
201
198
|
except OverflowError as exc:
|
|
202
199
|
raise MalformedGenerateResponseError(
|
|
203
|
-
"Engine response "
|
|
204
|
-
f"choice.logprobs.content[{index}].logprob must be finite."
|
|
200
|
+
f"Engine response choice.logprobs.content[{index}].logprob must be finite."
|
|
205
201
|
) from exc
|
|
206
202
|
if not math.isfinite(logprob):
|
|
207
203
|
raise MalformedGenerateResponseError(
|
|
208
|
-
"Engine response "
|
|
209
|
-
f"choice.logprobs.content[{index}].logprob must be finite."
|
|
204
|
+
f"Engine response choice.logprobs.content[{index}].logprob must be finite."
|
|
210
205
|
)
|
|
211
206
|
if logprob == VLLM_LOGPROB_SENTINEL:
|
|
212
207
|
raise MalformedGenerateResponseError(
|
|
213
|
-
"Engine response "
|
|
214
|
-
f"choice.logprobs.content[{index}].logprob does not contain "
|
|
215
|
-
"sampling evidence."
|
|
208
|
+
f"Engine response choice.logprobs.content[{index}].logprob does not contain sampling evidence."
|
|
216
209
|
)
|
|
217
210
|
completion_logprobs.append(logprob)
|
|
218
211
|
return completion_logprobs
|
|
@@ -424,9 +417,9 @@ def _build_mm_features(
|
|
|
424
417
|
model-family specific. For now we dispatch on the renderer class;
|
|
425
418
|
extend the dispatch table as more multimodal renderers land.
|
|
426
419
|
|
|
427
|
-
NOTE — future engine pluggability: this encoder is vLLM
|
|
420
|
+
NOTE — future engine pluggability: this encoder is vLLM-specific
|
|
428
421
|
(uses ``vllm.multimodal.inputs.MultiModalKwargsItems``,
|
|
429
|
-
``vllm.entrypoints.
|
|
422
|
+
``vllm.entrypoints.scale_out.token_in_token_out.mm_serde.encode_mm_kwargs_item``, and
|
|
430
423
|
``_create_qwen2vl_field_factory``). When a second inference engine
|
|
431
424
|
arrives (SGLang, MAX, ...) the renderer client should be parameterized
|
|
432
425
|
on engine: either (a) move the encoder onto the renderer as
|
|
@@ -473,7 +466,9 @@ def _build_qwen_vl_features(
|
|
|
473
466
|
try:
|
|
474
467
|
import torch
|
|
475
468
|
from transformers.feature_extraction_utils import BatchFeature
|
|
476
|
-
from vllm.entrypoints.
|
|
469
|
+
from vllm.entrypoints.scale_out.token_in_token_out.mm_serde import (
|
|
470
|
+
encode_mm_kwargs_item,
|
|
471
|
+
)
|
|
477
472
|
from vllm.model_executor.models.qwen2_vl import _create_qwen2vl_field_factory
|
|
478
473
|
from vllm.multimodal.inputs import MultiModalKwargsItems
|
|
479
474
|
except ImportError as exc:
|
|
@@ -505,6 +505,28 @@ class LagunaXS2RendererConfig(BaseRendererConfig):
|
|
|
505
505
|
chat template's ``render_assistant_messages_raw`` gate."""
|
|
506
506
|
|
|
507
507
|
|
|
508
|
+
class LagunaM1RendererConfig(BaseRendererConfig):
|
|
509
|
+
"""Laguna M.1 renderer config.
|
|
510
|
+
|
|
511
|
+
Laguna M.1 shares Laguna XS.2's role and tool-call format, but its
|
|
512
|
+
official checkpoint has a distinct chat template: it does not inject
|
|
513
|
+
XS.2's fallback system message and it gives ``message.reasoning``
|
|
514
|
+
precedence over ``message.reasoning_content``. Served by
|
|
515
|
+
:class:`renderers.laguna_xs2.LagunaM1Renderer`.
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
name: Literal["laguna-m.1"] = "laguna-m.1"
|
|
519
|
+
|
|
520
|
+
enable_thinking: bool = False
|
|
521
|
+
"""When ``True``, the generation prompt includes ``<think>``. Mirrors
|
|
522
|
+
the official template's ``enable_thinking`` kwarg and default."""
|
|
523
|
+
|
|
524
|
+
render_assistant_messages_raw: bool = False
|
|
525
|
+
"""When ``True``, assistant messages use the official template's
|
|
526
|
+
verbatim passthrough branch. See
|
|
527
|
+
:class:`LagunaXS2RendererConfig.render_assistant_messages_raw`."""
|
|
528
|
+
|
|
529
|
+
|
|
508
530
|
class LagunaXS21RendererConfig(BaseRendererConfig):
|
|
509
531
|
"""Laguna XS-2.1 renderer config.
|
|
510
532
|
|
|
@@ -680,6 +702,7 @@ RendererConfig = Annotated[
|
|
|
680
702
|
KimiK2RendererConfig,
|
|
681
703
|
KimiK25RendererConfig,
|
|
682
704
|
LagunaXS2RendererConfig,
|
|
705
|
+
LagunaM1RendererConfig,
|
|
683
706
|
LagunaXS21RendererConfig,
|
|
684
707
|
Llama3RendererConfig,
|
|
685
708
|
MiniMaxM2RendererConfig,
|
|
@@ -720,6 +743,7 @@ _CONFIG_BY_NAME: dict[str, type[BaseRendererConfig]] = {
|
|
|
720
743
|
"kimi-k2": KimiK2RendererConfig,
|
|
721
744
|
"kimi-k2.5": KimiK25RendererConfig,
|
|
722
745
|
"laguna-xs.2": LagunaXS2RendererConfig,
|
|
746
|
+
"laguna-m.1": LagunaM1RendererConfig,
|
|
723
747
|
"laguna-xs-2.1": LagunaXS21RendererConfig,
|
|
724
748
|
"llama-3": Llama3RendererConfig,
|
|
725
749
|
"minimax-m2": MiniMaxM2RendererConfig,
|
|
@@ -767,6 +791,7 @@ __all__ = [
|
|
|
767
791
|
"Hy3RendererConfig",
|
|
768
792
|
"KimiK25RendererConfig",
|
|
769
793
|
"KimiK2RendererConfig",
|
|
794
|
+
"LagunaM1RendererConfig",
|
|
770
795
|
"LagunaXS2RendererConfig",
|
|
771
796
|
"LagunaXS21RendererConfig",
|
|
772
797
|
"Llama3RendererConfig",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Laguna
|
|
1
|
+
"""Poolside Laguna renderer family.
|
|
2
2
|
|
|
3
3
|
Main properties:
|
|
4
4
|
- Prefix is the single token ``〈|EOS|〉`` (also the EOS / stop token).
|
|
@@ -36,6 +36,11 @@ XS-2.1's template (upstream rev ``575f0f28``) is served by the
|
|
|
36
36
|
section ends at ``</available_tools>``.
|
|
37
37
|
- The ``<system>`` block is emitted whenever there is system content,
|
|
38
38
|
tools, or ``enable_thinking`` — even if that leaves it empty.
|
|
39
|
+
|
|
40
|
+
Laguna M.1's official template (revision ``2bf8a4ab``) is served by
|
|
41
|
+
:class:`LagunaM1Renderer`. It shares XS.2's byte layout, tool syntax, and
|
|
42
|
+
generation prompt, but has no fallback system prompt and reads assistant
|
|
43
|
+
reasoning from ``reasoning`` before falling back to ``reasoning_content``.
|
|
39
44
|
"""
|
|
40
45
|
|
|
41
46
|
from __future__ import annotations
|
|
@@ -56,7 +61,11 @@ from renderers.base import (
|
|
|
56
61
|
resolve_thinking_retention,
|
|
57
62
|
should_rerender_for_thinking_retention,
|
|
58
63
|
)
|
|
59
|
-
from renderers.configs import
|
|
64
|
+
from renderers.configs import (
|
|
65
|
+
LagunaM1RendererConfig,
|
|
66
|
+
LagunaXS2RendererConfig,
|
|
67
|
+
LagunaXS21RendererConfig,
|
|
68
|
+
)
|
|
60
69
|
from renderers.parsing import parse_laguna_xs2
|
|
61
70
|
|
|
62
71
|
_DEFAULT_SYSTEM_MESSAGE = (
|
|
@@ -107,7 +116,12 @@ class LagunaXS2Renderer:
|
|
|
107
116
|
def __init__(
|
|
108
117
|
self,
|
|
109
118
|
tokenizer: PreTrainedTokenizer,
|
|
110
|
-
config:
|
|
119
|
+
config: (
|
|
120
|
+
LagunaXS2RendererConfig
|
|
121
|
+
| LagunaM1RendererConfig
|
|
122
|
+
| LagunaXS21RendererConfig
|
|
123
|
+
| None
|
|
124
|
+
) = None,
|
|
111
125
|
):
|
|
112
126
|
self._tokenizer = tokenizer
|
|
113
127
|
self.config = config or LagunaXS2RendererConfig()
|
|
@@ -481,12 +495,9 @@ class LagunaXS2Renderer:
|
|
|
481
495
|
emit_text,
|
|
482
496
|
emit_text_segments,
|
|
483
497
|
) -> None:
|
|
484
|
-
# Raw passthrough is
|
|
485
|
-
#
|
|
486
|
-
if (
|
|
487
|
-
isinstance(self.config, LagunaXS2RendererConfig)
|
|
488
|
-
and self.config.render_assistant_messages_raw
|
|
489
|
-
):
|
|
498
|
+
# Raw passthrough is shared by XS.2 and M.1; XS-2.1's config does
|
|
499
|
+
# not expose this template gate.
|
|
500
|
+
if getattr(self.config, "render_assistant_messages_raw", False):
|
|
490
501
|
self._render_assistant_raw(
|
|
491
502
|
msg_idx,
|
|
492
503
|
content,
|
|
@@ -495,16 +506,7 @@ class LagunaXS2Renderer:
|
|
|
495
506
|
)
|
|
496
507
|
return
|
|
497
508
|
|
|
498
|
-
reasoning_content =
|
|
499
|
-
if isinstance(msg.get("reasoning_content"), str):
|
|
500
|
-
reasoning_content = msg["reasoning_content"]
|
|
501
|
-
else:
|
|
502
|
-
# When the caller stores reasoning as a ``ThinkingPart`` inside
|
|
503
|
-
# a list-form ``content`` (e.g. after parse_response →
|
|
504
|
-
# reserialize), pull it out here so it survives the re-render.
|
|
505
|
-
part_thinking = self._thinking_text(msg.get("content"))
|
|
506
|
-
if part_thinking:
|
|
507
|
-
reasoning_content = part_thinking
|
|
509
|
+
reasoning_content, content = self._assistant_reasoning_and_content(msg, content)
|
|
508
510
|
|
|
509
511
|
# ``<assistant>\n`` is template-injected scaffolding — the chat
|
|
510
512
|
# template emits these as the generation prompt at inference and
|
|
@@ -567,6 +569,22 @@ class LagunaXS2Renderer:
|
|
|
567
569
|
emit_special(self._assistant_end, msg_idx, is_sampled=True, is_content=True)
|
|
568
570
|
emit_text("\n", msg_idx, is_sampled=False, is_content=False)
|
|
569
571
|
|
|
572
|
+
def _assistant_reasoning_and_content(
|
|
573
|
+
self, msg: Message, content: str
|
|
574
|
+
) -> tuple[str, str]:
|
|
575
|
+
"""Return the reasoning/body pair used by the XS.2 template."""
|
|
576
|
+
reasoning_content = ""
|
|
577
|
+
if isinstance(msg.get("reasoning_content"), str):
|
|
578
|
+
reasoning_content = msg["reasoning_content"]
|
|
579
|
+
else:
|
|
580
|
+
# When the caller stores reasoning as a ``ThinkingPart`` inside
|
|
581
|
+
# a list-form ``content`` (e.g. after parse_response →
|
|
582
|
+
# reserialize), pull it out here so it survives the re-render.
|
|
583
|
+
part_thinking = self._thinking_text(msg.get("content"))
|
|
584
|
+
if part_thinking:
|
|
585
|
+
reasoning_content = part_thinking
|
|
586
|
+
return reasoning_content, content
|
|
587
|
+
|
|
570
588
|
def _render_assistant_raw(
|
|
571
589
|
self,
|
|
572
590
|
msg_idx: int,
|
|
@@ -620,6 +638,43 @@ class LagunaXS2Renderer:
|
|
|
620
638
|
emit_text("\n", msg_idx, is_sampled=False, is_content=False)
|
|
621
639
|
|
|
622
640
|
|
|
641
|
+
class LagunaM1Renderer(LagunaXS2Renderer):
|
|
642
|
+
"""Renderer for the official ``poolside/Laguna-M.1`` checkpoint.
|
|
643
|
+
|
|
644
|
+
The token protocol is shared with XS.2, while the absent fallback
|
|
645
|
+
system prompt and assistant-reasoning precedence are M.1-specific.
|
|
646
|
+
"""
|
|
647
|
+
|
|
648
|
+
def __init__(
|
|
649
|
+
self,
|
|
650
|
+
tokenizer: PreTrainedTokenizer,
|
|
651
|
+
config: LagunaM1RendererConfig | None = None,
|
|
652
|
+
):
|
|
653
|
+
super().__init__(tokenizer, config or LagunaM1RendererConfig())
|
|
654
|
+
self._default_system_message = ""
|
|
655
|
+
|
|
656
|
+
def _assistant_reasoning_and_content(
|
|
657
|
+
self, msg: Message, content: str
|
|
658
|
+
) -> tuple[str, str]:
|
|
659
|
+
# Match the official Jinja exactly: ``reasoning`` wins whenever it
|
|
660
|
+
# is a string (including the empty string), then
|
|
661
|
+
# ``reasoning_content`` is considered. An inline </think> block is
|
|
662
|
+
# removed from content and becomes the fallback reasoning source.
|
|
663
|
+
reasoning_content = ""
|
|
664
|
+
if isinstance(msg.get("reasoning"), str):
|
|
665
|
+
reasoning_content = msg["reasoning"]
|
|
666
|
+
elif isinstance(msg.get("reasoning_content"), str):
|
|
667
|
+
reasoning_content = msg["reasoning_content"]
|
|
668
|
+
|
|
669
|
+
if "</think>" in content:
|
|
670
|
+
if not reasoning_content:
|
|
671
|
+
before_close = content.split("</think>", 1)[0].rstrip("\n")
|
|
672
|
+
reasoning_content = before_close.split("<think>")[-1].lstrip("\n")
|
|
673
|
+
content = content.rsplit("</think>", 1)[-1].lstrip("\n")
|
|
674
|
+
|
|
675
|
+
return reasoning_content, content
|
|
676
|
+
|
|
677
|
+
|
|
623
678
|
class LagunaXS21Renderer(LagunaXS2Renderer):
|
|
624
679
|
"""Laguna-XS-2.1 — mirrors the ``poolside/Laguna-XS-2.1`` chat
|
|
625
680
|
template (upstream rev ``575f0f28``); see the module docstring for
|
|
@@ -37,6 +37,7 @@ RENDERER_MODELS = [
|
|
|
37
37
|
# name (auto → MODEL_RENDERER_MAP → nemotron-3-ultra).
|
|
38
38
|
("nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", "auto"),
|
|
39
39
|
("poolside/Laguna-XS.2", "auto"),
|
|
40
|
+
("poolside/Laguna-M.1", "auto"),
|
|
40
41
|
# XS-2.1 resolves to `LagunaXS21Renderer` via the model name — its
|
|
41
42
|
# chat template differs from XS.2's (see renderers/laguna_xs2.py).
|
|
42
43
|
("poolside/Laguna-XS-2.1", "auto"),
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Exact chat-template parity for the official Laguna M.1 checkpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from pydantic import TypeAdapter
|
|
9
|
+
|
|
10
|
+
from renderers import (
|
|
11
|
+
LagunaM1Renderer,
|
|
12
|
+
LagunaM1RendererConfig,
|
|
13
|
+
RendererConfig,
|
|
14
|
+
create_renderer,
|
|
15
|
+
)
|
|
16
|
+
from renderers.base import load_tokenizer
|
|
17
|
+
|
|
18
|
+
_MODEL = "poolside/Laguna-M.1"
|
|
19
|
+
|
|
20
|
+
TOOLS = [
|
|
21
|
+
{
|
|
22
|
+
"type": "function",
|
|
23
|
+
"function": {
|
|
24
|
+
"name": "shell",
|
|
25
|
+
"description": "Run a shell command",
|
|
26
|
+
"parameters": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"cmd": {"type": "string"},
|
|
30
|
+
"timeout": {"type": "integer"},
|
|
31
|
+
},
|
|
32
|
+
"required": ["cmd"],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@lru_cache(maxsize=None)
|
|
40
|
+
def _tok():
|
|
41
|
+
return load_tokenizer(_MODEL)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _renderer(**kwargs) -> LagunaM1Renderer:
|
|
45
|
+
renderer = create_renderer(_tok(), LagunaM1RendererConfig(**kwargs))
|
|
46
|
+
assert isinstance(renderer, LagunaM1Renderer)
|
|
47
|
+
return renderer
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _expected(messages, *, tools=None, add_generation_prompt=False, **kwargs):
|
|
51
|
+
return list(
|
|
52
|
+
_tok().apply_chat_template(
|
|
53
|
+
messages,
|
|
54
|
+
tools=tools,
|
|
55
|
+
add_generation_prompt=add_generation_prompt,
|
|
56
|
+
tokenize=True,
|
|
57
|
+
return_dict=False,
|
|
58
|
+
**kwargs,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_auto_selection_and_typed_config():
|
|
64
|
+
renderer = create_renderer(_tok())
|
|
65
|
+
assert isinstance(renderer, LagunaM1Renderer)
|
|
66
|
+
assert isinstance(renderer.config, LagunaM1RendererConfig)
|
|
67
|
+
|
|
68
|
+
parsed = TypeAdapter(RendererConfig).validate_python(
|
|
69
|
+
{"name": "laguna-m.1", "enable_thinking": True}
|
|
70
|
+
)
|
|
71
|
+
assert isinstance(parsed, LagunaM1RendererConfig)
|
|
72
|
+
assert parsed.enable_thinking is True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize("enable_thinking", [False, True])
|
|
76
|
+
def test_no_system_and_generation_prompt_parity(enable_thinking):
|
|
77
|
+
messages = [{"role": "user", "content": " preserve me "}]
|
|
78
|
+
renderer = _renderer(enable_thinking=enable_thinking)
|
|
79
|
+
got = renderer.render_ids(messages, add_generation_prompt=True)
|
|
80
|
+
assert got == _expected(
|
|
81
|
+
messages,
|
|
82
|
+
add_generation_prompt=True,
|
|
83
|
+
enable_thinking=enable_thinking,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
text = _tok().decode(got)
|
|
87
|
+
assert text.startswith("〈|EOS|〉<user>\n preserve me \n</user>\n<assistant>\n")
|
|
88
|
+
assert "<system>" not in text
|
|
89
|
+
assert text.endswith("<think>" if enable_thinking else "</think>")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize("enable_thinking", [False, True])
|
|
93
|
+
def test_reasoning_content_history_parity_in_both_modes(enable_thinking):
|
|
94
|
+
messages = [
|
|
95
|
+
{"role": "user", "content": "Compute."},
|
|
96
|
+
{
|
|
97
|
+
"role": "assistant",
|
|
98
|
+
"reasoning_content": "\n two steps \n",
|
|
99
|
+
"content": "\n result \n",
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
got = _renderer(enable_thinking=enable_thinking).render_ids(messages)
|
|
103
|
+
assert got == _expected(messages, enable_thinking=enable_thinking)
|
|
104
|
+
assert "<think>\ntwo steps\n</think>\nresult\n</assistant>\n" in _tok().decode(got)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_reasoning_field_precedence_and_inline_think_extraction():
|
|
108
|
+
precedence = [
|
|
109
|
+
{"role": "user", "content": "Compute."},
|
|
110
|
+
{
|
|
111
|
+
"role": "assistant",
|
|
112
|
+
"reasoning": "preferred",
|
|
113
|
+
"reasoning_content": "ignored",
|
|
114
|
+
"content": "answer",
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
got = _renderer(enable_thinking=True).render_ids(precedence)
|
|
118
|
+
assert got == _expected(precedence, enable_thinking=True)
|
|
119
|
+
text = _tok().decode(got)
|
|
120
|
+
assert "preferred" in text
|
|
121
|
+
assert "ignored" not in text
|
|
122
|
+
|
|
123
|
+
empty_reasoning_wins = [
|
|
124
|
+
{"role": "user", "content": "Compute."},
|
|
125
|
+
{
|
|
126
|
+
"role": "assistant",
|
|
127
|
+
"reasoning": "",
|
|
128
|
+
"reasoning_content": "also ignored",
|
|
129
|
+
"content": "answer",
|
|
130
|
+
},
|
|
131
|
+
]
|
|
132
|
+
got = _renderer(enable_thinking=True).render_ids(empty_reasoning_wins)
|
|
133
|
+
assert got == _expected(empty_reasoning_wins, enable_thinking=True)
|
|
134
|
+
assert "also ignored" not in _tok().decode(got)
|
|
135
|
+
|
|
136
|
+
inline = [
|
|
137
|
+
{"role": "user", "content": "Compute."},
|
|
138
|
+
{
|
|
139
|
+
"role": "assistant",
|
|
140
|
+
"content": "<think>\ninline reason\n</think>\nvisible answer",
|
|
141
|
+
},
|
|
142
|
+
]
|
|
143
|
+
assert _renderer().render_ids(inline) == _expected(inline)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_tools_calls_responses_and_generation_prompt_parity():
|
|
147
|
+
messages = [
|
|
148
|
+
{"role": "system", "content": "Use tools carefully."},
|
|
149
|
+
{"role": "user", "content": "Inspect the machine."},
|
|
150
|
+
{
|
|
151
|
+
"role": "assistant",
|
|
152
|
+
"reasoning": "Need a command.",
|
|
153
|
+
"content": "Running it.",
|
|
154
|
+
"tool_calls": [
|
|
155
|
+
{
|
|
156
|
+
"id": "call_1",
|
|
157
|
+
"type": "function",
|
|
158
|
+
"function": {
|
|
159
|
+
"name": "shell",
|
|
160
|
+
"arguments": {"cmd": "uname -a", "timeout": 5},
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"role": "tool",
|
|
167
|
+
"tool_call_id": "call_1",
|
|
168
|
+
"content": '{"stdout":"Linux"}',
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
renderer = _renderer(enable_thinking=True)
|
|
172
|
+
got = renderer.render_ids(messages, tools=TOOLS, add_generation_prompt=True)
|
|
173
|
+
assert got == _expected(
|
|
174
|
+
messages,
|
|
175
|
+
tools=TOOLS,
|
|
176
|
+
add_generation_prompt=True,
|
|
177
|
+
enable_thinking=True,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
text = _tok().decode(got)
|
|
181
|
+
assert "<available_tools>\n" in text
|
|
182
|
+
assert (
|
|
183
|
+
"<tool_call>shell\n<arg_key>cmd</arg_key>\n"
|
|
184
|
+
"<arg_value>uname -a</arg_value>\n<arg_key>timeout</arg_key>\n"
|
|
185
|
+
"<arg_value>5</arg_value>\n</tool_call>\n"
|
|
186
|
+
) in text
|
|
187
|
+
assert '<tool_response>\n{"stdout":"Linux"}\n</tool_response>\n' in text
|
|
188
|
+
assert text.endswith("<assistant>\n<think>")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_reasoning_content_and_tool_call_round_trip():
|
|
192
|
+
renderer = _renderer(enable_thinking=True)
|
|
193
|
+
prompt = [{"role": "user", "content": "Inspect the machine."}]
|
|
194
|
+
assistant = {
|
|
195
|
+
"role": "assistant",
|
|
196
|
+
"reasoning": "Need a command.",
|
|
197
|
+
"content": "Running it.",
|
|
198
|
+
"tool_calls": [
|
|
199
|
+
{
|
|
200
|
+
"function": {
|
|
201
|
+
"name": "shell",
|
|
202
|
+
"arguments": {"cmd": "uname -a", "timeout": 5},
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
}
|
|
207
|
+
prompt_ids = renderer.render_ids(prompt, add_generation_prompt=True)
|
|
208
|
+
full_ids = renderer.render_ids([*prompt, assistant])
|
|
209
|
+
completion_ids = full_ids[len(prompt_ids) :]
|
|
210
|
+
parsed = renderer.parse_response(completion_ids, tools=TOOLS)
|
|
211
|
+
|
|
212
|
+
assert parsed.reasoning_content == "Need a command."
|
|
213
|
+
assert parsed.content == "Running it."
|
|
214
|
+
assert len(parsed.tool_calls) == 1
|
|
215
|
+
assert parsed.tool_calls[0].name == "shell"
|
|
216
|
+
assert parsed.tool_calls[0].arguments == {"cmd": "uname -a", "timeout": 5}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_raw_assistant_round_trip_parity():
|
|
220
|
+
messages = [
|
|
221
|
+
{"role": "user", "content": "Continue."},
|
|
222
|
+
{
|
|
223
|
+
"role": "assistant",
|
|
224
|
+
"content": "<think>raw reason</think>raw body</assistant>",
|
|
225
|
+
},
|
|
226
|
+
]
|
|
227
|
+
renderer = _renderer(enable_thinking=True, render_assistant_messages_raw=True)
|
|
228
|
+
assert renderer.render_ids(messages) == _expected(
|
|
229
|
+
messages,
|
|
230
|
+
enable_thinking=True,
|
|
231
|
+
render_assistant_messages_raw=True,
|
|
232
|
+
)
|
|
@@ -64,6 +64,7 @@ _RENDERER_MODELS = [
|
|
|
64
64
|
# name; parity asserted against the Ultra apply_chat_template (``medium_effort``).
|
|
65
65
|
("nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", "auto"),
|
|
66
66
|
("poolside/Laguna-XS.2", "auto"),
|
|
67
|
+
("poolside/Laguna-M.1", "auto"),
|
|
67
68
|
("poolside/Laguna-XS-2.1", "auto"),
|
|
68
69
|
("tencent/Hy3", "auto"),
|
|
69
70
|
("openai/gpt-oss-20b", "gpt-oss"),
|
|
@@ -119,7 +120,7 @@ _KWARG_VALUES: dict[str, list[Any]] = {
|
|
|
119
120
|
"You are a helpful assistant. Your name is MiniMax-M2.5 and is built by MiniMax.",
|
|
120
121
|
"You are CustomBot, a research assistant.",
|
|
121
122
|
],
|
|
122
|
-
# Laguna-XS.2 — switches assistant rendering to a verbatim
|
|
123
|
+
# Laguna-XS.2 / Laguna-M.1 — switches assistant rendering to a verbatim
|
|
123
124
|
# passthrough mode. The renderer paths diverge significantly under
|
|
124
125
|
# this flag, so both values are exercised.
|
|
125
126
|
"render_assistant_messages_raw": [True, False],
|
|
@@ -47,6 +47,7 @@ _ROUNDTRIP_MODELS = [
|
|
|
47
47
|
# (no separating newline) — the Ultra-specific glue stresses the round-trip.
|
|
48
48
|
("nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-BF16", "auto"),
|
|
49
49
|
("poolside/Laguna-XS.2", "auto"),
|
|
50
|
+
("poolside/Laguna-M.1", "auto"),
|
|
50
51
|
# Laguna-XS-2.1 is deliberately absent: under the default
|
|
51
52
|
# ``enable_thinking=False`` its template drops assistant reasoning at
|
|
52
53
|
# render time, so the reasoning round-trip can't hold by design.
|
|
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
|