renderers 0.1.13.dev4__tar.gz → 0.1.13.dev5__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.13.dev4 → renderers-0.1.13.dev5}/PKG-INFO +2 -2
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/README.md +1 -1
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/docs/renderer-config.md +2 -2
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/__init__.py +4 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/_version.py +2 -2
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/base.py +9 -1
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/client.py +11 -2
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/configs.py +9 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/glm5.py +326 -35
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/parity.py +5 -0
- renderers-0.1.13.dev5/tests/test_glm53_flash.py +624 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_reasoning_boundaries.py +2 -1
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/.github/workflows/style.yml +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/.github/workflows/test.yml +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/.gitignore +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/LICENSE +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/examples/README.md +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/pyproject.toml +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/custom.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/deepseek_r1.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/deepseek_v4.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/default.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/gemma4.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/glm45.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/hy3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/inkling.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/laguna_s21.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/laguna_xs2.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/llama_3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/nemotron3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/parsers.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/parsing.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/prime_qwen3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/qwen3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/qwen35.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/qwen36.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/qwen38.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/reasoning.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/renderers/tagged.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/audit_prefix_stability.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/conftest.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/fixtures/prefix_stability.json +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/reference_continuations.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/reference_rendering.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/replay.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_bridge.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_build_helpers.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_client.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_custom.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_deepseek_r1.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_deepseek_v4.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_disabled_thinking_stability.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_gemma4.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_glm_tool_name_validation.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_hy3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_incremental.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_inkling.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_is_content.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_laguna_m1.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_laguna_xs21.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_llama_3.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_message_indices.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_message_tool_names.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_multimodal.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_nemotron35_vlm.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_nemotron3_ultra.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_offsetless_tokenizers.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_parity.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_parse_response.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_parsers.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_qwen38.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_roundtrip.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_tagged_rollout.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/tests/test_tool_arg_type_preservation.py +0 -0
- {renderers-0.1.13.dev4 → renderers-0.1.13.dev5}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: renderers
|
|
3
|
-
Version: 0.1.13.
|
|
3
|
+
Version: 0.1.13.dev5
|
|
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
|
|
@@ -114,7 +114,7 @@ next_prompt_ids = r.bridge_to_next_turn(
|
|
|
114
114
|
)
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `glm-5`, `glm-5.1`, `glm-5.3`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `deepseek-v4` (V4 Flash 0731), `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, `inkling` / `inkling-small`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper. `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `kimi-k2.5` / `kimi-k2.6`, and the Inkling checkpoints are multimodal (Inkling handles both image **and** audio).
|
|
117
|
+
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `glm-5`, `glm-5.1`, `glm-5.3`, `glm-5.3-flash`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `deepseek-v4` (V4 Flash 0731), `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, `inkling` / `inkling-small`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper. `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `glm-5.3-flash`, `kimi-k2.5` / `kimi-k2.6`, and the Inkling checkpoints are multimodal (Inkling handles both image **and** audio; `glm-5.3-flash` renders images only with `process_multimodal=False`).
|
|
118
118
|
|
|
119
119
|
## API
|
|
120
120
|
|
|
@@ -94,7 +94,7 @@ next_prompt_ids = r.bridge_to_next_turn(
|
|
|
94
94
|
)
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `glm-5`, `glm-5.1`, `glm-5.3`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `deepseek-v4` (V4 Flash 0731), `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, `inkling` / `inkling-small`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper. `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `kimi-k2.5` / `kimi-k2.6`, and the Inkling checkpoints are multimodal (Inkling handles both image **and** audio).
|
|
97
|
+
Hand-coded renderers ship for `qwen3`, `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `glm-5`, `glm-5.1`, `glm-5.3`, `glm-5.3-flash`, `glm-4.5`, `minimax-m2`, `deepseek-v3`, `deepseek-r1`, `deepseek-v4` (V4 Flash 0731), `kimi-k2`, `kimi-k2.5` / `kimi-k2.6`, `laguna-xs.2`, `laguna-xs-2.1`, `laguna-s-2.1`, `laguna-m.1`, `nemotron-3`, `nemotron-3-ultra`, `nemotron-3.5`, `llama-3`, `gpt-oss`, `hy3`, `inkling` / `inkling-small`, and `prime-qwen3`. Anything else falls back to `DefaultRenderer`, a generic `apply_chat_template` wrapper. `qwen3-vl`, `qwen3.5`, `qwen3.6`, `qwen3.8`, `gemma4`, `glm-5.3-flash`, `kimi-k2.5` / `kimi-k2.6`, and the Inkling checkpoints are multimodal (Inkling handles both image **and** audio; `glm-5.3-flash` renders images only with `process_multimodal=False`).
|
|
98
98
|
|
|
99
99
|
## API
|
|
100
100
|
|
|
@@ -34,7 +34,7 @@ product in `tests/test_parity.py`.
|
|
|
34
34
|
| Qwen3-VL | `Qwen3VLRendererConfig` | `add_vision_id` | `image_cache_max` |
|
|
35
35
|
| Gemma 4 | `Gemma4RendererConfig` | `enable_thinking`, `preserve_thinking` | `image_cache_max` |
|
|
36
36
|
| GLM-5 / 5.1 | `GLM5RendererConfig` / `GLM51RendererConfig` | `enable_thinking`, `clear_thinking` | - |
|
|
37
|
-
| GLM-5.3 | `GLM53RendererConfig` | `clear_thinking`, `reasoning_effort` | - |
|
|
37
|
+
| GLM-5.3 / 5.3 Flash | `GLM53RendererConfig` / `GLM53FlashRendererConfig` | `clear_thinking`, `reasoning_effort` | - |
|
|
38
38
|
| GLM-4.5 | `GLM45RendererConfig` | `enable_thinking` | - |
|
|
39
39
|
| gpt-oss | `GptOssRendererConfig` | `reasoning_effort`, `conversation_start_date` | `use_system_prompt`, `knowledge_cutoff`, `model_identity`, `auto_drop_analysis` |
|
|
40
40
|
| Hy3 | `Hy3RendererConfig` | `reasoning_effort`, `preserved_thinking`, `is_training`, `raw_last_assistant`, `fallback_strategy` | - |
|
|
@@ -141,7 +141,7 @@ the knobs its template actually exposes:
|
|
|
141
141
|
| Qwen3.8 | `preserve_thinking=True -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
142
142
|
| Gemma 4 | `preserve_thinking=True -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
143
143
|
| GLM-5 / 5.1 | `clear_thinking=False -> all`; else `enable_thinking=False -> all`; else `tool_cycle` |
|
|
144
|
-
| GLM-5.3 | `clear_thinking=False -> all`, else `tool_cycle` |
|
|
144
|
+
| GLM-5.3 / 5.3 Flash | `clear_thinking=False -> all`, else `tool_cycle` |
|
|
145
145
|
| GLM-4.5 | `enable_thinking=False -> all`, else `tool_cycle` |
|
|
146
146
|
| gpt-oss | `auto_drop_analysis=False -> all`, else `tool_cycle` |
|
|
147
147
|
| Hy3 | `preserved_thinking=True -> all`, else `tool_cycle` |
|
|
@@ -55,6 +55,7 @@ from renderers.configs import (
|
|
|
55
55
|
GLM5RendererConfig,
|
|
56
56
|
GLM45RendererConfig,
|
|
57
57
|
GLM51RendererConfig,
|
|
58
|
+
GLM53FlashRendererConfig,
|
|
58
59
|
GLM53RendererConfig,
|
|
59
60
|
GptOssRendererConfig,
|
|
60
61
|
Hy3RendererConfig,
|
|
@@ -95,6 +96,7 @@ _LAZY_RENDERERS: dict[str, str] = {
|
|
|
95
96
|
"DefaultRenderer": "renderers.default",
|
|
96
97
|
"GLM45Renderer": "renderers.glm45",
|
|
97
98
|
"GLM51Renderer": "renderers.glm5",
|
|
99
|
+
"GLM53FlashRenderer": "renderers.glm5",
|
|
98
100
|
"GLM53Renderer": "renderers.glm5",
|
|
99
101
|
"GLM5Renderer": "renderers.glm5",
|
|
100
102
|
"GptOssRenderer": "renderers.gpt_oss",
|
|
@@ -155,6 +157,8 @@ __all__ = [
|
|
|
155
157
|
"GLM45RendererConfig",
|
|
156
158
|
"GLM51Renderer",
|
|
157
159
|
"GLM51RendererConfig",
|
|
160
|
+
"GLM53FlashRenderer",
|
|
161
|
+
"GLM53FlashRendererConfig",
|
|
158
162
|
"GLM53Renderer",
|
|
159
163
|
"GLM53RendererConfig",
|
|
160
164
|
"GLM5Renderer",
|
|
@@ -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.13.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 13, '
|
|
21
|
+
__version__ = version = '0.1.13.dev5'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 13, 'dev5')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1012,6 +1012,8 @@ MODEL_RENDERER_MAP: dict[str, str] = {
|
|
|
1012
1012
|
"zai-org/GLM-5.1": "glm-5.1",
|
|
1013
1013
|
"zai-org/GLM-5.3": "glm-5.3",
|
|
1014
1014
|
"zai-org/GLM-5.3-BF16": "glm-5.3",
|
|
1015
|
+
"zai-org/GLM-5.3-Flash": "glm-5.3-flash",
|
|
1016
|
+
"zai-org/GLM-5.3-Flash-BF16": "glm-5.3-flash",
|
|
1015
1017
|
# GLM-4.5.
|
|
1016
1018
|
"THUDM/GLM-4.5-Air": "glm-4.5",
|
|
1017
1019
|
"zai-org/GLM-4.5-Air": "glm-4.5",
|
|
@@ -1353,7 +1355,12 @@ def _populate_registry():
|
|
|
1353
1355
|
from renderers.deepseek_v4 import DeepSeekV4Renderer
|
|
1354
1356
|
from renderers.default import DefaultRenderer
|
|
1355
1357
|
from renderers.gemma4 import Gemma4Renderer
|
|
1356
|
-
from renderers.glm5 import
|
|
1358
|
+
from renderers.glm5 import (
|
|
1359
|
+
GLM5Renderer,
|
|
1360
|
+
GLM51Renderer,
|
|
1361
|
+
GLM53FlashRenderer,
|
|
1362
|
+
GLM53Renderer,
|
|
1363
|
+
)
|
|
1357
1364
|
from renderers.glm45 import GLM45Renderer
|
|
1358
1365
|
from renderers.gpt_oss import GptOssRenderer
|
|
1359
1366
|
from renderers.hy3 import Hy3Renderer
|
|
@@ -1393,6 +1400,7 @@ def _populate_registry():
|
|
|
1393
1400
|
"glm-5": GLM5Renderer,
|
|
1394
1401
|
"glm-5.1": GLM51Renderer,
|
|
1395
1402
|
"glm-5.3": GLM53Renderer,
|
|
1403
|
+
"glm-5.3-flash": GLM53FlashRenderer,
|
|
1396
1404
|
"glm-4.5": GLM45Renderer,
|
|
1397
1405
|
"minimax-m2": MiniMaxM2Renderer,
|
|
1398
1406
|
"deepseek-v3": DeepSeekV3Renderer,
|
|
@@ -336,7 +336,9 @@ async def generate(
|
|
|
336
336
|
"token_ids": prompt_ids,
|
|
337
337
|
"sampling_params": sp,
|
|
338
338
|
}
|
|
339
|
-
content_parts =
|
|
339
|
+
content_parts = (
|
|
340
|
+
_content_parts(messages, renderer=renderer) if not process_multimodal else None
|
|
341
|
+
)
|
|
340
342
|
features = (
|
|
341
343
|
_build_mm_features(renderer, mm_data)
|
|
342
344
|
if process_multimodal and mm_data and not mm_data.is_empty()
|
|
@@ -454,8 +456,15 @@ _MEDIA_URL_TYPES = {
|
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
|
|
457
|
-
def _content_parts(
|
|
459
|
+
def _content_parts(
|
|
460
|
+
messages: list[Message], *, renderer: Renderer
|
|
461
|
+
) -> list[dict[str, Any]]:
|
|
458
462
|
"""Flatten raw media in prompt order for vLLM's token generate endpoint."""
|
|
463
|
+
# Some templates reorder tool results. Use the same order for raw media,
|
|
464
|
+
# including when the caller supplies a prebuilt prompt and skips rendering.
|
|
465
|
+
order_messages = getattr(renderer, "messages_in_prompt_order", None)
|
|
466
|
+
if order_messages is not None:
|
|
467
|
+
messages = order_messages(messages)
|
|
459
468
|
parts: list[dict[str, Any]] = []
|
|
460
469
|
for message in messages:
|
|
461
470
|
content = message.get("content")
|
|
@@ -434,6 +434,12 @@ class GLM53RendererConfig(BaseRendererConfig):
|
|
|
434
434
|
return self
|
|
435
435
|
|
|
436
436
|
|
|
437
|
+
class GLM53FlashRendererConfig(GLM53RendererConfig):
|
|
438
|
+
"""GLM-5.3 Flash renderer config. GLM-5.3's template surface, plus images."""
|
|
439
|
+
|
|
440
|
+
name: Literal["glm-5.3-flash"] = "glm-5.3-flash"
|
|
441
|
+
|
|
442
|
+
|
|
437
443
|
class GLM45RendererConfig(BaseRendererConfig):
|
|
438
444
|
"""GLM-4.5 Air renderer config."""
|
|
439
445
|
|
|
@@ -1042,6 +1048,7 @@ RendererConfig = Annotated[
|
|
|
1042
1048
|
GLM5RendererConfig,
|
|
1043
1049
|
GLM51RendererConfig,
|
|
1044
1050
|
GLM53RendererConfig,
|
|
1051
|
+
GLM53FlashRendererConfig,
|
|
1045
1052
|
GLM45RendererConfig,
|
|
1046
1053
|
GptOssRendererConfig,
|
|
1047
1054
|
Hy3RendererConfig,
|
|
@@ -1091,6 +1098,7 @@ _CONFIG_BY_NAME: dict[str, type[BaseRendererConfig]] = {
|
|
|
1091
1098
|
"glm-5": GLM5RendererConfig,
|
|
1092
1099
|
"glm-5.1": GLM51RendererConfig,
|
|
1093
1100
|
"glm-5.3": GLM53RendererConfig,
|
|
1101
|
+
"glm-5.3-flash": GLM53FlashRendererConfig,
|
|
1094
1102
|
"glm-4.5": GLM45RendererConfig,
|
|
1095
1103
|
"gpt-oss": GptOssRendererConfig,
|
|
1096
1104
|
"hy3": Hy3RendererConfig,
|
|
@@ -1144,6 +1152,7 @@ __all__ = [
|
|
|
1144
1152
|
"DeepSeekV4RendererConfig",
|
|
1145
1153
|
"GLM45RendererConfig",
|
|
1146
1154
|
"GLM51RendererConfig",
|
|
1155
|
+
"GLM53FlashRendererConfig",
|
|
1147
1156
|
"GLM53RendererConfig",
|
|
1148
1157
|
"GLM5RendererConfig",
|
|
1149
1158
|
"Gemma4RendererConfig",
|
|
@@ -12,10 +12,12 @@ Key differences from Qwen family:
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
|
+
from collections.abc import Mapping
|
|
15
16
|
from typing import Any
|
|
16
17
|
|
|
17
18
|
from renderers.base import (
|
|
18
19
|
Message,
|
|
20
|
+
MultiModalData,
|
|
19
21
|
ParsedResponse,
|
|
20
22
|
RenderedTokens,
|
|
21
23
|
Tokenizer,
|
|
@@ -30,6 +32,7 @@ from renderers.base import (
|
|
|
30
32
|
from renderers.configs import (
|
|
31
33
|
GLM5RendererConfig,
|
|
32
34
|
GLM51RendererConfig,
|
|
35
|
+
GLM53FlashRendererConfig,
|
|
33
36
|
GLM53RendererConfig,
|
|
34
37
|
)
|
|
35
38
|
from renderers.parsing import parse_glm
|
|
@@ -52,6 +55,24 @@ _TOOLS_FOOTER = (
|
|
|
52
55
|
)
|
|
53
56
|
|
|
54
57
|
|
|
58
|
+
def _is_list_of_outputs(content: Any) -> bool:
|
|
59
|
+
return (
|
|
60
|
+
isinstance(content, list)
|
|
61
|
+
and bool(content)
|
|
62
|
+
and isinstance(content[0], dict)
|
|
63
|
+
and "output" in content[0]
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _tool_contents(content: Any) -> list[Any]:
|
|
68
|
+
if _is_list_of_outputs(content):
|
|
69
|
+
return [
|
|
70
|
+
entry.get("output") if isinstance(entry, dict) else None
|
|
71
|
+
for entry in content
|
|
72
|
+
]
|
|
73
|
+
return [content]
|
|
74
|
+
|
|
75
|
+
|
|
55
76
|
class GLM5Renderer:
|
|
56
77
|
"""Deterministic message → token renderer for GLM-5 models."""
|
|
57
78
|
|
|
@@ -138,6 +159,26 @@ class GLM5Renderer:
|
|
|
138
159
|
return "".join(parts)
|
|
139
160
|
return str(content)
|
|
140
161
|
|
|
162
|
+
def _emit_body(
|
|
163
|
+
self, content: Any, msg_idx: int, *, emit_special, emit_text
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Emit a system, user or tool message body. Renderers for models that
|
|
166
|
+
read images in the conversation override this to place the media."""
|
|
167
|
+
emit_text(
|
|
168
|
+
self._visible_text(content), msg_idx, is_sampled=False, is_content=True
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def _emit_tool_body(
|
|
172
|
+
self, content: Any, msg_idx: int, *, tools, emit_special, emit_text
|
|
173
|
+
) -> None:
|
|
174
|
+
self._emit_body(
|
|
175
|
+
content, msg_idx, emit_special=emit_special, emit_text=emit_text
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _prompt_tools(tools: list[ToolSpec]) -> list[ToolSpec]:
|
|
180
|
+
return tools
|
|
181
|
+
|
|
141
182
|
@staticmethod
|
|
142
183
|
def _last_user_index(messages: list[Message]) -> int:
|
|
143
184
|
for i in range(len(messages) - 1, -1, -1):
|
|
@@ -218,7 +259,7 @@ class GLM5Renderer:
|
|
|
218
259
|
if tools:
|
|
219
260
|
emit_special(self._system, -1, is_sampled=False, is_content=False)
|
|
220
261
|
tool_text = _TOOLS_HEADER
|
|
221
|
-
for tool in tools:
|
|
262
|
+
for tool in self._prompt_tools(tools):
|
|
222
263
|
tool_text += self._format_tool_spec(tool) + "\n"
|
|
223
264
|
tool_text += _TOOLS_FOOTER
|
|
224
265
|
emit_text(tool_text, -1, is_sampled=False, is_content=False)
|
|
@@ -237,7 +278,12 @@ class GLM5Renderer:
|
|
|
237
278
|
|
|
238
279
|
if role == "system":
|
|
239
280
|
emit_special(self._system, i, is_sampled=False, is_content=False)
|
|
240
|
-
|
|
281
|
+
self._emit_body(
|
|
282
|
+
msg.get("content"),
|
|
283
|
+
i,
|
|
284
|
+
emit_special=emit_special,
|
|
285
|
+
emit_text=emit_text,
|
|
286
|
+
)
|
|
241
287
|
|
|
242
288
|
elif role == "user":
|
|
243
289
|
emit_special(
|
|
@@ -246,7 +292,12 @@ class GLM5Renderer:
|
|
|
246
292
|
is_sampled=closes_assistant_turn,
|
|
247
293
|
is_content=closes_assistant_turn,
|
|
248
294
|
)
|
|
249
|
-
|
|
295
|
+
self._emit_body(
|
|
296
|
+
msg.get("content"),
|
|
297
|
+
i,
|
|
298
|
+
emit_special=emit_special,
|
|
299
|
+
emit_text=emit_text,
|
|
300
|
+
)
|
|
250
301
|
|
|
251
302
|
elif role == "assistant":
|
|
252
303
|
self._render_assistant(
|
|
@@ -261,13 +312,13 @@ class GLM5Renderer:
|
|
|
261
312
|
|
|
262
313
|
elif role == "tool":
|
|
263
314
|
if i == 0 or messages[i - 1]["role"] != "tool":
|
|
264
|
-
for position, tool_idx in enumerate(
|
|
265
|
-
self.
|
|
315
|
+
for position, (tool_idx, tool_content) in enumerate(
|
|
316
|
+
self._tool_results(messages, i)
|
|
266
317
|
):
|
|
267
|
-
tool_message = messages[tool_idx]
|
|
268
318
|
self._render_tool(
|
|
269
319
|
tool_idx,
|
|
270
|
-
|
|
320
|
+
tool_content,
|
|
321
|
+
tools=tools,
|
|
271
322
|
emit_special=emit_special,
|
|
272
323
|
emit_text=emit_text,
|
|
273
324
|
emit_text_segments=emit_text_segments,
|
|
@@ -316,6 +367,14 @@ class GLM5Renderer:
|
|
|
316
367
|
block_end += 1
|
|
317
368
|
return list(range(block_start, block_end + 1))
|
|
318
369
|
|
|
370
|
+
def _tool_results(
|
|
371
|
+
self, messages: list[Message], block_start: int
|
|
372
|
+
) -> list[tuple[int, Any]]:
|
|
373
|
+
return [
|
|
374
|
+
(i, messages[i].get("content"))
|
|
375
|
+
for i in self._ordered_tool_indices(messages, block_start)
|
|
376
|
+
]
|
|
377
|
+
|
|
319
378
|
def render_ids(
|
|
320
379
|
self,
|
|
321
380
|
messages: list[Message],
|
|
@@ -469,15 +528,24 @@ class GLM5Renderer:
|
|
|
469
528
|
# *actually* produced this step.
|
|
470
529
|
for i, msg in enumerate(new_messages):
|
|
471
530
|
role = msg.get("role")
|
|
472
|
-
content = self._visible_text(msg.get("content"))
|
|
473
531
|
if role == "user":
|
|
474
532
|
# Dedup: model already emitted <|user|> as its stop token.
|
|
475
533
|
if not (i == 0 and last_prev == self._user):
|
|
476
534
|
emit_special(self._user, i)
|
|
477
|
-
|
|
535
|
+
self._emit_body(
|
|
536
|
+
msg.get("content"),
|
|
537
|
+
i,
|
|
538
|
+
emit_special=emit_special,
|
|
539
|
+
emit_text=emit_text,
|
|
540
|
+
)
|
|
478
541
|
elif role == "system":
|
|
479
542
|
emit_special(self._system, i)
|
|
480
|
-
|
|
543
|
+
self._emit_body(
|
|
544
|
+
msg.get("content"),
|
|
545
|
+
i,
|
|
546
|
+
emit_special=emit_special,
|
|
547
|
+
emit_text=emit_text,
|
|
548
|
+
)
|
|
481
549
|
elif role == "tool":
|
|
482
550
|
prev_is_tool = i > 0 and new_messages[i - 1].get("role") == "tool"
|
|
483
551
|
if i == 0 and last_prev == self._observation:
|
|
@@ -486,7 +554,13 @@ class GLM5Renderer:
|
|
|
486
554
|
elif not prev_is_tool:
|
|
487
555
|
emit_special(self._observation, i)
|
|
488
556
|
emit_special(self._tool_response_tok, i)
|
|
489
|
-
|
|
557
|
+
self._emit_tool_body(
|
|
558
|
+
msg.get("content"),
|
|
559
|
+
i,
|
|
560
|
+
tools=tools,
|
|
561
|
+
emit_special=emit_special,
|
|
562
|
+
emit_text=emit_text,
|
|
563
|
+
)
|
|
490
564
|
emit_special(self._tool_response_end_tok, i)
|
|
491
565
|
else:
|
|
492
566
|
return None
|
|
@@ -637,8 +711,9 @@ class GLM5Renderer:
|
|
|
637
711
|
def _render_tool(
|
|
638
712
|
self,
|
|
639
713
|
msg_idx: int,
|
|
640
|
-
content:
|
|
714
|
+
content: Any,
|
|
641
715
|
*,
|
|
716
|
+
tools: list[ToolSpec] | None = None,
|
|
642
717
|
emit_special,
|
|
643
718
|
emit_text,
|
|
644
719
|
emit_text_segments,
|
|
@@ -664,7 +739,13 @@ class GLM5Renderer:
|
|
|
664
739
|
emit_special(
|
|
665
740
|
self._tool_response_tok, msg_idx, is_sampled=False, is_content=False
|
|
666
741
|
)
|
|
667
|
-
|
|
742
|
+
self._emit_tool_body(
|
|
743
|
+
content,
|
|
744
|
+
msg_idx,
|
|
745
|
+
tools=tools,
|
|
746
|
+
emit_special=emit_special,
|
|
747
|
+
emit_text=emit_text,
|
|
748
|
+
)
|
|
668
749
|
emit_special(
|
|
669
750
|
self._tool_response_end_tok, msg_idx, is_sampled=False, is_content=False
|
|
670
751
|
)
|
|
@@ -727,10 +808,45 @@ class GLM53Renderer(GLM5Renderer):
|
|
|
727
808
|
spec = {k: v for k, v in spec.items() if k not in ("defer_loading", "strict")}
|
|
728
809
|
return json.dumps(spec, ensure_ascii=False)
|
|
729
810
|
|
|
730
|
-
|
|
811
|
+
@staticmethod
|
|
812
|
+
def _prompt_tools(tools: list[ToolSpec]) -> list[ToolSpec]:
|
|
813
|
+
return [
|
|
814
|
+
tool
|
|
815
|
+
for tool in tools
|
|
816
|
+
if not (tool.get("function") or tool).get("defer_loading")
|
|
817
|
+
]
|
|
818
|
+
|
|
819
|
+
def _emit_tool_body(
|
|
820
|
+
self, content: Any, msg_idx: int, *, tools, emit_special, emit_text
|
|
821
|
+
) -> None:
|
|
822
|
+
if (
|
|
823
|
+
isinstance(content, list)
|
|
824
|
+
and content
|
|
825
|
+
and isinstance(content[0], dict)
|
|
826
|
+
and content[0].get("type") == "tool_reference"
|
|
827
|
+
):
|
|
828
|
+
if not tools:
|
|
829
|
+
raise ValueError("Tool references require tool specifications")
|
|
830
|
+
text = "<tools>\n"
|
|
831
|
+
for reference in content:
|
|
832
|
+
for tool in tools:
|
|
833
|
+
spec = tool.get("function") or tool
|
|
834
|
+
if spec.get("name") == reference.get("name"):
|
|
835
|
+
text += self._format_tool_spec(tool) + "\n"
|
|
836
|
+
emit_text(text + "</tools>", msg_idx, is_sampled=False, is_content=False)
|
|
837
|
+
else:
|
|
838
|
+
super()._emit_tool_body(
|
|
839
|
+
content,
|
|
840
|
+
msg_idx,
|
|
841
|
+
tools=tools,
|
|
842
|
+
emit_special=emit_special,
|
|
843
|
+
emit_text=emit_text,
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
def _tool_results(
|
|
731
847
|
self, messages: list[Message], block_start: int
|
|
732
|
-
) -> list[int]:
|
|
733
|
-
"""
|
|
848
|
+
) -> list[tuple[int, Any]]:
|
|
849
|
+
"""Unpack and order tool results, retaining each input message's owner."""
|
|
734
850
|
block_end = block_start
|
|
735
851
|
while (
|
|
736
852
|
block_end + 1 < len(messages)
|
|
@@ -738,31 +854,55 @@ class GLM53Renderer(GLM5Renderer):
|
|
|
738
854
|
):
|
|
739
855
|
block_end += 1
|
|
740
856
|
|
|
857
|
+
def identifier(obj: Mapping[str, Any]) -> str:
|
|
858
|
+
return str(obj.get("tool_call_id") or obj.get("id") or "")
|
|
859
|
+
|
|
860
|
+
results: list[tuple[int, str, Any]] = []
|
|
861
|
+
for i in range(block_start, block_end + 1):
|
|
862
|
+
content = messages[i].get("content")
|
|
863
|
+
if _is_list_of_outputs(content):
|
|
864
|
+
for entry in content:
|
|
865
|
+
entry = entry if isinstance(entry, dict) else {}
|
|
866
|
+
results.append((i, identifier(entry), entry.get("output")))
|
|
867
|
+
else:
|
|
868
|
+
results.append((i, identifier(messages[i]), content))
|
|
869
|
+
|
|
741
870
|
previous = messages[block_start - 1] if block_start else {}
|
|
742
871
|
calls = (
|
|
743
872
|
previous.get("tool_calls") if previous.get("role") == "assistant" else None
|
|
744
873
|
)
|
|
745
|
-
if
|
|
746
|
-
|
|
874
|
+
if calls:
|
|
875
|
+
call_ids = [identifier(call) for call in calls]
|
|
876
|
+
result_ids = [result_id for _, result_id, _ in results]
|
|
877
|
+
if (
|
|
878
|
+
all(call_ids)
|
|
879
|
+
and all(result_ids)
|
|
880
|
+
and len(set(call_ids)) == len(call_ids)
|
|
881
|
+
and len(set(result_ids)) == len(result_ids)
|
|
882
|
+
and set(result_ids) <= set(call_ids)
|
|
883
|
+
):
|
|
884
|
+
by_id = {result_id: (i, content) for i, result_id, content in results}
|
|
885
|
+
return [by_id[call_id] for call_id in call_ids if call_id in by_id]
|
|
886
|
+
return [(i, content) for i, _, content in results]
|
|
747
887
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
]
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
888
|
+
def bridge_to_next_turn(
|
|
889
|
+
self,
|
|
890
|
+
previous_prompt_ids: list[int],
|
|
891
|
+
previous_completion_ids: list[int],
|
|
892
|
+
new_messages: list[Message],
|
|
893
|
+
*,
|
|
894
|
+
tools: list[ToolSpec] | None = None,
|
|
895
|
+
) -> RenderedTokens | None:
|
|
896
|
+
tool_messages = [m for m in new_messages if m.get("role") == "tool"]
|
|
897
|
+
if len(tool_messages) > 1 or any(
|
|
898
|
+
_is_list_of_outputs(m.get("content")) for m in tool_messages
|
|
758
899
|
):
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
return [by_id[identifier] for identifier in call_ids]
|
|
900
|
+
# Sorting needs the original assistant's tool-call IDs. Those IDs
|
|
901
|
+
# cannot be recovered from the raw sampled completion tokens.
|
|
902
|
+
return None
|
|
903
|
+
return super().bridge_to_next_turn(
|
|
904
|
+
previous_prompt_ids, previous_completion_ids, new_messages, tools=tools
|
|
905
|
+
)
|
|
766
906
|
|
|
767
907
|
def render(
|
|
768
908
|
self,
|
|
@@ -854,3 +994,154 @@ class GLM53Renderer(GLM5Renderer):
|
|
|
854
994
|
emit_special(
|
|
855
995
|
self._tool_call_end_tok, msg_idx, is_sampled=True, is_content=True
|
|
856
996
|
)
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
_IMAGE_TYPES = ("image", "image_url")
|
|
1000
|
+
_UNSUPPORTED_MEDIA_TYPES = ("video", "video_url", "audio", "audio_url", "input_audio")
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
class GLM53FlashRenderer(GLM53Renderer):
|
|
1004
|
+
"""Deterministic message → token renderer for GLM-5.3 Flash.
|
|
1005
|
+
|
|
1006
|
+
GLM-5.3 Flash shares GLM-5.3's text template and adds images: the template
|
|
1007
|
+
renders each image part of a system, user or tool message as
|
|
1008
|
+
``<|begin_of_image|><|image|><|end_of_image|>``.
|
|
1009
|
+
|
|
1010
|
+
Renders the logical prompt (``process_multimodal=False``): one ``<|image|>``
|
|
1011
|
+
per image, which the inference engine expands to the image's token count
|
|
1012
|
+
from the raw image. Rendering with expanded placeholders and processed
|
|
1013
|
+
pixels (``process_multimodal=True``) is not implemented yet. Video and
|
|
1014
|
+
audio are not supported.
|
|
1015
|
+
"""
|
|
1016
|
+
|
|
1017
|
+
_config_cls = GLM53FlashRendererConfig
|
|
1018
|
+
supports_process_multimodal = True
|
|
1019
|
+
|
|
1020
|
+
def __init__(
|
|
1021
|
+
self,
|
|
1022
|
+
tokenizer: Tokenizer,
|
|
1023
|
+
config: GLM53FlashRendererConfig | None = None,
|
|
1024
|
+
):
|
|
1025
|
+
super().__init__(tokenizer, config)
|
|
1026
|
+
self._begin_of_image = self._token_id("<|begin_of_image|>")
|
|
1027
|
+
self._image = self._token_id("<|image|>")
|
|
1028
|
+
self._end_of_image = self._token_id("<|end_of_image|>")
|
|
1029
|
+
|
|
1030
|
+
@property
|
|
1031
|
+
def mm_token_type_id_map(self) -> dict[int, int]:
|
|
1032
|
+
return {self._image: 1}
|
|
1033
|
+
|
|
1034
|
+
@staticmethod
|
|
1035
|
+
def _validate_media(messages: list[Message], *, process_multimodal: bool) -> None:
|
|
1036
|
+
for message in messages:
|
|
1037
|
+
contents = (
|
|
1038
|
+
_tool_contents(message.get("content"))
|
|
1039
|
+
if message.get("role") == "tool"
|
|
1040
|
+
else [message.get("content")]
|
|
1041
|
+
)
|
|
1042
|
+
for content in contents:
|
|
1043
|
+
if not isinstance(content, list):
|
|
1044
|
+
continue
|
|
1045
|
+
for item in content:
|
|
1046
|
+
GLM53FlashRenderer._validate_media_part(
|
|
1047
|
+
item,
|
|
1048
|
+
role=message.get("role"),
|
|
1049
|
+
process_multimodal=process_multimodal,
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
@staticmethod
|
|
1053
|
+
def _validate_media_part(
|
|
1054
|
+
item: Any, *, role: str | None, process_multimodal: bool
|
|
1055
|
+
) -> None:
|
|
1056
|
+
if not isinstance(item, dict):
|
|
1057
|
+
return
|
|
1058
|
+
kind = item.get("type")
|
|
1059
|
+
if kind in _UNSUPPORTED_MEDIA_TYPES:
|
|
1060
|
+
raise NotImplementedError(
|
|
1061
|
+
f"GLM53FlashRenderer does not support {kind!r} content"
|
|
1062
|
+
)
|
|
1063
|
+
if kind in _IMAGE_TYPES:
|
|
1064
|
+
if role not in ("system", "user", "tool"):
|
|
1065
|
+
raise NotImplementedError(
|
|
1066
|
+
f"GLM53FlashRenderer does not support images in {role!r} messages"
|
|
1067
|
+
)
|
|
1068
|
+
if process_multimodal:
|
|
1069
|
+
raise NotImplementedError(
|
|
1070
|
+
"GLM53FlashRenderer renders images only with "
|
|
1071
|
+
"process_multimodal=False (one placeholder per image)"
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
def messages_in_prompt_order(self, messages: list[Message]) -> list[Message]:
|
|
1075
|
+
"""Order raw-media inputs like the full render's tool-result blocks."""
|
|
1076
|
+
self._validate_media(messages, process_multimodal=False)
|
|
1077
|
+
ordered: list[Message] = []
|
|
1078
|
+
for i, message in enumerate(messages):
|
|
1079
|
+
if message.get("role") != "tool":
|
|
1080
|
+
ordered.append(message)
|
|
1081
|
+
elif i == 0 or messages[i - 1].get("role") != "tool":
|
|
1082
|
+
for j, content in self._tool_results(messages, i):
|
|
1083
|
+
result = messages[j].copy()
|
|
1084
|
+
result["content"] = content
|
|
1085
|
+
ordered.append(result)
|
|
1086
|
+
return ordered
|
|
1087
|
+
|
|
1088
|
+
def _emit_body(
|
|
1089
|
+
self, content: Any, msg_idx: int, *, emit_special, emit_text
|
|
1090
|
+
) -> None:
|
|
1091
|
+
if not isinstance(content, list):
|
|
1092
|
+
super()._emit_body(
|
|
1093
|
+
content, msg_idx, emit_special=emit_special, emit_text=emit_text
|
|
1094
|
+
)
|
|
1095
|
+
return
|
|
1096
|
+
# Consecutive text parts are joined before encoding, as the template does.
|
|
1097
|
+
text = ""
|
|
1098
|
+
for item in content:
|
|
1099
|
+
if isinstance(item, str):
|
|
1100
|
+
text += item
|
|
1101
|
+
elif not isinstance(item, dict):
|
|
1102
|
+
continue
|
|
1103
|
+
elif item.get("type") == "text":
|
|
1104
|
+
text += item.get("text", "")
|
|
1105
|
+
elif item.get("type") in _IMAGE_TYPES:
|
|
1106
|
+
emit_text(text, msg_idx, is_sampled=False, is_content=True)
|
|
1107
|
+
text = ""
|
|
1108
|
+
# The placeholder stands for caller-provided image data (body
|
|
1109
|
+
# content); the markers around it are template scaffolding.
|
|
1110
|
+
emit_special(
|
|
1111
|
+
self._begin_of_image, msg_idx, is_sampled=False, is_content=False
|
|
1112
|
+
)
|
|
1113
|
+
emit_special(self._image, msg_idx, is_sampled=False, is_content=True)
|
|
1114
|
+
emit_special(
|
|
1115
|
+
self._end_of_image, msg_idx, is_sampled=False, is_content=False
|
|
1116
|
+
)
|
|
1117
|
+
emit_text(text, msg_idx, is_sampled=False, is_content=True)
|
|
1118
|
+
|
|
1119
|
+
def render(
|
|
1120
|
+
self,
|
|
1121
|
+
messages: list[Message],
|
|
1122
|
+
*,
|
|
1123
|
+
tools: list[ToolSpec] | None = None,
|
|
1124
|
+
add_generation_prompt: bool = False,
|
|
1125
|
+
process_multimodal: bool = True,
|
|
1126
|
+
) -> RenderedTokens:
|
|
1127
|
+
self._validate_media(messages, process_multimodal=process_multimodal)
|
|
1128
|
+
return super().render(
|
|
1129
|
+
messages, tools=tools, add_generation_prompt=add_generation_prompt
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1132
|
+
def bridge_to_next_turn(
|
|
1133
|
+
self,
|
|
1134
|
+
previous_prompt_ids: list[int],
|
|
1135
|
+
previous_completion_ids: list[int],
|
|
1136
|
+
new_messages: list[Message],
|
|
1137
|
+
*,
|
|
1138
|
+
tools: list[ToolSpec] | None = None,
|
|
1139
|
+
previous_multi_modal_data: MultiModalData | None = None,
|
|
1140
|
+
process_multimodal: bool = True,
|
|
1141
|
+
) -> RenderedTokens | None:
|
|
1142
|
+
self._validate_media(new_messages, process_multimodal=process_multimodal)
|
|
1143
|
+
# Logical prompts carry no processed image data, so nothing from
|
|
1144
|
+
# ``previous_multi_modal_data`` needs carrying forward.
|
|
1145
|
+
return super().bridge_to_next_turn(
|
|
1146
|
+
previous_prompt_ids, previous_completion_ids, new_messages, tools=tools
|
|
1147
|
+
)
|