Pipeline¶
Explain pipeline types and orchestration.
@domain pipeline @see [[knowledge/architecture/pipeline]]
Explain pipeline configuration schema.
GenerationConfig, DedupConfig and KvCacheConfig live in
mllm_shap.domain.config so the runtime can use them without importing the
pipeline; they are re-exported here.
@domain pipeline @see [[knowledge/architecture/pipeline]] @tested-by [[tests/pipeline/test_config.py]]
- class mllm_shap.pipeline.config.ArchiveConfig(*, path: str = './runs/{run_id}', flush_every: Annotated[int, Ge(ge=1)] = 50, on_exists: Literal['error', 'overwrite'] = 'error')[source]¶
Bases:
BaseModelRun archive persistence options.
- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mllm_shap.pipeline.config.BackendConfig(*, kind: str, model_id: str, api_host: str | None = None, timeout_s: Annotated[float, Gt(gt=0)] = 120.0, max_retries: Annotated[int, Ge(ge=0)] = 3, options: dict[str, object] = {})[source]¶
Bases:
BaseModelBackend selection and connection parameters.
- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- api_host: str | None¶
API host for HTTP-compatible backends.
Required for the
apibackend (there is no environment or localhost fallback); optional forlmstudio, which otherwise asks LM Studio for its local server. Theapibackend sendsMLLM_SHAP_API_KEYas the bearer key to any host;OPENAI_API_KEYis used only when that is unset and the host isapi.openai.com.
- class mllm_shap.pipeline.config.BudgetConfig(*, fraction: Annotated[float | None, Gt(gt=0.0), Le(le=1.0)] = None, evaluations: Annotated[int | None, Strict(strict=True), Ge(ge=1)] = None)[source]¶
Bases:
BaseModelCoalition budget: a fraction of the
2^ncoalitions or an absolute count.Set at most one of
fractionandevaluations; with neither the budget is the full table (fraction: 1.0). Only the key that is set is serialised, so configurations withoutevaluationskeep their run id.- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mllm_shap.pipeline.config.DedupConfig(*, enabled: Literal['auto', 'on', 'off'] = 'auto')[source]¶
Bases:
BaseModelCoalition deduplication options.
- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mllm_shap.pipeline.config.ExplainConfig(*, backend: BackendConfig, generation: GenerationConfig = GenerationConfig(max_new_tokens=128, temperature=0.0, top_k=1, compute_embeddings=False), explanation: ExplanationConfig = ExplanationConfig(estimand=<Estimand.SHAPLEY: 'shapley'>, estimator='neyman_cc', value_fn='tfidf_cosine', normalizer='identity', players='tokens', absence_policy='delete', budget=BudgetConfig(fraction=1.0, evaluations=None), include_minimal_masks=False, neyman=NeymanConfig(use_standard_method=False, initial_fraction=None, initial_num_samples=None), max_inflight=2, max_players=24, archive=ArchiveConfig(path='./runs/{run_id}', flush_every=50, on_exists='error'), dedup=DedupConfig(enabled='auto'), kv_cache=KvCacheConfig(enabled=False, max_entries=8), embedding_mode=<EmbeddingMode.STATIC: 'static'>, seed=42, plugin_options={}))[source]¶
Bases:
BaseModelTop-level explain pipeline configuration.
- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- backend: BackendConfig¶
Backend selection and connection parameters.
- generation: GenerationConfig¶
Generation parameters for base and coalition evaluations.
- explanation: ExplanationConfig¶
Explanation algorithm and runtime controls.
- class mllm_shap.pipeline.config.ExplanationConfig(*, estimand: Estimand = Estimand.SHAPLEY, estimator: str = 'neyman_cc', value_fn: str = 'tfidf_cosine', normalizer: str = 'identity', players: str = 'tokens', absence_policy: str = 'delete', budget: BudgetConfig = BudgetConfig(fraction=1.0, evaluations=None), include_minimal_masks: bool = False, neyman: NeymanConfig = NeymanConfig(use_standard_method=False, initial_fraction=None, initial_num_samples=None), max_inflight: Annotated[int, Ge(ge=1)] = 2, max_players: Annotated[int, Ge(ge=1), Le(le=62)] = 24, archive: ArchiveConfig = ArchiveConfig(path='./runs/{run_id}', flush_every=50, on_exists='error'), dedup: DedupConfig = DedupConfig(enabled='auto'), kv_cache: KvCacheConfig = KvCacheConfig(enabled=False, max_entries=8), embedding_mode: EmbeddingMode = EmbeddingMode.STATIC, seed: int = 42, plugin_options: dict[str, dict[str, object]] = {})[source]¶
Bases:
BaseModelExplanation algorithm and runtime controls.
- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- budget: BudgetConfig¶
Estimator budget controls.
- neyman: NeymanConfig¶
Neyman-CC controls applied when the Neyman estimator is selected.
- max_players: int¶
Runs planning more than 2^max_players coalitions (fraction * 2^n, or evaluations) are refused. The ceiling of 62 keeps 2^n inside a signed 64-bit integer, which the samplers’ NumPy mask arithmetic needs.
- archive: ArchiveConfig¶
Run archive persistence settings.
- dedup: DedupConfig¶
Coalition deduplication settings.
- kv_cache: KvCacheConfig¶
Prefix cache settings for supported backends.
- embedding_mode: EmbeddingMode¶
Embedding mode used by embedding-based value functions.
- class mllm_shap.pipeline.config.GenerationConfig(*, max_new_tokens: int = 128, temperature: float = 0.0, top_k: int = 1, compute_embeddings: bool = False)[source]¶
Bases:
BaseModelGeneration parameters for the base and coalition evaluations.
- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mllm_shap.pipeline.config.KvCacheConfig(*, enabled: bool = False, max_entries: Annotated[int, Ge(ge=1)] = 8)[source]¶
Bases:
BaseModelPrefix cache options for supported backends.
- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mllm_shap.pipeline.config.NeymanConfig(*, use_standard_method: bool = False, initial_fraction: Annotated[float | None, Gt(gt=0.0), Le(le=1.0)] = None, initial_num_samples: Annotated[int | None, Ge(ge=1)] = None)[source]¶
Bases:
BaseModelNeyman-CC estimator controls used when
estimatorisneyman_cc.- model_config = {'extra': 'forbid', 'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- mllm_shap.pipeline.config.explain_config_from_yaml(text: str) ExplainConfig[source]¶
Parse YAML text into a validated
ExplainConfig.Unknown keys and values of the wrong type or range raise pydantic’s
ValidationError.- Raises:
ConfigError – If the YAML root is not a mapping (an empty document included) or settings are inconsistent with each other (for example
estimator: exactwith a partial budget).
- mllm_shap.pipeline.config.explain_config_to_yaml(config: ExplainConfig) str[source]¶
Serialize a config to YAML with stable key ordering within sections.
Public explain pipeline entry point.
@domain pipeline @see [[knowledge/architecture/pipeline]]
- class mllm_shap.pipeline.runner.ExplainRunner(config: ExplainConfig, registry: PluginRegistry | None = None, telemetry: ObservabilitySink | None = None, progress: CoalitionProgress | None = None, backend: GenerativeBackend | None = None)[source]¶
Bases:
objectRun the explain pipeline for a conversation snapshot.
With an injected backend,
explain_sync()runs every call on one event loop owned by the runner, so backends holding loop-bound resources (HTTP clients, locks) work across calls.close(),aclose()or awith/async withblock close the backend and that loop.- property registry: PluginRegistry¶
Return the plugin registry used by this runner.
- async explain(snapshot: ConversationSnapshot) ExplainRunOutput[source]¶
Execute the async explain pipeline.
- async aclose() None[source]¶
Close the injected backend and the loop of
explain_sync().When
explain_sync()already ran, the backend is closed on its own loop (in a worker thread), since its resources are bound to that loop.
- close() None[source]¶
Close the injected backend and the loop of
explain_sync().- Raises:
RuntimeError – If called from a running event loop before
explain_sync()created its loop; useawait runner.aclose()there.
- explain_sync(snapshot: ConversationSnapshot) ExplainRunOutput[source]¶
Run
explain()to completion from synchronous code.Without an injected backend each call uses a fresh event loop. With one, every call reuses a loop the runner creates on first use and closes in
close()/aclose().- Raises:
RuntimeError – If called from a running event loop (for example Jupyter); use
await runner.explain(snapshot)there.
- reanalyze(archive_root: Path | str, strict: bool = False) ExplainRunOutput[source]¶
Rescore an archived run with the runner’s current explain settings.
Embedding value functions score hash embeddings of the archived text. When the archived run scored backend embeddings, this logs a WARNING, or with
strictraisesReanalyzeMismatchError.
Explain pipeline result types.
@domain pipeline @see [[knowledge/architecture/pipeline]] @tested-by [[tests/pipeline/test_result.py]]
- class mllm_shap.pipeline.result.PlayerLabelPayload[source]¶
Bases:
TypedDictJSON form of one player: its id and, when known, where it sits.
- class mllm_shap.pipeline.result.ExplainRunPayload[source]¶
Bases:
TypedDictJSON form of an explain or reanalyze run, keys in a stable order.
- labels: list[PlayerLabelPayload]¶
One entry per player, aligned with
values.
- metrics: dict[str, int]¶
Scheduler counters (
SchedulerMetrics).
- manifest: RunManifestPayload¶
Run manifest, as written next to the archive.
- class mllm_shap.pipeline.result.ExplainResult(estimand: Estimand, values: tuple[float, ...], base_value: float = 0.0, grand_value: float = 0.0)[source]¶
Bases:
objectAttribution output for a single explain run.
- class mllm_shap.pipeline.result.ExplainRunOutput(result: ExplainResult, metrics: SchedulerMetrics, run_id: str, manifest: RunManifest, perf: PerfSummary | None = None, player_set: PlayerSet | None = None)[source]¶
Bases:
objectExplain pipeline output including scheduler metrics.
- result: ExplainResult¶
Aggregated and normalized attribution values.
- metrics: SchedulerMetrics¶
Coalition scheduler counters for the run.
- manifest: RunManifest¶
Manifest metadata persisted with optional archives.
- perf: PerfSummary | None¶
Optional wall-clock breakdown for the run.
- to_dict() ExplainRunPayload[source]¶
Return a JSON-compatible payload with a stable key order.
Labels carry each player’s text and span when
player_setis known (explain); afterreanalyzeonly the archived player ids are available and the span fields areNone.
Versioned run manifest persisted next to every run archive.
@domain pipeline @see [[knowledge/architecture/pipeline]] @tested-by [[tests/pipeline/test_manifest.py]]
- class mllm_shap.pipeline.manifest.EmbeddingSource(*values)[source]¶
Bases:
StrEnumWhich embeddings the value function of a run scored.
- NONE = 'none'¶
The value function read no embeddings.
- HASH = 'hash'¶
Hash embeddings derived from the generated text, as
reanalyzescores.
- BACKEND = 'backend'¶
Model embeddings computed by the backend;
reanalyzecannot reproduce them.
- class mllm_shap.pipeline.manifest.RunManifest(estimand: Estimand, run_id: str, snapshot_id: str = '', estimator: str = '', value_fn: str = '', normalizer: str = '', absence_policy: str = '', players: str = '', backend_kind: str = '', model_id: str = '', player_ids: tuple[str, ...]=(), embedding_source: EmbeddingSource = EmbeddingSource.NONE, embedding_model: str = '', config: ExplainConfig | None = None, mllm_shap_version: str = <factory>, schema_version: int = 4)[source]¶
Bases:
objectTop-level metadata persisted alongside a run archive.
Only
estimandandrun_idare required so hand-built archives stay simple; the explain pipeline fills every field.- embedding_source: EmbeddingSource¶
Which embeddings the value function scored.
- embedding_model: str¶
Backend model that computed the embeddings when
embedding_sourceis"backend"; empty otherwise.
- config: ExplainConfig | None¶
Explain configuration of the run, when the pipeline wrote the manifest.
- to_dict() RunManifestPayload[source]¶
Serialize manifest fields to JSON-compatible primitives.
- class mllm_shap.pipeline.manifest.RunManifestPayload[source]¶
Bases:
TypedDictJSON-compatible manifest payload persisted with a run archive.
- embedding_source: str¶
Which embeddings the run scored, as an
EmbeddingSourcevalue.
- mllm_shap.pipeline.manifest.parse_manifest(payload: Mapping[str, object]) RunManifest[source]¶
Parse a JSON manifest payload into a run manifest.
A missing
embedding_sourcereads as"none"and a missingembedding_modelas empty.- Raises:
ManifestVersionError – If
schema_versionis missing or differs fromMANIFEST_SCHEMA_VERSION.ArchiveCorruptError – If a field has the wrong type or value, the recorded config is invalid, or the manifest records a config but lacks the run fields the explain pipeline always writes.
Reanalyze archived coalition generations without backend calls.
@domain pipeline @see [[knowledge/architecture/pipeline]] @tested-by [[tests/pipeline/test_reanalyze.py]]
- mllm_shap.pipeline.reanalyze.reanalyze_sync(archive_root: Path, config: ExplainConfig, registry: PluginRegistry, *, manifest: RunManifest | None = None, telemetry: ObservabilitySink | None = None, strict: bool = False) ExplainRunOutput[source]¶
Rescore and re-aggregate an archived explain run without backend calls.
The archived estimator re-aggregates the coalitions, so
reanalyzereproducesexplainfor the same value function. Archives store text only, so embedding value functions score hash embeddings of that text; when the archived run scored backend embeddings this logs a WARNING, or raises withstrict.- Raises:
ValueError – If the archive has no coalition rows or no base generation.
ArchiveNotFoundError – If the manifest or the SQLite database is missing.
ArchiveCorruptError – If the manifest or the database cannot be read.
ManifestVersionError – If the archive was written by another release.
ArchiveIncompleteError – If the archive was not written by
ExplainRunner, its run did not finish, it lacks the grand or empty coalition, or a full-budget run (budget.fraction1.0, which includes everyexactrun) lacks some of the 2^n coalitions.ReanalyzeMismatchError – If
configuses a different estimator, absence policy or partition than the archived run, the value function needs logprobs and a row has none, or, withstrict, the value function reads embeddings and the archived run scored backend embeddings.ConfigError – If
explanation.plugin_optionsnames a plugin outside the run or holds an unknown option.
Deletion curves: how fast a ranking of players destroys the answer.
@domain pipeline @see [[knowledge/architecture/pipeline]] @tested-by [[tests/pipeline/test_faithfulness.py]]
- class mllm_shap.pipeline.faithfulness.DeletionCurve(num_players: int, grand_value: float, removed: tuple[int, ...], values: tuple[float, ...])[source]¶
Bases:
objectValue of the coalition left after removing the top-
kplayers, perk.
- async mllm_shap.pipeline.faithfulness.deletion_curve(config: ExplainConfig, snapshot: ConversationSnapshot, attributions: Sequence[float], ks: Sequence[int] | None = None, registry: PluginRegistry | None = None, backend: GenerativeBackend | None = None) DeletionCurve[source]¶
Score
snapshotwith itskhighest-attributed players removed at once.Players are ranked by
attributions(ties keep player order) and eachkinks(default0..n) removes the topktogether, never greedily, usingconfig’s partition, absence policy, backend and value function. Use a different value function than the explanation’s to avoid a circular check. Abackend(for example the explained run’s loaded model) is reused and left open; without one the run builds and closes its own.- Raises:
ValueError – If
attributionsdoes not have one value per player, or akis outside0..n.
Explain pipeline orchestration.
@domain pipeline @see [[knowledge/architecture/pipeline]] @tested-by [[tests/pipeline/test_orchestrator.py]]
- class mllm_shap.pipeline.orchestrator.ExplainOrchestrator(context: ExplainContext, backend: GenerativeBackend)[source]¶
Bases:
objectWire partition, sampling, generation, scoring, and aggregation.
- async run() ExplainRunOutput[source]¶
Execute the configured explain pipeline and return attributions.
- mllm_shap.pipeline.orchestrator.score_coalitions(value_fn: ValueFunction, base_record: GenerationRecord, coalition_records: Sequence[GenerationRecord]) list[float][source]¶
Fit a corpus-dependent
value_fnon all records, then score each coalition.
Shared state for one explain pipeline run.
@domain pipeline @see [[knowledge/architecture/pipeline]]
- class mllm_shap.pipeline.context.ExplainContext(config: ExplainConfig, snapshot: ConversationSnapshot, player_set: PlayerSet, registry: PluginRegistry, run_id: str, archive_root: Path | None = None, telemetry: ObservabilitySink | None = None, progress: CoalitionProgress | None = None)[source]¶
Bases:
objectInputs resolved for one explain orchestrator run.
- config: ExplainConfig¶
Validated explain configuration.
- snapshot: ConversationSnapshot¶
Conversation under explanation.
- registry: PluginRegistry¶
Plugin registry used to resolve pipeline components.
- telemetry: ObservabilitySink | None¶
Optional observability sink for stage spans.
- progress: CoalitionProgress | None¶
Optional sync sink for coalition scheduling progress.
- plugin_context() PluginContext[source]¶
Return the context passed to plugin factories for this run.
- resolved_telemetry() ObservabilitySink[source]¶
Return the configured telemetry sink or a no-op implementation.
- resolved_progress() CoalitionProgress[source]¶
Return the configured progress sink or a no-op implementation.