renderers 0.1.8.dev27__tar.gz → 0.1.8.dev29__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.8.dev27 → renderers-0.1.8.dev29}/PKG-INFO +1 -1
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/__init__.py +45 -14
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/_version.py +2 -2
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/parsing.py +17 -11
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_tool_arg_type_preservation.py +72 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/.github/workflows/publish-dev.yml +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/.github/workflows/publish.yml +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/.github/workflows/style.yml +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/.github/workflows/test.yml +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/.gitignore +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/.pre-commit-config.yaml +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/LICENSE +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/README.md +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/docs/renderer-config.md +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/examples/README.md +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/examples/sglang/multiturn_generate_sglang.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/examples/sglang/online_multiturn_sglang.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/examples/tinker/multiturn_generate_tinker.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/examples/transformers/multiturn_generate_transformers.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/examples/vllm/multiturn_generate_vllm.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/pyproject.toml +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/base.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/client.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/configs.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/deepseek_v3.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/default.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/glm45.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/glm5.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/gpt_oss.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/kimi_k2.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/kimi_k25.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/laguna_xs2.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/minimax_m2.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/nemotron3.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/parsers.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/qwen3.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/qwen35.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/qwen36.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/renderers/qwen3_vl.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/conftest.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_bridge.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_build_helpers.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_client.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_gpt_oss_harmony_parity.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_incremental.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_is_content.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_kimi_k25_tool_schema.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_load_tokenizer.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_load_tokenizer_fastokens.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_message_indices.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_multimodal.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_parse_response.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_parse_response_robustness.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_parsers.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_preserve_thinking.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_qwen35_size_coverage.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_render_ids.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_renderer_config.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_renderer_config_parity.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_roundtrip.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_sampled_mask.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/tests/test_tokens_per_message.py +0 -0
- {renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/uv.lock +0 -0
|
@@ -59,20 +59,51 @@ from renderers.configs import (
|
|
|
59
59
|
Qwen3VLRendererConfig,
|
|
60
60
|
RendererConfig,
|
|
61
61
|
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
from
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
from renderers
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
|
|
63
|
+
# Concrete renderer classes are lazy-loaded so that consumers needing
|
|
64
|
+
# only the config layer (``RendererConfig`` discriminated union) don't
|
|
65
|
+
# pay the ``transformers`` import cost. Each renderer module does
|
|
66
|
+
# ``from transformers.tokenization_utils import PreTrainedTokenizer``
|
|
67
|
+
# at module level, so eager imports here would drag ``transformers``
|
|
68
|
+
# into every downstream ``import renderers``. ``__getattr__`` (PEP 562)
|
|
69
|
+
# resolves the names on first attribute access, so ``from renderers
|
|
70
|
+
# import DefaultRenderer`` and ``renderers.DefaultRenderer`` both work
|
|
71
|
+
# transparently. ``create_renderer`` doesn't depend on these eager
|
|
72
|
+
# imports — ``renderers.base._populate_registry`` lazy-imports the
|
|
73
|
+
# concrete classes itself when a renderer is instantiated.
|
|
74
|
+
_LAZY_RENDERERS: dict[str, str] = {
|
|
75
|
+
"DeepSeekV3Renderer": "renderers.deepseek_v3",
|
|
76
|
+
"DefaultRenderer": "renderers.default",
|
|
77
|
+
"GLM45Renderer": "renderers.glm45",
|
|
78
|
+
"GLM51Renderer": "renderers.glm5",
|
|
79
|
+
"GLM5Renderer": "renderers.glm5",
|
|
80
|
+
"GptOssRenderer": "renderers.gpt_oss",
|
|
81
|
+
"KimiK25Renderer": "renderers.kimi_k25",
|
|
82
|
+
"KimiK2Renderer": "renderers.kimi_k2",
|
|
83
|
+
"LagunaXS2Renderer": "renderers.laguna_xs2",
|
|
84
|
+
"MiniMaxM2Renderer": "renderers.minimax_m2",
|
|
85
|
+
"Nemotron3Renderer": "renderers.nemotron3",
|
|
86
|
+
"Qwen35Renderer": "renderers.qwen35",
|
|
87
|
+
"Qwen36Renderer": "renderers.qwen36",
|
|
88
|
+
"Qwen3Renderer": "renderers.qwen3",
|
|
89
|
+
"Qwen3VLRenderer": "renderers.qwen3_vl",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def __getattr__(name: str):
|
|
94
|
+
if name in _LAZY_RENDERERS:
|
|
95
|
+
import importlib
|
|
96
|
+
|
|
97
|
+
module = importlib.import_module(_LAZY_RENDERERS[name])
|
|
98
|
+
value = getattr(module, name)
|
|
99
|
+
globals()[name] = value # cache for subsequent lookups
|
|
100
|
+
return value
|
|
101
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def __dir__() -> list[str]:
|
|
105
|
+
return sorted(set(globals().keys()) | set(_LAZY_RENDERERS))
|
|
106
|
+
|
|
76
107
|
|
|
77
108
|
__all__ = [
|
|
78
109
|
"AutoRendererConfig",
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.8.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, 8, '
|
|
21
|
+
__version__ = version = '0.1.8.dev29'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 8, 'dev29')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -65,30 +65,36 @@ def _coerce_arg_value(
|
|
|
65
65
|
"""Coerce a raw ``<arg_value>`` body to its declared type.
|
|
66
66
|
|
|
67
67
|
Returns ``(value, used_json_fallback)``. The boolean is ``True`` only
|
|
68
|
-
when ``json.loads`` was attempted
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
when ``json.loads`` was attempted, raised, AND the schema doesn't
|
|
69
|
+
permit a string. Returning a string verbatim because the schema
|
|
70
|
+
permits strings is NOT a fallback.
|
|
71
71
|
|
|
72
72
|
Rule (matches vLLM / SGLang reference parsers):
|
|
73
73
|
|
|
74
74
|
- If the param's declared ``type`` is ``"string"`` (or single-element
|
|
75
75
|
``["string"]``), return ``text`` verbatim — never ``json.loads``.
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Union types that include ``"string"`` alongside
|
|
81
|
-
attempt ``json.loads`` first so an explicit
|
|
82
|
-
parse; the string branch
|
|
76
|
+
- Otherwise try ``json.loads``. If that fails, return raw ``text``.
|
|
77
|
+
The ``used_json_fallback`` flag is ``True`` only when the schema
|
|
78
|
+
does NOT permit a string — i.e. the fallback is truly suspect.
|
|
79
|
+
|
|
80
|
+
Union types (``anyOf``/``oneOf``) that include ``"string"`` alongside
|
|
81
|
+
other types still attempt ``json.loads`` first so an explicit
|
|
82
|
+
integer / bool can parse; the string branch wins as fallback, and
|
|
83
|
+
landing there is expected — not a malformed-JSON signal.
|
|
83
84
|
"""
|
|
85
|
+
string_is_allowed = False
|
|
84
86
|
if param_schema is not None:
|
|
85
87
|
declared = param_schema.get("type")
|
|
86
88
|
if declared == "string" or declared == ["string"]:
|
|
87
89
|
return text, False
|
|
90
|
+
for branch in param_schema.get("anyOf") or param_schema.get("oneOf") or []:
|
|
91
|
+
if isinstance(branch, dict) and branch.get("type") == "string":
|
|
92
|
+
string_is_allowed = True
|
|
93
|
+
break
|
|
88
94
|
try:
|
|
89
95
|
return json.loads(text), False
|
|
90
96
|
except (json.JSONDecodeError, ValueError):
|
|
91
|
-
return text,
|
|
97
|
+
return text, not string_is_allowed
|
|
92
98
|
|
|
93
99
|
|
|
94
100
|
def _find(ids: list[int], target: int, start: int = 0) -> int:
|
|
@@ -137,3 +137,75 @@ def test_string_arg_preserves_type(model, renderer_name, renderer, args):
|
|
|
137
137
|
assert got == args, (
|
|
138
138
|
f"{model}: tool-arg type drift — sent {args!r}, parser returned {got!r}"
|
|
139
139
|
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Schemas where ``string`` is one branch of a union (``anyOf`` / ``oneOf``).
|
|
143
|
+
# These are common in practice — e.g. ``form_input.value: str | bool`` in
|
|
144
|
+
# Pydantic serialises to ``{"anyOf": [{"type": "string"}, {"type": "boolean"}]}``.
|
|
145
|
+
# Without the union-aware check, the XML parser's ``json.loads`` falls back
|
|
146
|
+
# to raw text for bare strings, but flags the call ``INVALID_JSON`` because
|
|
147
|
+
# no top-level ``type`` key declared a string — silently dropping otherwise
|
|
148
|
+
# valid tool calls in the renderer client.
|
|
149
|
+
UNION_WITH_STRING_SCHEMAS = [
|
|
150
|
+
pytest.param(
|
|
151
|
+
{"anyOf": [{"type": "string"}, {"type": "boolean"}]},
|
|
152
|
+
id="anyOf-string-boolean",
|
|
153
|
+
),
|
|
154
|
+
pytest.param(
|
|
155
|
+
{"anyOf": [{"type": "string"}, {"type": "null"}]},
|
|
156
|
+
id="anyOf-string-null",
|
|
157
|
+
),
|
|
158
|
+
pytest.param(
|
|
159
|
+
{"oneOf": [{"type": "string"}, {"type": "integer"}]},
|
|
160
|
+
id="oneOf-string-integer",
|
|
161
|
+
),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.mark.parametrize("param_schema", UNION_WITH_STRING_SCHEMAS)
|
|
166
|
+
def test_union_with_string_emits_ok_status(
|
|
167
|
+
model, renderer_name, renderer, param_schema
|
|
168
|
+
):
|
|
169
|
+
"""Union schemas containing ``string`` must yield ``status=OK`` when
|
|
170
|
+
the model emits a bare string. Pre-fix, ``_coerce_arg_value`` flagged
|
|
171
|
+
this as ``INVALID_JSON`` because the top-level ``type`` key was
|
|
172
|
+
absent (the string branch was under ``anyOf`` / ``oneOf``)."""
|
|
173
|
+
from renderers.base import ToolCallParseStatus
|
|
174
|
+
|
|
175
|
+
tools = [
|
|
176
|
+
{
|
|
177
|
+
"type": "function",
|
|
178
|
+
"function": {
|
|
179
|
+
"name": "f",
|
|
180
|
+
"description": "Test tool with one union-typed parameter.",
|
|
181
|
+
"parameters": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"properties": {"x": param_schema},
|
|
184
|
+
"required": ["x"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
args = {"x": "abc"}
|
|
190
|
+
msg = {
|
|
191
|
+
"role": "assistant",
|
|
192
|
+
"content": "",
|
|
193
|
+
"tool_calls": [
|
|
194
|
+
{
|
|
195
|
+
"id": "functions.f:0",
|
|
196
|
+
"function": {"name": "f", "arguments": args},
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
}
|
|
200
|
+
completion_ids = _extract_assistant_tokens(renderer, PROMPT, msg, tools=tools)
|
|
201
|
+
parsed = renderer.parse_response(completion_ids, tools=tools)
|
|
202
|
+
|
|
203
|
+
assert parsed.tool_calls, f"{model}: parser returned no tool_calls"
|
|
204
|
+
tc = parsed.tool_calls[0]
|
|
205
|
+
assert tc.status == ToolCallParseStatus.OK, (
|
|
206
|
+
f"{model}: union-with-string schema flagged {tc.status} on bare string"
|
|
207
|
+
)
|
|
208
|
+
got = _normalize_args(tc.arguments)
|
|
209
|
+
assert got == args, (
|
|
210
|
+
f"{model}: tool-arg drift — sent {args!r}, parser returned {got!r}"
|
|
211
|
+
)
|
|
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.8.dev27 → renderers-0.1.8.dev29}/examples/sglang/multiturn_generate_sglang.py
RENAMED
|
File without changes
|
|
File without changes
|
{renderers-0.1.8.dev27 → renderers-0.1.8.dev29}/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
|