Ster/Docs

Rust library API reference

The ster package publishes no crates.io release (publish = false), but the repository builds a library crate alongside the CLI. Depend on a pinned Git revision or path. All fallible calls return anyhow::Result.

Overview#

The ster package publishes no crates.io release (publish = false), but the repository builds a library crate alongside the CLI. Depend on a pinned Git revision or path. All fallible calls return anyhow::Result.

toml
[dependencies]
ster = { git = "https://github.com/wisent-ai/ster", rev = "<commit>" }

The crate root re-exports ContrastivePair, PairSet, SteeringArtifact, SetReport, SynthesisOptions, SynthesisReport, TrainingMethod, DeviceChoice, GenerationOptions, and Runtime. Its ten modules are also public. This page lists every public item in Ster 0.13.

artifact#

  • ARTIFACT_SCHEMA_VERSION: u32 — currently 1.
  • ContrastivePair { positive: String, negative: String } — serde JSON pair.
  • PairSet { trait_name: String, pairs: Vec<ContrastivePair> } — serde JSON set; missing trait_name defaults to empty.
  • PairSet::load(&Path) reads JSON and checks a non-empty set and nonblank sides.
  • PairSet::validate(&str) checks the same two invariants against an in-memory set; the &str label is the identity quoted in the refusal sentence.
  • PairSet::save(&Path) validates, creates a named parent directory, and writes pretty JSON with a trailing newline — pair files are hand-edited, so they are stored readably rather than compactly.
  • LayerVector { layer: usize, values: Vec<f32>, train_margin: f32, train_accuracy: f32 } — one scored direction.
  • SteeringArtifact { schema_version, product, model, model_revision, precision, chat_template, trait_name, method, hidden_size, vectors, metadata } — all fields public; metadata is BTreeMap<String, String> and precision and chat_template are Option<String>, all three serde-defaulted, so a document written before the two provenance fields existed deserializes with both None.
  • new(model, model_revision, trait_name, method, hidden_size, vectors, precision, chat_template) stamps the current schema/product and empty metadata, and records the two provenance values as Some; it takes a Precision and a chat::Status and stores each one's own word.
  • Document::recognise(&[u8]) -> Document — Steering, AdapterSidecar, or Unrecognised, decided by which pair of fields a JSON object carries: trait_name and vectors, or rank and targets. load uses it to refuse the other product's document by name instead of letting serde escape as a missing field.
  • load(&Path) reads and validates.
  • save(&Path) validates, creates parent directories, and writes compact JSON.
  • validate() checks schema, product, non-empty width/vectors, vector widths, and finite values.

Wire details: steering artifact.

representation#

  • TrainingMethod::{Caa, Pca, Logistic} — Debug + Clone + Copy + PartialEq + Eq.
  • parse(&str) accepts caa, pca, and logistic.
  • name() returns canonical caa, pca, or logistic.
  • train_direction(positive, negative, method) -> Result<Vec<f32>> — validates matched rows, runs the selected algorithm, returns an L2-normalized vector.
  • evaluate_direction(positive, negative, direction) -> Result<(f32, f32)> — returns (accuracy, mean_margin).
  • cosine_similarity(left, right) -> Result<f32> — equal non-empty non-zero vectors only.

Algorithm constants and measures: training method and evaluation.

runtime#

  • DeviceChoice::{Cpu, Metal, Cuda} — Debug + Clone + Copy.
  • parse(&str) accepts exactly lowercase cpu, metal, or cuda.
  • GenerationOptions { strength: f64, max_new_tokens: usize, temperature: f64, top_p: Option<f64>, seed: u64 } — no Default implementation; callers set every field.
  • Runtime — owns tokenizer, model, device, dtype, and EOS set. Public fields: model_id: String, revision: Option<String>.
  • load(model, revision, device) resolves and loads one F32 model; load_at(model, revision, device, precision) is the same load at a stated Precision.
  • hidden_size() and layer_count() expose config dimensions.
  • set_chat_template(choice) records the run's ChatChoice, returns the resulting ChatStatus, and prints one sentence naming the decision; chat_status() reads it back. Every read and every generation after it encodes accordingly, so it is called once per run before any work.
  • activations(prompt, layers) returns sorted (layer, Vec<f32>) final-token states, encoding the prompt through whatever this runtime's chat-template decision selects.
  • generate(prompt, artifact, options) returns generated text; None artifact is baseline generation.

