v0.5.0
Replaces the evaluation stack with an Inspect AI package, adds .spipe as a portable serialization format for SteeringPipeline, and renames the package from aisteer360 to steerability. Additionally ports the toolkit to transformers v5 (Python 3.12+) and broadens the model families that state controls and activation capture run on.
Evaluation
The Benchmark, metrics, and use-case modules are replaced by an Inspect AI evaluation package (steerability/evaluation/, behind the new eval extra). Target-behavior evaluations are ordinary Inspect Tasks; the toolkit ships no task, scorer, or metric classes of its own.
as_inspect_model(pipeline)andProviderOptions: a generation-only Inspect model provider over a steered pipeline. Every generation routes throughSteeringPipeline.generate(); prompts enter asmessages=when the tokenizer has a chat template (soadapt_messagescontrols fire) and as renderedtext=otherwise, with the path recorded asprompt_pathin provenance. Logprob parameters, tools, and multimodal content are refused with actionable messages- Concurrent Inspect requests collate into batched pipeline calls when every enabled control is batch-safe (
evaluation/batching.py); a seeded dispatch carriesseed_scopefromProviderOptions(default"dispatch"), so a seeded batch decodes in one pass on the Hugging Face backend runtime_kwargs_solverdelivers per-sample runtime kwargs fromSample.metadata;sample_scorer_from_inspectadapts any Inspect scorer into a per-rowSampleScorerreward;InspectSuitenames a task set overeval_set(limit,sample_ids,task_args,generate_overrides,retry_attempts)SteeringEvalruns configurations (fixed controls,ControlSpecsweeps, and the empty-list baseline arm) x trials x suites, one GPU-resident pipeline at a time, with a pre-flightcheck()over every sweep point (on_unsupported="raise" | "skip") and one derived seed per (configuration, trial). There is no separate results checkpoint: the.evallogs undersave_dir/inspect_logs/are the store, andeval_setresumes each cell at sample granularity. Results reshape throughresults(),runs_frame(),samples_frame(), andsummarize_runsviz_utilsis reworked intoevaluation/plotting.pyover the summarized runs frame (plot_metric_by_config,plot_tradeoff_scatter,plot_metric_heatmap,plot_comparison_bars,plot_sensitivity,plot_tradeoff,plot_pareto_frontier)- The sweep layer has no Inspect dependency:
identity.pymoves intoalgorithms/core/, alongside the newcore/sweeps.py(expand_configurations,preflight,PipelineFactory) andcore/scoring.py(SampleScorer) - Every
RUNTIME_KWARGS_SCHEMAentry declares ascope("row"for a per-prompt value delivered row-aligned in batched calls,"call"for one value per call), validated atsteer()and read by the provider;SearchDriverreward_paramsis row-scoped (one mapping per row, merged into the scorer's params) - The benchmark notebooks are ported to Inspect as
examples/notebooks/studies/with task modules (commonsense_mcqa,instruction_following), and a routing-vs-prompting study is added
Pipeline serialization
A new steerability/spipe/ package adds .spipe, a portable serialization format for SteeringPipeline (spipe/1 manifest; a zip when the path ends in .spipe, a directory otherwise).
- A spipe stores the recipe (model reference plus controls as constructed) and, once the pipeline is steered, the frozen resolution (fitted vectors, probes, adapters, optimized prompts) in a content-addressed artifact store with a lock section (fingerprints, per-fit digests)
SteeringPipeline.to_spipe()builds one;SPipe.load,save(artifacts="thin"writes the manifest only, resolved at load throughartifact_store=),pipeline,verify, andthawround-trip it. Frozen entries instantiate from their resolution, sosteer()on a loaded pipeline is cheap and model-free;prefer="recipe"forces re-fits,allow_code=Truegates callable references, non-toolkit dataclasses, and pickle-backed memories, and a stale bundle (fit-relevant recipe fields edited after freezing) raises unlessallow_stale=True- Controls declare how they freeze through
steer_fits(),export_state(),frozen_form(), andfit_identity();InterventionControlderives all four from its template, and a control that produces state without a frozen form raisesNotFreezableError - Trained structural controls freeze as the new
load_checkpoint/load_loracontrols; intervention controls freeze asactivation_adapterentries unless they declare a same-class frozen form (caa,act_add,iti)
New methods
system_prompt: sets or merges the leading system message of a chat (prepend, append, or replace), always producing exactly one system messageuser_prefix: prepends a fixed text marker to a user turn (the last, first, or all user turns), with the token stream as a fallback for non-chat inputload_checkpointandload_lora: structural controls that load a checkpoint directory or a LoRA adapter (merge=Falsekeeps the PEFT wrapper, so a state control listed after it steers the adapted model); these are the frozen forms of trained structural controls in a.spipe
Other changes
- Dependencies: ported to transformers v5 with the ML stack bumped (trl 1.0, peft 0.20, datasets 4.7, accelerate 1.4, vllm 0.26 with
trl[vllm], mergekit from a pinned commit); Python 3.12+ required;uv.lockis committed - Model families: new
core/internals/model_layout.py(resolve_model_layout,text_config,register_layout_detector, per-layerhead_geometry) resolves the decoder stack atmodel.layers,model.language_model.layers, ortransformer.h; state controls and activation capture support composite multimodal wrappers (Gemma 3), unmerged PEFT/LoRA wrappers, and hybrid attention stacks (Qwen3.5 / Qwen3-Next, where residual-stream controls work unchanged and head-level controls refuse the non-attention layers) - Generation: batched prompts are left-packed on the HF session and before state-hook assembly in
_execute_generation, so full-sequence returns of a padded batch are left-packed ([pads, prompt, continuation]);seed_scope("item"or"dispatch") maps agenerate(seed=...)onto the items of a multi-item dispatch;Output.generated_tokensandDecodingDriver.max_rollouts_per_query()count the tokens and continuations a driver produces across rollouts - Decoding drivers: the phased, routed, and budget-forcing paths are fixed across the backend interface, and finished-beam detection in the search
Frontieris fixed - PASTA: head profiling as a steer-time fit (
HeadProfile, resolved atsteer()to a head map);scale_positionand the decode-phase attention mask are fixed under transformers v5 - RAD: prefix-reward training (
rad/utils/reward_training.py, with the loss computed in float32) and sequence-classification heads for the Granite families; the reward head is read at the candidate position, so an eos candidate no longer receives the prefix's reward - SASA fits from contrastive pairs (
gen_wv_dataacceptsLabeledExamplesorContrastivePairs); the ITI estimator is reworked - vLLM: new
backends/vllm/environment.pyturns the FlashInfer sampler off by default at offline engine boot (an explicit caller value wins), forcesVLLM_HOOK_WORKER=unifiedforhook_pluginengines, and providesserve_environmentfor launched servers;vllm-hook-pluginsinstalls from the [steerability-interface](https://github.com/emiehling/vLLM-Hook/tree/steerability-interface) branch - TRL wrappers:
prompt_formaton the DPO/APO preference path (with a boundary warning),training_argsvalidated against the installed TRL config (an undeclared key raises at construction), DPO multi-loss (loss_typeas a list withloss_weights), andresume_from_checkpointpassed totrainer.train() - Reasoning models:
split_thinking_idssplits on token ids when the delimiters are special tokens (ProviderOptions.reasoning_split="auto" | "text" | "tokens", withreasoning_opened_at_startfor templates that open the channel in the generation prompt);utils/answers.pyadds numeric answer extraction canonical_valuerecurses into the elements of NumPy object arrays, soconfig_digestno longer raises on object arrays arriving from a pandas or datasets row- Docs: the evaluation tutorial is rewritten (
evaluate_steering_pipelines.md); the spipe concept page and reference pages for evaluation, backends, spipe, utils, and the new controls are added; a bibtex-warning hook (docs/_hooks/) and a docs-nav test are added,mkdocs build --strictpasses, and AGENTS.md and README are rewritten - Notebooks: the generics and library-wrapper notebooks move under
algorithms/(generics/,wrappers/); system_prompt, honest-persona prompting, routing-vs-prompting, vLLM serve, and working-with-spipes notebooks are added; the SASA (style self-steering), RAD (prefix-reward detoxification on Granite 4.1), best-of-n, and budget-forcing notebooks are refreshed; all notebooks rerun - Tests: added for the Inspect provider, batching, solvers, scorers, suite, runner, and frames; spipe codec, format, store, identity, and freeze; sweeps and the runtime-kwargs schema; Gemma 3, hybrid-attention, and LoRA-composition steering; the new controls; the DPO wrapper, TRL resume, and training-args validation; ITI, SASA, and RAD reward training; the search frontier, reward params, phased token boundaries, max rollouts, generated tokens, and padding alignment; the vLLM engine environment; the docs nav
Breaking changes
- Package rename:
aisteer360becomessteerabilityfor the package directory, distribution, identifiers, and documentation (e.g.aisteer360.algorithms.core.steering_pipeline→steerability.algorithms.core.steering_pipeline); the spipe lock records the toolkit under the new name - Evaluation:
Benchmark, the metrics package (LLMJudgeMetric,Perplexity, ...), theUseCasesubclasses,viz_utils, and thegenerate_on_pipeline()/batch_retry_generate()helpers are removed in favour ofsteerability/evaluation/. Benchmark checkpoints andcheckpoint_everyare gone (the.evallogs are the store), so 0.4.0 checkpoints are not resumed. The TruthfulQA composite study is removed - Python 3.12+ is required, and the dependency ranges move to transformers 5.x, trl 1.x, peft 0.20, datasets 4.7+, accelerate 1.4+, and vllm 0.26+
- Packaging: the
plots,guided, andcpoextras are removed (xgrammaris a core dependency;econmlfor CPO'suse_dmlis installed separately);eval(Inspect AI, inspect-evals, matplotlib, seaborn) is the new workflow extra andallinstalls it;mergingis declared a uv conflict witheval,all, andvllm; contributor tooling moves from extras into[dependency-groups](dev,notebooks,docs), sodevis no longer an extra anduv syncinstalls it by default SearchDriverpresets (DeAL,BestOfN,SearchDecoding) takereward_paramsper row: a batched call passes one mapping per row rather than one mapping for the callidentity.pymoves tosteerability/algorithms/core/identity.py
What's Changed
- Refactor evaluation to use Inspect, bump package versions, rename package to steerability by [@emiehling](https://github.com/emiehling) in [#25](#25)
Full Changelog: [v0.4.0...v0.5.0](v0.4.0...v0.5.0)