Operational profiles¶
A benchmark accuracy is a statement about the benchmark’s mix of tasks. A reliability claim has to be a statement about the mix the system will meet in service, and those two are rarely the same.
Concretely: a model measured on a corpus that is 18% long multi-hop questions, then deployed on a workload that is 70% of them, does not keep its measured failure rate. Nothing about the model changed; the question changed.
The operational profile is that mix — a partition of the input space into strata, with the probability of each. Everything downstream is conditional on it.
Building one¶
from hiphopsllm import OperationalProfile, empirical_profile, uniform_profile
# declared
profile = OperationalProfile({"short": 0.30, "medium": 0.50, "long": 0.20})
# measured from logged usage
profile = empirical_profile(observed_stratum_labels)
# assumed, and recorded as an assumption
profile = uniform_profile(["short", "medium", "long"])
Each carries its own provenance, printed in every report that uses it:
print(profile.summary())
operational profile (3 strata)
short 0.300 █████████
medium 0.500 ███████████████
long 0.200 ██████
provenance: declared by the analyst
That line is not decoration. “Declared by the analyst” and “observed frequencies over 12,480 logged items” support very different claims, and a reader six months later cannot tell them apart from the numbers alone.
How this maps onto HIP-LLM’s hierarchy¶
HIP-LLM’s operational profile is hierarchical (paper Definition 2, Section 3.2.1):
subdomain -> domain : p_i = sum_j Omega_ij * theta_ij (sum_j Omega_ij = 1)
domain -> LLM : p_L = sum_i W_i * p_i (sum_i W_i = 1)
theta_ij is the failure probability in subdomain j of domain i; Omega_ij
and W_i are the profile weights. The paper also gives the algebraically
identical flat form OP_ij = W_i * Omega_ij.
A stratum here is a HIP-LLM subdomain, and this package uses the flat,
single-domain case: your {label: weight} mapping is Omega, and there is one
domain, so p_L is the profile-weighted average of the per-stratum failure
probabilities. That is what
hiphopsllm.reliability.hipllm.OperationalFailureProb is given, at
level="benchmark_stratum".
If you need several domains, use the engine directly:
hiphopsllm.reliability.hipllm.posterior.run_domain with DomainData and
SubdomainData from the same module.
What is actually reported¶
The measurement is the probability that a component fails on one task drawn from the profile, and the reliability R(n) = P(failure-free over n future tasks), which is HIP-LLM’s definition of reliability. Neither is about any particular failure mode:
study.operational_reliability(n_tasks=10)
study.evidence["react_agent"].statement(n_tasks=10)
study.evidence["react_agent"].reliability(100)
react_agent fails on a task with probability [0.1542, 0.2977] under the
operational profile (short 30%, medium 50%, long 20%), from 33/160 observed
failures; failure-free over 10 tasks with probability [0.0736, 0.1003]
The fault tree then decomposes each of those numbers over the component’s failure modes so it can be propagated through the architecture. That split is a modelling step; the measurement is the statement above.
Choosing the strata¶
The strata must partition the input space, and they should be chosen so that failure probability varies between strata more than within them. Otherwise the stratification buys nothing.
For question answering, decomposition length works well, and HIP-LLM ships the StrategyQA stratifier that produced it:
from hiphopsllm import decomposition_stratum, load_strategyqa
items = load_strategyqa("train")
strata = [decomposition_stratum(item) for item in items]
Warning
Use HIP-LLM’s own load_strategyqa and decomposition_stratum. The Hugging Face
mirror ChilleD/StrategyQA exposes facts, not decomposition; substituting it
collapses every task into one stratum and silently destroys the profile. The
loader raises rather than falling back.
For your own workload, stratify takes a callable and checks the labels against
the profile:
from hiphopsllm import stratify
labels = stratify(
requests,
lambda r: "long" if len(r["question"].split()) > 40 else "short",
profile=profile, # an unexpected label is an error, not a zero weight
)
Passing the profile matters. A stratum with no weight silently drops those items from every downstream estimate — the kind of error that survives all the way into a published number.
Using one¶
The profile-weighted average of any per-stratum quantity:
profile.expected({"short": 0.05, "medium": 0.12, "long": 0.31}) # 0.137
A missing stratum raises rather than renormalising, for the same reason.
Conditioning on a subset — “what if we only served short questions?”:
profile.restricted_to(["short", "medium"]) # renormalised, provenance updated
Sample size follows the profile¶
A stratum that carries 5% of the weight still needs enough items to estimate its
own failure rate; otherwise its interval is wide and the profile-weighted
envelope inherits that width. When a stratum has no items at all, the fast
Jeffreys path returns [0, 1] for it — the honest answer — and the envelope goes
correspondingly wide. That is a signal to collect data, not to drop the stratum.
The two OperationalProfile classes¶
HIP-LLM’s schema keeps parallel labels/weights arrays; this package’s class is
mapping-shaped and more convenient. They convert both ways:
from hiphopsllm import OperationalProfile
from hiphopsllm.reliability.hipllm import HIPLLMOperationalProfile
engine_profile = profile.to_hipllm() # -> hip_llm.schemas version
OperationalProfile.coerce(engine_profile) # -> back again
Anything in this package that takes a profile= argument accepts either, or a
plain dict.
Never let the benchmark be the profile by default¶
dataset_proportional_profile exists so that using the dataset’s own mix as the
workload is a named choice rather than something that happens quietly. HIP-LLM
names it too (paper Section 4.2, Remark 7).
from hiphopsllm import dataset_proportional_profile, empirical_profile
dataset_proportional_profile(strata) # "my test set looks like production"
empirical_profile(production_labels) # production traffic, actually measured
The arithmetic is identical. The claim is not, which is why they are separate
functions and why their provenance strings differ. If you call observe()
without a profile you get the first one, and a warning saying so.
Why it is never inferred¶
AgenticReliabilityStudy.calibrate() refuses to run without one:
StudyNotReady: no operational profile has been set; pass profile=... to the
study or to observe(). Every failure probability is conditional on it, so it is
never inferred silently.
The obvious default — use the benchmark’s own mix — is exactly the mistake this
layer exists to prevent. If the benchmark’s mix is your workload, say so
explicitly with profile=empirical_profile(strata), which records that choice.