Utils
steerability.utils
answers
Extracting a canonical numeric answer from a decoded model response.
extract_numeric_answer(text)
Extract the final numeric answer from a response and canonicalize it.
The last value anchored on an Answer: label or a \boxed{...} wrapper wins. Anchored values
may be integers, decimals, plain fractions (2/3), or LaTeX fractions (\frac{2}{3},
\dfrac{2}{3}), optionally wrapped in $ or * markers. When no anchored value is present,
the last number-like token in the text is used instead. Thousands separators are ignored.
The extracted value is canonicalized through fractions.Fraction, so equivalent forms map to
one string (4/6, \frac{2}{3}, and 2/3 all yield "2/3"; exact decimals merge with their
fraction, e.g. 0.5 yields "1/2"). This makes the result usable as an agreement key, e.g. as
the answer_extractor of MajorityVoteScorer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The decoded response to extract from. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The canonical answer string, or the empty string when no value is found or the anchored |
str
|
value does not parse (e.g. a zero denominator). |
Source code in steerability/utils/answers.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
model_utils
find_project_root(current_path)
Finds root dir by looking for pyproject.toml
Source code in steerability/utils/model_utils.py
4 5 6 7 8 9 10 | |
is_valid_model(config, model_id, service)
Source code in steerability/utils/model_utils.py
13 14 15 16 17 18 | |
optional
Optional-dependency declarations and import guard.
Single source of truth mapping optional third-party modules to the extra that
provides them. The registry consults it for skip messages; feature modules call
require() at their import boundary to fail with an actionable hint.
OPTIONAL_MODULE_EXTRAS = {'mergekit': 'merging', 'inspect_ai': 'eval', 'inspect_evals': 'eval', 'vllm': 'vllm', 'vllm_hook_plugins': 'vllm', 'matplotlib': 'eval', 'seaborn': 'eval'}
module-attribute
require(module_name)
Import module_name or raise ModuleNotFoundError naming the extra that provides it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
module_name
|
str
|
The importable module name (e.g., |
required |
Returns:
| Type | Description |
|---|---|
ModuleType
|
The imported module. |
Raises:
| Type | Description |
|---|---|
ModuleNotFoundError
|
If the module is not installed. The message names the
|
Source code in steerability/utils/optional.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
rendering
General-purpose, tokenizer-level rendering.
PromptFormat = Literal['raw', 'chat_completion', 'chat_prompt']
module-attribute
logger = logging.getLogger(__name__)
module-attribute
encode_for_model(tokenizer, *, prompt=None, completion=None, messages=None, mode='chat_prompt', **tokenizer_kwargs)
Render then tokenize with the correct add_special_tokens.
Convenience for single-prompt call sites (e.g. judges).
A template is applied iff has_chat_template(tokenizer) and
(for the prompt path) mode != "raw"; in that case the
rendered string already contains the special tokens, so it is
tokenized with add_special_tokens=False, otherwise True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tokenizer
|
PreTrainedTokenizerBase
|
Tokenizer to render and tokenize with. |
required |
prompt
|
str | None
|
User-turn content for the |
None
|
completion
|
str | None
|
Continuation for the |
None
|
messages
|
list[dict[str, str]] | None
|
Chat messages for the |
None
|
mode
|
PromptFormat
|
Rendering policy for the |
'chat_prompt'
|
Returns:
| Type | Description |
|---|---|
BatchEncoding
|
The tokenizer output (a |
Source code in steerability/utils/rendering.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
has_chat_template(tokenizer)
Single source of truth for chat-template detection.
Source code in steerability/utils/rendering.py
15 16 17 | |
render_for_model(tokenizer, *, prompt=None, completion=None, mode='chat_completion')
Render a single example into the exact string the model will process.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tokenizer
|
PreTrainedTokenizerBase
|
Tokenizer whose chat template defines the rendering. |
required |
prompt
|
str | None
|
The user-turn content (or, for |
None
|
completion
|
str | None
|
The continuation appended after the generation prompt (or,
for |
None
|
mode
|
PromptFormat
|
Rendering policy.
|
'chat_completion'
|
Returns:
| Type | Description |
|---|---|
str
|
The rendered string. Falls back to |
str
|
formatting is requested but the tokenizer has no chat template. |
Note
The returned string already contains the template's special tokens
(e.g. BOS). Callers MUST tokenize it with add_special_tokens=False.
Source code in steerability/utils/rendering.py
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
render_messages(tokenizer, messages, *, add_generation_prompt=True)
Render chat messages into a string via the tokenizer's chat template.
Wrapper around tokenizer.apply_chat_template(tokenize=False, ...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tokenizer
|
PreTrainedTokenizerBase
|
Tokenizer whose chat template defines the rendering. |
required |
messages
|
list[dict[str, str]]
|
Chat messages as |
required |
add_generation_prompt
|
bool
|
Whether to append the assistant generation prompt. |
True
|
Returns:
| Type | Description |
|---|---|
str
|
The rendered string. When the tokenizer has no chat template, falls back |
str
|
(with a warning) to joining message contents with blank lines. |
Note
The returned string already contains the template's special tokens (e.g.
BOS). Callers MUST tokenize it with add_special_tokens=False (see
encode_for_model).
Source code in steerability/utils/rendering.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | |
thinking
Splitting continuations from reasoning models into thinking and answer segments.
The split runs in one of two modes. Text mode is a substring split on the decoded continuation.
Token mode is an id-level split on the continuation ids, decoding each segment afterwards; it exists
because some tokenizers encode the reasoning delimiters as special tokens that skip_special_tokens=True
strips before a substring split could see them. resolve_split_mode picks between the two for a given
tokenizer and tag pair.
DEFAULT_THINK_TAGS = ('<think>', '</think>')
module-attribute
ThinkingSplit
Bases: NamedTuple
The two segments of one continuation.
Attributes:
| Name | Type | Description |
|---|---|---|
thinking |
str | None
|
The reasoning segment, or None when no think tag is present. |
answer |
str
|
The answer segment; the full text when no think tag is present, and the empty string when an opened thinking segment never closes. |
Source code in steerability/utils/thinking.py
16 17 18 19 20 21 22 23 24 25 26 | |
answer
instance-attribute
thinking
instance-attribute
find_subsequence(haystack, needle, start=0)
Return the first index at or after start where needle occurs in haystack, or -1.
An empty needle never matches (returns -1).
Source code in steerability/utils/thinking.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
resolve_split_mode(tokenizer, tags=DEFAULT_THINK_TAGS)
Resolve "auto" to "text" or "tokens" for a tokenizer and tag pair.
Each tag is encoded with add_special_tokens=False and the result decoded with
skip_special_tokens=True. When both tags round-trip to their exact original strings, a
substring split on the decoded continuation sees the delimiters, so the mode is "text".
Otherwise at least one delimiter is stripped by skip_special_tokens=True (it is a special
token) and the split must run on the ids, so the mode is "tokens".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tokenizer
|
PreTrainedTokenizerBase
|
The pipeline tokenizer whose vocabulary decides the delimiter treatment. |
required |
tags
|
tuple[str, str]
|
The |
DEFAULT_THINK_TAGS
|
Returns:
| Type | Description |
|---|---|
Literal['text', 'tokens']
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If either tag entry is not a non-empty string, or a tag encodes to an empty id
sequence (checked so |
Source code in steerability/utils/thinking.py
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | |
split_thinking(text, tags=DEFAULT_THINK_TAGS, *, opened_at_start=False)
Split a decoded continuation into its thinking and answer segments by substring matching.
Matching is plain substring, case-sensitive. Let open_tag, close_tag = tags. The result
depends on which tags are present and on opened_at_start:
- Close tag present (open tag optional): the split is at the last occurrence of
close_tag.thinkingis everything before it, with one leadingopen_tagremoved when the thinking segment starts withopen_tagafter leading whitespace.answeris everything after, left-stripped. The open tag is optional because thinking-mode chat templates commonly end the generation prompt with the open tag, so the continuation carries only the closing tag. - Open tag present, close tag absent (thinking truncated):
thinkingis everything after the firstopen_tagandansweris the empty string, since an unclosed thinking segment means no final answer was produced. - Neither tag present,
opened_at_start=False:thinkingis None andansweris the full text, so the split is a no-op for non-reasoning models. - Neither tag present,
opened_at_start=True: the generation prompt opened the channel and it never closed, sothinkingis the full text andansweris the empty string.
An empty thinking segment yields thinking == "" (not None), since a tag was present or the
channel was opened by the prompt.
The split is at the last occurrence of close_tag, so any earlier close_tag occurrences fold
into thinking and any open_tag/close_tag occurrences after the split stay in answer
verbatim.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The decoded continuation to split. |
required |
tags
|
tuple[str, str]
|
The |
DEFAULT_THINK_TAGS
|
opened_at_start
|
bool
|
Whether the generation prompt already opened the reasoning channel, so a continuation carrying neither tag is treated as unclosed reasoning rather than a plain answer. |
False
|
Returns:
| Type | Description |
|---|---|
ThinkingSplit
|
A |
Raises:
| Type | Description |
|---|---|
ValueError
|
If either tag entry is not a non-empty string. |
Source code in steerability/utils/thinking.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
split_thinking_ids(output_ids, tokenizer, tags=DEFAULT_THINK_TAGS, *, opened_at_start=False)
Split a continuation into thinking and answer segments at the token-id level.
Each tag is the id sequence obtained by encoding it with add_special_tokens=False, and
matching is first-occurrence subsequence search over output_ids. The delimiter ids belong to
neither segment; each resulting segment is decoded with skip_special_tokens=True. Let
open_ids, close_ids = tags encoded this way:
opened_at_start=False, open subsequence found: the reasoning starts after the open subsequence. Any ids preceding the open subsequence are prepended to the answer, since output emitted before the channel opened is not reasoning.opened_at_start=False, no open subsequence but a close subsequence: the reasoning starts at position 0. The open tag is optional because thinking-mode chat templates commonly end the generation prompt with it, so the continuation carries only the close.opened_at_start=True: the continuation begins inside the channel, so the reasoning starts at position 0 and no open subsequence is searched.- Once inside the channel, the split is at the first close subsequence:
thinkingis the ids up to it andansweris the ids after it. Close subsequences after the first stay in the answer verbatim. - Inside the channel with no close subsequence (truncation, or a caller stop string halted
generation before the close was emitted):
thinkingis the rest of the ids andansweris the empty string. opened_at_start=Falseand neither subsequence:thinkingis None andansweris the full decoded continuation.
An empty thinking segment yields thinking == "" (not None), since the channel was entered.
A multi-token ordinary-text portion of a composed tag (for example the thought\n following a
special open token) can tokenize context-dependently, so its id sequence in isolation need not
be the id sequence it forms inside the continuation. When opened_at_start=True the open tag is
never searched, so this affects the open tag only when the channel is opened within the
continuation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_ids
|
Sequence[int]
|
The continuation token ids (one row, the prompt excluded). Trailing padding or
eos ids may be present; delimiter matching is unaffected since those ids do not match a
delimiter, and each segment is decoded with |
required |
tokenizer
|
PreTrainedTokenizerBase
|
The tokenizer used to encode the tags and decode the segments. |
required |
tags
|
tuple[str, str]
|
The |
DEFAULT_THINK_TAGS
|
opened_at_start
|
bool
|
Whether the generation prompt already opened the reasoning channel. The flag matters only for a continuation carrying neither tag, which is classified as unclosed reasoning when set and as a plain answer otherwise. |
False
|
Returns:
| Type | Description |
|---|---|
ThinkingSplit
|
A |
Raises:
| Type | Description |
|---|---|
ValueError
|
If either tag entry is not a non-empty string, or a tag encodes to an empty id sequence under this tokenizer. |
Source code in steerability/utils/thinking.py
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
tokenization
Tokenizer- and attention-mask-level hygiene: pad-token setup, token counting, mask inference, batch re-padding, and BOS sanity checks.
logger = logging.getLogger(__name__)
module-attribute
count_tokens(tokenizer, text)
Count the tokens in text, without special tokens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tokenizer
|
PreTrainedTokenizerBase
|
HuggingFace tokenizer instance. |
required |
text
|
str
|
The text to tokenize. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The token count. |
Source code in steerability/utils/tokenization.py
28 29 30 31 32 33 34 35 36 37 38 | |
ensure_pad_token(tokenizer)
Set pad token to eos token if not already defined.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tokenizer
|
PreTrainedTokenizerBase
|
HuggingFace tokenizer instance |
required |
Returns:
| Type | Description |
|---|---|
PreTrainedTokenizerBase
|
The same tokenizer with pad_token configured |
Source code in steerability/utils/tokenization.py
13 14 15 16 17 18 19 20 21 22 23 24 25 | |
infer_attention_mask_from_ids(input_ids, pad_token_id)
Best-effort attention mask when the caller supplied none.
Marks only leading and trailing runs of pad_token_id as padding; interior occurrences are kept
as real tokens. This matters when pad == eos (set by ensure_pad_token for tokenizers without a
pad token): chat templates legitimately contain the eos mid-prompt (e.g. ChatML <|im_end|>), and
a token-identity mask (ids != pad_id) would wrongly mask those positions.
Rules:
pad_token_idis None -> all ones.- a row that is entirely pad -> all ones (degenerate; safest).
- otherwise ones on
[first_nonpad, last_nonpad], zeros outside.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
Tensor
|
Token ids of shape [batch, seq_len]. |
required |
pad_token_id
|
int | None
|
The tokenizer's pad token id, or None. |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A long tensor of shape [batch, seq_len] on |
Source code in steerability/utils/tokenization.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
to_left_pad(input_ids, attention_mask)
Rearrange a batch to left-padded layout for correct batched causal scoring.
Left-padding ensures that all sequences in the batch end at the same position, allowing a uniform logit slice after concatenation with reference tokens. Works correctly regardless of whether the input is right-padded, left-padded, or unpadded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
Tensor
|
Input token IDs [batch, seq_len] |
required |
attention_mask
|
Tensor
|
Corresponding attention mask [batch, seq_len] |
required |
Returns:
| Type | Description |
|---|---|
tuple[Tensor, Tensor]
|
tuple[torch.Tensor, torch.Tensor]: Left-padded (input_ids, attention_mask) |
Source code in steerability/utils/tokenization.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
warn_if_duplicate_bos(input_ids, attention_mask, tokenizer, already_warned)
Warn once if any row starts with two BOS tokens; returns the updated warned-state.
The dominant cause is rendering a chat template to a string and re-tokenizing it with
add_special_tokens=True; the template already contains the BOS.
Returns already_warned unchanged (no re-check) when already_warned is True, when tokenizer
is None, when tokenizer.bos_token_id is None, or when seq_len < 2. Returns True after emitting
the warning.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
Tensor
|
The (2D) prompt token ids about to be generated from. |
required |
attention_mask
|
Tensor
|
The matching attention mask (used to locate the first real token, so the check is left-padding safe). |
required |
tokenizer
|
PreTrainedTokenizerBase | None
|
The pipeline tokenizer (or None). |
required |
already_warned
|
bool
|
Whether the duplicate-BOS warning has already fired for this pipeline. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
The updated warned-state. |
Source code in steerability/utils/tokenization.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | |
verbosity
Opt-in verbosity controls for the steerability package logger.
The package attaches a logging.NullHandler to its root logger at import, so toolkit logging is
silent by default and never emits "no handlers could be found" warnings. This module exposes the
supported way to turn that logging up or down and to quiet known-noisy third-party libraries. None
of these functions run at import time, and the library never calls them on its own; the package
mutates no global logging state unless a caller asks.
The named module is verbosity rather than logging so it does not shadow the standard library
logging module from inside the package.
get_verbosity()
Return the effective level of the steerability logger.
Applies the STEERABILITY_VERBOSITY environment default once (on first library use) before
reading, so an environment-configured level is reflected without an explicit set_verbosity
call.
Returns:
| Type | Description |
|---|---|
int
|
The effective integer level, resolved up the logger hierarchy when the package logger has |
int
|
no level of its own. |
Source code in steerability/utils/verbosity.py
90 91 92 93 94 95 96 97 98 99 100 101 102 | |
quiet_third_party()
Reduce output from known-noisy third-party libraries.
Sets the transformers logging facility to ERROR, disables Hugging Face Hub progress bars, and
(when importable) sets the datasets logging facility to ERROR. Each step is guarded so a
missing optional dependency is a no-op. This is opt-in and the library never calls it. It does
not add any warnings filter and does not set TQDM_DISABLE, so user-owned progress bars and
Python warnings are left alone.
Source code in steerability/utils/verbosity.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
set_verbosity(level)
Set the level of the steerability logger and attach one stream handler if none is attached.
Sets the level of the package root logger (steerability), so every module logger under it is
affected. If the package root logger has no handler other than the import-time NullHandler, a
single StreamHandler with a plain %(levelname)s %(name)s: %(message)s format is attached so
records reach the console. The function is idempotent: a second call updates the level and does
not attach a second handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
int | str
|
One of |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a string level name is not recognized. |
TypeError
|
If |
Source code in steerability/utils/verbosity.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | |