Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
reject_assistant_in_extension,
trim_to_turn_close,
)
from renderers.stability import (
FULLY_STABLE,
OPAQUE,
STABLE_IN_TOOL_CYCLE,
Boundary,
RenderStability,
)
from renderers.deepseek_v3 import DeepSeekV3Renderer
from renderers.default import DefaultRenderer
from renderers.glm5 import GLM5Renderer
Expand All @@ -38,6 +45,7 @@
"ContentPart",
"DeepSeekV3Renderer",
"DefaultRenderer",
"FULLY_STABLE",
"GLM45Renderer",
"GLM5Renderer",
"GptOssRenderer",
Expand All @@ -46,6 +54,7 @@
"Message",
"MiniMaxM2Renderer",
"Nemotron3Renderer",
"OPAQUE",
"ParsedResponse",
"Qwen3Renderer",
"Qwen3VLRenderer",
Expand All @@ -55,11 +64,14 @@
"RenderedTokens",
"Renderer",
"RendererPool",
"RenderStability",
"STABLE_IN_TOOL_CYCLE",
"TextPart",
"ThinkingPart",
"ToolCall",
"ToolCallFunction",
"ToolSpec",
"Boundary",
"build_training_sample",
"build_trajectory_step",
"create_renderer",
Expand Down
7 changes: 7 additions & 0 deletions renderers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from dataclasses import dataclass, field
from typing import Any, Callable, Literal, Protocol, TypedDict, runtime_checkable

from renderers.stability import RenderStability

logger = logging.getLogger("renderers.base")


