Getting started

This page gets you from zero to a first token attribution in a few minutes, and shows the two numbers every Shapley explanation is checked against.

Installation

Install from PyPI (Python 3.12 or newer):

pip install mllm-shap

From a local clone (development uses uv):

git clone https://github.com/Pawlo77/MLLM-SHAP
cd MLLM-SHAP
make install

Your first aggregation

The estimand aggregators take boolean coalition masks and aligned payoffs \(v(S)\). On a three-player majority game, Shapley and Banzhaf disagree:

from itertools import product

from mllm_shap import BanzhafAggregator, ShapleyAggregator

num_players = 3
masks = [tuple(bits) for bits in product([False, True], repeat=num_players)]

def majority_payoff(mask: tuple[bool, ...]) -> float:
    return 1.0 if sum(mask) >= 2 else 0.0

payoffs = [majority_payoff(mask) for mask in masks]

shapley = ShapleyAggregator().aggregate(masks, payoffs)
banzhaf = BanzhafAggregator().aggregate(masks, payoffs)

print("Shapley:", shapley)
print("Banzhaf:", banzhaf)

Output

Shapley: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
Banzhaf: [0.5, 0.5, 0.5]

See Using estimand aggregators for why the two differ.

Explain a short prompt

For end-to-end token attributions, build a ConversationSnapshot, validate an ExplainConfig, and call ExplainRunner. The mock backend needs no model, GPU or optional extra:

import math

from mllm_shap import (
    ConversationSnapshot,
    ExplainConfig,
    ExplainRunner,
    Message,
    Role,
    Turn,
)

snapshot = ConversationSnapshot.from_turns((
    Turn(messages=(Message(role=Role.USER, text="refund my order"),)),
))
config = ExplainConfig.model_validate({
    "backend": {"kind": "mock", "model_id": "stub"},
    "explanation": {
        "estimator": "exact",
        "value_fn": "tfidf_cosine",
        "absence_policy": "pad",
        "archive": {"path": ""},  # keep this demo off disk
    },
})
output = ExplainRunner(config).explain_sync(snapshot)
result = output.result

print("values:", tuple(round(value, 4) for value in result.values))
print("base_value v(empty):", round(result.base_value, 4))
print("grand_value v(N):", round(result.grand_value, 4))
# Efficiency: Shapley values sum to v(N) - v(empty).
print(
    "efficient:",
    math.isclose(sum(result.values), result.grand_value - result.base_value),
)

Output

values: (0.3333, 0.3333, 0.3333)
base_value v(empty): 0.0
grand_value v(N): 1.0
efficient: True

Each token of "refund my order" is a player. base_value is the payoff of the fully masked prompt and grand_value the payoff of the full prompt; Shapley values distribute the difference. With the default identity normalizer the check above holds for every exact run and is the first thing to verify when an attribution looks wrong.

Without archive.path: "" the run is archived under ./runs/{run_id}, and re-running the same configuration on the same snapshot raises ArchiveExistsError unless explanation.archive.on_exists is overwrite. See Configuration.

Which estimator?

exact scores all \(2^n\) coalitions and is only practical for short prompts. For longer inputs pick mc, complementary or neyman_cc and a budget.fraction below 1; Choosing an estimator has the decision table and Using approximate estimators the code.

Label results for archives

Every run carries a RunManifest that records the estimand, estimator, value function and players. It round-trips through JSON:

from mllm_shap import parse_manifest

restored = parse_manifest(output.manifest.to_dict())
print("schema_version:", restored.schema_version)
print("estimand:", restored.estimand.value)
print("estimator:", restored.estimator)
print("players:", restored.player_ids)

Output

schema_version: 4
estimand: shapley
estimator: exact
players: ('f5986ca22ea8567e:0:0:0', 'f5986ca22ea8567e:0:0:1', 'f5986ca22ea8567e:0:0:2')

Logging

Importing mllm_shap leaves your logging configuration alone. Call mllm_shap.configure_logging(level="INFO") to see the pipeline’s stage events (run start, base generation, scheduled coalitions and run duration) on stderr, mllm_shap.configure_logging() to apply the [tool.logging] section of the nearest pyproject.toml (or pass a path), or set MLLM_SHAP_LOGGING_CONFIG. Runtime core lists the events.

Next steps