A Runtime is reusable across calls but methods take &self; each activation or generation call allocates a fresh attention cache. It does not cache captures or downloads beyond hf-hub's disk cache.

workflow#

  • train(runtime, pairs, layers, method) -> SteeringArtifact
  • optimize(runtime, pairs, layers) -> Selection
  • evaluate(runtime, pairs, artifact) -> EvaluationReport
  • extract(runtime, input, output, layers) -> ()
  • parse_layers(value, count) -> Vec<usize>
  • artifact_summary(artifact) -> serde_json::Value — the document train, optimize and inspect all print: identity, precision, chat_template, metadata, and per layer layer, width, norm, train_accuracy, train_margin.
  • Selection { artifact: SteeringArtifact, holdout: Holdout, candidates: Vec<Candidate> }, with summary() returning artifact_summary plus a selection object. Candidate { layer, method, holdout_accuracy, holdout_margin, selected } and Holdout { fit_pairs, holdout_pairs } are both serde serializable; selected is true on exactly one candidate.
  • EvaluationReport { model, trait_name, method, pair_count, layers } — serde serializable.
  • LayerEvaluation { layer, accuracy, margin } — serde serializable.
  • set_progress_sink(Option<Box<dyn Fn(&str) + Send>>) — process-global progress replacement used by request; setting it affects all workflows and is not safe for concurrent independent jobs. Normal library clients should leave it unset so progress goes to stderr.

extract's prompt and activation structs are intentionally private; its public contract is file-to-file JSON described under activation.

pairs#

  • UNBALANCED_RATIO: f64 — 3.0. A pair whose two sides differ by more than this factor in characters is reported unbalanced.
  • InspectOptions { dedupe: DedupeOptions, refusal_threshold: f32 } — has Default: the dedupe defaults and threshold 0.5.
  • inspect(&PairSet, &InspectOptions) -> Result<SetReport> — the model-free audit; every judgement is textual, which is why a desktop can show it the moment a file is opened.
  • SetReport { trait_name, pair_count, duplicate_count, refusal_count, unbalanced_count, diversity, entries } — serde serializable.
  • EntryReport { index, positive, negative, positive_chars, negative_chars, positive_words, negative_words, duplicate, positive_refusal, negative_refusal, length_ratio } — one per pair.
  • RefusalFlag { score: f32, family: String, snippet: String } — what a flagged side carries; family is the wire name, snippet the matched text.
  • SynthesisOptions { trait_description, trait_name, opposite, count, retry_multiplier, dedupe, refusal_threshold, generation } — no Default; callers set every field.
  • synthesize(&Runtime, &SynthesisOptions) -> Result<(PairSet, SynthesisReport)> — generates, repairs, and deduplicates in one pass. It does not write anything; callers pass the returned set to PairSet::save.
  • SynthesisReport { trait_name, trait_description, opposite, requested, attempts, kept, rejected_empty, rejected_refusals, rejected_duplicates, refusal_retries, diversity } — serde serializable.

Step order, rejection rules, and field meanings: synthetic pairs.

