Adds renderers.KimiK31Pro() and tokenizers.KimiK3Tokenizer() for Kimi K3.1-Pro. The default reasoning effort is max; use .high(), .low(), .disable_thinking(), or .no_reasoning_effort() for other supported modes.
Changes
None.
Fixed
Kimi: Keeps KimiK2 and KimiK25 streamed answer indices consistent with the completed message after an empty thinking block.
GLM: Trains the correct assistant-turn ending in Glm52, Glm53, and Glm53Flash supervised examples: <|observation|> after tool calls and <|user|> otherwise, matching the next prompt. This changes the last trained token of GLM SFT examples.
Qwen: Corrects Qwen35Tokenizer token IDs for more text. This changes rendered tokens in Qwen3.5 and Qwen3.8 prompts and SFT examples.
Use renderers.Inkling() for Inkling and Inkling-Small, or renderers.Inkling2() for Inkling 2 Small. Each builds its matching tokenizer. Set thinking effort with render_for_completion_with_effort(messages, effort).
Use tokenizers.H200kBaseChatTokenizer(), the Inkling 2 Small tokenizer, with low-level TMLv0 rendering: v0.Renderer(tokenizers.H200kBaseChatTokenizer()).
Changes
None.
Fixed
GLM: Corrects GlmTokenizer token IDs for some text. This changes rendered tokens for Glm52, Glm53, and Glm53Flash.
Qwen: Corrects Qwen35Tokenizer token IDs for some text. This changes rendered tokens for Qwen35 and Qwen38.
Deprecations
None.
Breaking changes
parser.parse_tokens() and parser.parse() now return ParseResult instead of a list. For list-specific operations, replace messages = parser.parse_tokens(tokens) with messages = parser.parse_tokens(tokens).messages; iteration and integer indexing still work on the result. result.token_counts reports counts per completed message, and streaming completion updates expose num_tokens.
renderers.DeepSeekR1 and tokenizers.DeepSeekR1Tokenizer are removed. Select a supported model and its matching renderer/tokenizer before upgrading; applications that still require these R1 APIs must remain on 0.3.0.
A v0.Renderer pickled with 0.3.0 or earlier raises TypeError when loaded with 0.4.0. Recreate and re-save renderers after upgrading, and use matching package versions in workers that exchange them.
Parse sampled tokens incrementally with parser.parse_stream(). Inside its context manager, call stream.feed(token_ids) for each chunk and stream.finish() at completion; each returns ParseUpdate objects.
Continue a conversation without re-encoding its sampled prefix using renderer.bridge_to_next_turn(prefix, message_history, next_messages). It returns the combined token spans, the appended suffix, and a parser for the next turn.
Use renderers.Nemotron3VL() and tokenizers.Nemotron3VLTokenizer() for Nemotron 3.5 Super VL, including image inputs. Call .disable_thinking() for the non-thinking preset.
Preserve DeepSeek-V4.1 reasoning across turns with renderers.DeepSeekV41.preserve_thinking_history() or DeepSeekV41(strip_thinking_from_history=False).
Use chat.ParseErrorContent(error_message=..., sampled_tokens=...) to record a failed parse in a conversation. Renderers write only its error_message into the prompt. DeepSeek-V3.1 does not accept it, and DeepSeek-V4, DeepSeek-V4.1, GLM, and GPT-OSS accept it only in user messages.
Convert OpenAI Chat Completions messages with MessageList.from_openai_chat_dicts(messages, tools=...) and convert back with to_openai_chat_dicts(). A top-level reasoning_content becomes reasoning, and the request's tools list becomes a tool declaration. OpenAIMessage has the same methods.
Changes
None.
Fixed
Accepts repeated argument names when constructing or copying InvokeTool calls, such as calls returned by the parser. 0.2.x raised ValueError.
Keeps assistant dictionaries with content: [] as empty assistant turns during conversion, instead of dropping them.
Rejects zero-width or zero-height image pointers during image-dictionary conversion instead of failing later during rendering.
Encodes adjacent same-weight text together in Qwen and Nemotron supervised targets, so the tokens match what the model samples. This changes SFT tokens.
Corrects whitespace, newlines, and tool-result order in Qwen, Nemotron, GLM, and DeepSeek-V4 prompts. This changes prompt tokens for some conversations.
Smaller parsing fixes for GLM, Nemotron, DeepSeek-V4, GPT-OSS, and Kimi: unfinished tool calls, string arguments, message authors, and adjacent assistant turns.
Deprecations
The from_oss_* and to_oss_* methods of OpenAIMessage and MessageList are deprecated and emit DeprecationWarning. Use the *_openai_chat_* methods, or tinker_cookbook.third_party.tml_renderers_compat for cookbook-format messages. No removal version or date is announced.
Breaking changes
The TMLv0, Qwen3, Qwen3.5, Qwen3.8, Nemotron 3, DeepSeek-R1, and DeepSeek-V3.1 parsers now raise when a completion stops inside a message, instead of returning only the completed messages. The error is a ValueError (v0.ParseError for TMLv0). Catch ValueError and read error.completed_messages to recover the completed messages. DeepSeek-V3, V4, and V4.1 errors now carry completed_messages too. This applies to both parse_tokens() and parse().
Glm53 and Glm53Flash now preserve historical reasoning by default (clear_thinking=False), changing prompts and supervised examples. Pass clear_thinking=True, including to .high() or .low(), to retain the previous behavior consistently in sampling and training.
renderers.GptOss() now defaults to reasoning_effort="medium" instead of "low". Pass reasoning_effort="low" to retain the previous prompt setting.
Fixed
Allows renderers and parsers to be used from a Python thread other than the one that created them, without raising PanicException.
Prevents a deadlock when multiple threads construct their first tokenizer concurrently.
When a reply is cut off after one or more finished messages, parse_tokens() and parse() return the finished messages instead of raising. This applies to TMLv0, Qwen3, Qwen3.5, Qwen3.8, Nemotron 3, DeepSeek-R1, and DeepSeek-V3.1 in thinking mode. A reply cut off in its first message still raises.
Places system text at a fixed position in Qwen and Nemotron prompts that declare tools, whatever the message order: before the tool declarations for Qwen3 and Nemotron 3, and after them for Qwen3.5 and Qwen3.8, with a blank line before it. This changes prompt tokens, for example for a Qwen3.5 system message given before the tool declaration.
Uses the correct special-token IDs in Qwen3.5 and Nemotron tool-declaration examples. User-provided text and schema strings remain literal text.
TMLv0: Parses an empty message as Text("") and keeps every value of a repeated tool-call argument, instead of raising v0.ParseError.
Deprecations
None.
Breaking changes
TMLv0: Writes the tool name in the header of each structured tool-call message, for example <|message_model|>get_weather<|content_invoke_tool_json|>. This restores the 0.1.0 tokens for tool calls converted from OpenAI-style messages, and adds the name for tool calls without an author name. A tool call whose author.name differs from the tool name now raises ValueError; unset author.name or set it to the tool name.
Introducing support for other model families in tml-renderers: Qwen3, Qwen3.5, Qwen3.8, DeepSeek-V3, DeepSeek-R1, DeepSeek-V3.1, DeepSeek-V4, DeepSeek-V4.1, Kimi K2, Kimi K2.5, GLM-5.2, GLM-5.3, GLM-5.3-Flash, GPT-OSS, and Nemotron 3.
Set chat.ImagePointer.override_width and override_height to request a rendering resolution different from the stored image dimensions. Rendered image spans carry the resolved virtual_width and virtual_height.
Normalize native or OpenAI-style message sequences with chat.MessageList.from_messages(). The helper also accepts an existing MessageList.
Pass a tool failure as a content part such as {"type": "tool_error", "text": "command failed"} in OpenAI-style messages. It converts to chat.ToolError, and TMLv0 renders it as a tool error.
Pickle renderers, for example to send them to worker processes. This includes v0.Renderer.
Use renderer.tokenizer to get a renderer's tokenizer, and type code that accepts any renderer with tml_renderers.Renderer.
Changes
TMLv0: Parsed tool calls now carry a tool_call_id numbered in call order, for example functions.read:0. 0.1.0 returned None.
Fixed
TMLv0: Rejects TML image pointers with non-positive dimensions instead of silently rendering an image with zero tokens.
Preserves WAV audio through OSS message round trips.
TMLv0: Rejects sampled tool calls with an unknown field or an empty tool name with v0.ParseError. 0.1.0 dropped the field, for example "trace" in {"name": "read", "args": {}, "trace": "abc"}, and accepted the empty name.
Deprecations
tokenizers.o200k_base_chat() is deprecated; use tokenizers.O200kBaseChatTokenizer(). The old factory remains available and emits DeprecationWarning. No removal version or date is announced.
tml_renderers.tinker.TokenSpanWrapper and RawTokenSpan remain as deprecated compatibility names. Annotate rendered spans with chat.TokenSpan and access the contained value through .span. No removal version or date is announced.
Breaking changes
The old incremental parser methods parse_token(), parse_updates(), flush_updates(), flush(), reset(), and is_at_message_boundary() are removed. Accumulate a completion and pass it to parse_tokens() or parse(); each call handles a complete parsing attempt.
parse_errors.UnterminatedMessageError is replaced by UnterminatedStructureError and UnterminatedContentError. Update imports and checks of ParseError.kind to distinguish incomplete structure from incomplete content.
TMLv0:parse_tokens() and parse() now raise v0.ParseError when a completion stops inside a message, even when earlier messages completed. 0.1.0 returned only the completed messages, or []. A completion with no message, such as a lone end-of-sampling token, also raises (EmptyResponseError). Catch v0.ParseError when a completion can reach a token limit.
chat.InvokeTool raises ValueError for an empty tool name, a repeated argument name, or raw text combined with a structured call, on construction and on copy(). 0.1.0 accepted these. Remove repeated arguments, and use chat.InvokeTool("raw text") for a raw-text call.