Expand Down Expand Up @@ -133,6 +135,11 @@ def with_completion(
class Renderer(Protocol):
"""Owns message ↔ token conversion for a specific model family."""

@property
def stability(self) -> RenderStability:
"""Declared boundaries where appending a message preserves the prefix."""
...

def render(
self,
messages: list[Message],
Expand Down
6 changes: 6 additions & 0 deletions renderers/deepseek_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_deepseek_v3
from renderers.stability import OPAQUE, RenderStability

# Fullwidth vertical bar used in DeepSeek special token names.
_SEP = "\uff5c" # | (U+FF5C)
Expand Down Expand Up @@ -81,6 +82,11 @@ def __init__(
self._tool_output_begin = self._get_special_token(f"tool{_US}output{_US}begin")
self._tool_output_end = self._get_special_token(f"tool{_US}output{_US}end")

@property
def stability(self) -> RenderStability:
# TODO(#41): audit DeepSeek V3 before tightening this declaration.
return OPAQUE

# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions renderers/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
get_reasoning_parser,
get_tool_parser,
)
from renderers.stability import OPAQUE, RenderStability


def _decode_tool_call_arguments(messages: list) -> list:
Expand Down Expand Up @@ -114,6 +115,10 @@ def __init__(
preserve_thinking_between_tool_calls
)

@property
def stability(self) -> RenderStability:
return OPAQUE

@property
def supports_tools(self) -> bool:
return self._tool_parser is not None
Expand Down
6 changes: 6 additions & 0 deletions renderers/glm45.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
should_preserve_past_thinking,
)
from renderers.parsing import parse_glm
from renderers.stability import OPAQUE, RenderStability

_TOOLS_HEADER = (
"\n# Tools\n\n"
Expand Down Expand Up @@ -80,6 +81,11 @@ def __init__(
self._arg_value = self._token_id("<arg_value>")
self._arg_value_end = self._token_id("</arg_value>")

@property
def stability(self) -> RenderStability:
# TODO(#41): audit GLM-4.5 before tightening this declaration.
return OPAQUE

def _token_id(self, token: str) -> int:
tid = self._tokenizer.convert_tokens_to_ids(token)
assert isinstance(tid, int) and tid != self._tokenizer.unk_token_id, (
Expand Down
7 changes: 7 additions & 0 deletions renderers/glm5.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
should_preserve_past_thinking,
)
from renderers.parsing import parse_glm
from renderers.stability import FULLY_STABLE, STABLE_IN_TOOL_CYCLE, RenderStability

_TOOLS_HEADER = (
"\n# Tools\n\n"
Expand Down Expand Up @@ -86,6 +87,12 @@ def __init__(
self._arg_value = self._token_id("<arg_value>")
self._arg_value_end = self._token_id("</arg_value>")

@property
def stability(self) -> RenderStability:
if self._preserve_all_thinking:
return FULLY_STABLE
return STABLE_IN_TOOL_CYCLE

def _token_id(self, token: str) -> int:
tid = self._tokenizer.convert_tokens_to_ids(token)
assert isinstance(tid, int) and tid != self._tokenizer.unk_token_id, (
Expand Down
6 changes: 6 additions & 0 deletions renderers/gpt_oss.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_gpt_oss
from renderers.stability import OPAQUE, RenderStability


def _reasoning_effort(effort: str | None) -> ReasoningEffort:
Expand Down Expand Up @@ -170,6 +171,11 @@ def __init__(
self._message = self._token_id("<|message|>")
self._constrain = self._token_id("<|constrain|>")

@property
def stability(self) -> RenderStability:
# TODO(#41): audit GPT-OSS harmony before tightening this declaration.
return OPAQUE

# ── token utilities ──────────────────────────────────────────────────────

def _token_id(self, token: str) -> int:
Expand Down
6 changes: 6 additions & 0 deletions renderers/kimi_k2.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_kimi_k2
from renderers.stability import OPAQUE, RenderStability

_DEFAULT_SYSTEM = "You are Kimi, an AI assistant created by Moonshot AI."

Expand Down Expand Up @@ -63,6 +64,11 @@ def __init__(
self._tool_call_argument_begin = self._token_id("<|tool_call_argument_begin|>")
self._tool_call_end = self._token_id("<|tool_call_end|>")

@property
def stability(self) -> RenderStability:
# TODO(#41): audit Kimi K2 before tightening this declaration.
return OPAQUE

def _token_id(self, token: str) -> int:
tid = self._tokenizer.convert_tokens_to_ids(token)
assert isinstance(tid, int) and tid != self._tokenizer.unk_token_id, (
Expand Down
6 changes: 6 additions & 0 deletions renderers/kimi_k25.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
should_preserve_past_thinking,
trim_to_turn_close,
)
from renderers.stability import OPAQUE, RenderStability

# ---------------------------------------------------------------------------
# Constants
Expand Down Expand Up @@ -545,6 +546,11 @@ def __init__(
# The stop token for generation
self._endoftext: int | None = self._try_token_id("<|endoftext|>")

@property
def stability(self) -> RenderStability:
# TODO(#41): audit Kimi K2.5 before tightening this declaration.
return OPAQUE

# ------------------------------------------------------------------
# Token helpers
# ------------------------------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions renderers/minimax_m2.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_minimax
from renderers.stability import OPAQUE, RenderStability

_DEFAULT_SYSTEM = (
"You are a helpful assistant. Your name is MiniMax-M2.5 and is built by MiniMax."
Expand Down Expand Up @@ -78,6 +79,11 @@ def __init__(
self._tool_call_tok = self._token_id("<minimax:tool_call>")
self._tool_call_end_tok = self._token_id("</minimax:tool_call>")

@property
def stability(self) -> RenderStability:
# TODO(#41): audit MiniMax M2 before tightening this declaration.
return OPAQUE

def _token_id(self, token: str) -> int:
tid = self._tokenizer.convert_tokens_to_ids(token)
assert isinstance(tid, int) and tid != self._tokenizer.unk_token_id, (
Expand Down
6 changes: 6 additions & 0 deletions renderers/nemotron3.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_qwen35
from renderers.stability import OPAQUE, RenderStability

# ---------------------------------------------------------------------------
# Tool system prompt constants
Expand Down Expand Up @@ -104,6 +105,11 @@ def __init__(
self._tool_response = self._token_id("<tool_response>")
self._tool_response_end = self._token_id("</tool_response>")

@property
def stability(self) -> RenderStability:
# TODO(#41): audit Nemotron 3 before tightening this declaration.
return OPAQUE

def _token_id(self, token: str, *, optional: bool = False) -> int | None:
tid = self._tokenizer.convert_tokens_to_ids(token)
if not isinstance(tid, int) or tid == self._tokenizer.unk_token_id:
Expand Down
7 changes: 7 additions & 0 deletions renderers/qwen3.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_qwen3
from renderers.stability import FULLY_STABLE, STABLE_IN_TOOL_CYCLE, RenderStability

_TOOLS_HEADER = (
"# Tools\n\n"
Expand Down Expand Up @@ -67,6 +68,12 @@ def __init__(
self._tool_response = self._token_id("<tool_response>")
self._tool_response_end = self._token_id("</tool_response>")

@property
def stability(self) -> RenderStability:
if self._preserve_all_thinking:
return FULLY_STABLE
return STABLE_IN_TOOL_CYCLE

def _token_id(self, token: str) -> int:
tid = self._tokenizer.convert_tokens_to_ids(token)
assert isinstance(tid, int) and tid != self._tokenizer.unk_token_id, (
Expand Down
7 changes: 7 additions & 0 deletions renderers/qwen35.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_qwen35
from renderers.stability import FULLY_STABLE, STABLE_IN_TOOL_CYCLE, RenderStability

# ---------------------------------------------------------------------------
# Tool system prompt constants (must match the Jinja template exactly)
Expand Down Expand Up @@ -114,6 +115,12 @@ def __init__(
self._tool_response = self._token_id("<tool_response>")
self._tool_response_end = self._token_id("</tool_response>")

@property
def stability(self) -> RenderStability:
if self._preserve_all_thinking:
return FULLY_STABLE
return STABLE_IN_TOOL_CYCLE

def _token_id(self, token: str) -> int:
tid = self._tokenizer.convert_tokens_to_ids(token)
assert isinstance(tid, int) and tid != self._tokenizer.unk_token_id, (
Expand Down
6 changes: 6 additions & 0 deletions renderers/qwen36.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,17 @@
from typing import Any

from renderers.qwen35 import Qwen35Renderer
from renderers.stability import OPAQUE, RenderStability


class Qwen36Renderer(Qwen35Renderer):
"""Deterministic message → token renderer for Qwen3.6 models."""

@property
def stability(self) -> RenderStability:
# TODO(#41): audit Qwen3.6 before tightening this declaration.
return OPAQUE

@staticmethod
def _render_arg_value(arg_value: Any) -> str:
if isinstance(arg_value, str):
Expand Down
7 changes: 7 additions & 0 deletions renderers/qwen3_vl.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
trim_to_turn_close,
)
from renderers.parsing import parse_qwen3
from renderers.stability import FULLY_STABLE, STABLE_IN_TOOL_CYCLE, RenderStability

_TOOLS_HEADER = (
"# Tools\n\n"
Expand Down Expand Up @@ -67,6 +68,12 @@ def __init__(
self._tool_response = self._token_id("<tool_response>")
self._tool_response_end = self._token_id("</tool_response>")

@property
def stability(self) -> RenderStability:
if self._preserve_all_thinking:
return FULLY_STABLE
return STABLE_IN_TOOL_CYCLE

def _token_id(self, token: str) -> int:
tid = self._tokenizer.convert_tokens_to_ids(token)
assert isinstance(tid, int) and tid != self._tokenizer.unk_token_id, (
Expand Down
18 changes: 18 additions & 0 deletions renderers/stability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

Boundary = Literal["user", "assistant", "tool"]


@dataclass(frozen=True)
class RenderStability:
"""Declared append-boundaries that preserve an existing rendered prefix."""

preserves_through: frozenset[Boundary]


FULLY_STABLE = RenderStability(frozenset({"user", "assistant", "tool"}))
STABLE_IN_TOOL_CYCLE = RenderStability(frozenset({"tool"}))
OPAQUE = RenderStability(frozenset())
Loading