dedupe#

  • DedupeOptions { threshold_bits: u32, word_ngram: usize, char_ngram: usize, num_bands: u32 } — Copy; Default is 3, 1, 4, 8.
  • validate() refuses a band count that does not divide the 64-bit fingerprint evenly, an n-gram size below one (dedupe n-gram sizes must be at least 1), and a threshold wider than 64 bits.
  • Duplicate::{Exact { of }, Near { of, distance }} — serde-tagged with kind in snake_case.
  • Index::new(DedupeOptions), insert(&ContrastivePair) -> Option<Duplicate>, len(), is_empty() — the running first-occurrence-wins index. insert keeps a new pair and returns None, or returns the earliest colliding kept pair.
  • classify(&[ContrastivePair], DedupeOptions) -> Result<Vec<Option<Duplicate>>> — one verdict per input pair in input order, with indices pointing at positions in the input rather than in the kept subsequence.
  • simhash64(&[&str], DedupeOptions) -> u64, hamming(u64, u64) -> u32, normalize(&str) -> String — the 64-bit BLAKE2b-backed fingerprint, the bit distance, and the canonicalizer that replaces URLs and e-mail addresses with placeholders, lowercases, drops combining marks, and collapses whitespace.

refusal#

  • DEFAULT_THRESHOLD: f32 — 0.5.
  • Family::{AiDisclaimer, Policy, ApologyHedge, Unable, CannotAction, PreferRather, DeclineRefuse, NoSupport, NoAbility, RefusalWord} with weight() and name(). Weights run from 0.4 for a bare apology hedge, which prefixes plenty of non-refusals, to 1.0 for cannot_action, the one phrasing that is a refusal on its own.
  • Score { score: f32, family: Option<Family>, snippet: String } — the verdict for one text. snippet is the matched text, so a report can show why.
  • score(&str) -> Score — NFKC-normalized and case-insensitive; the highest-weighted matching family wins.
  • looks_like_refusal(&str, f32) -> bool — whether score(text).score is at or above the threshold.

diversity#

  • Scores { unique_unigrams: f64, unique_bigrams: f64, avg_jaccard: f64, mean_simhash_hamming: f64, min_simhash_hamming: u32 } — Copy and serde serializable.
  • compute(&[String]) -> Scores — every text is measured: the distinct-1 and distinct-2 ratios and the pairwise Jaccard and SimHash passes alike, so the pairwise passes are quadratic in the set's size and no sample is drawn. Fewer than two texts report avg_jaccard and mean_simhash_hamming as 0.0 and min_simhash_hamming as 64.

model#

These low-level Candle-facing types are public because the module is public, but most clients should use Runtime.

  • SteeringPlan::new(vectors, strength, hidden_size, device, dtype) copies vectors to tensors and requires at least one correctly sized vector.
  • ForwardOutput { logits: Tensor, activations: BTreeMap<usize, Vec<f32>> }.
  • Cache::new(use_kv_cache, dtype, &Config, &Architecture, &Device) keeps the rotation's frequencies and empty per-layer key-value and recurrent-state slots; each forward computes the angles of exactly the positions it rotates.
  • SteeringLlama::load(VarBuilder, Config), config(), and forward(tokens, index_pos, cache, steering, capture_layers) expose the native decoder. capture_layers must be sorted because lookup uses binary search.

request#

  • request::run(operation: &str) -> Result<i32> reads one JSON body from stdin, runs operation, prints its NDJSON events on stdout, and returns the status the process exits with: 0 completed, 1 failed while running, 2 refused before running. Protocol: desktop request reference.

Minimal library workflow#

rust
use std::path::Path;
use ster::{DeviceChoice, PairSet, Runtime, SteeringArtifact, TrainingMethod};
use ster::workflow;

let runtime = Runtime::load("./toy-llama", None, DeviceChoice::Cpu)?;
let pairs = PairSet::load(Path::new("pairs.json"))?;
let layers = workflow::parse_layers("1..3", runtime.layer_count())?;
let artifact = workflow::train(&runtime, &pairs, &layers, TrainingMethod::Caa)?;
artifact.save(Path::new("calm.ster.json"))?;
let checked = SteeringArtifact::load(Path::new("calm.ster.json"))?;
let report = workflow::evaluate(&runtime, &pairs, &checked)?;
# Ok::<(), anyhow::Error>(())

There are no async APIs, builder defaults, background workers, or global runtime configuration beyond the optional progress sink.