Skip to content

Context Reduction

Fit a document corpus to a token budget. The application collects; this subpackage reduces. The reasoning and the strategy tradeoffs are in context reduction; the task-oriented walkthrough is fitting a corpus to a budget.

Imported from its own path, like the other optional subsystems:

from anyinfer import context

Documents and Results

anyinfer.context.ContextDocument dataclass

ContextDocument(
    path: str,
    content: str,
    sha256: str,
    pinned: bool = False,
    language: str | None = None,
    extract: str = "",
)

One document offered to the reducer.

Attributes:

Name Type Description
path str

POSIX-style relative path. Doubles as identity and as a ranking signal — a query term matching the path outweighs the same term in the body.

content str

The document's text.

sha256 str

Hex digest of content, used for identity in rendered envelopes and for deterministic tie-breaking.

pinned bool

Sorts before every unpinned document and is selected first. This is how an app says "the user explicitly chose this file".

language str | None

Language name for rendering and rollups, detected when omitted.

extract str

A structural summary (signatures, imports, headings) used by the tiered strategy when the whole document does not fit. Empty means none.

bytes_length property

bytes_length: int

UTF-8 byte length of the content.

of classmethod

of(
    path: str,
    content: str,
    *,
    pinned: bool = False,
    language: str | None = None,
    extract: str | None = None,
) -> ContextDocument

Build a document, computing its digest and filling in what was omitted.

Parameters:

Name Type Description Default
path str

POSIX-style relative path.

required
content str

The document's text.

required
pinned bool

Whether the document must be included ahead of ranked candidates.

False
language str | None

Overrides language detection.

None
extract str | None

Overrides extraction. Pass "" to opt out of it entirely; omit it to have one derived from the content.

None

Returns:

Type Description
ContextDocument

The document, with sha256 computed and language/extract derived unless

ContextDocument

they were supplied.

anyinfer.context.Reduction dataclass

Reduction(
    strategy: str,
    representation: str,
    documents: tuple[ContextDocument, ...],
    candidate_count: int,
    text: str,
    estimated_tokens: int,
    max_tokens: int,
    max_bytes: int,
    max_documents: int,
    total_bytes: int,
    binding_constraints: tuple[str, ...] = (),
    collapsed_exact: int = 0,
    collapsed_near: int = 0,
    compacted_count: int = 0,
    partial_count: int = 0,
    carried_over: int = 0,
    tier_metadata: Mapping[str, Any] | None = None,
)

What a reduction produced, and what it cost.

Attributes:

Name Type Description
strategy str

The strategy that was requested. auto stays auto even after dispatch, so the caller can see what they asked for.

representation str

The strategy actually applied — what auto resolved to.

documents tuple[ContextDocument, ...]

Documents represented at full, compact, or extract fidelity. In tiered this is the set actually rendered in detail, not the ranked prefix.

candidate_count int

How many documents were offered.

text str

The rendered envelope. Always present — place it in your own message.

estimated_tokens int

Planning-side estimate of text.

max_tokens int

The token budget this reduction was held to.

max_bytes int

The byte ceiling in force.

max_documents int

The document-count ceiling in force.

total_bytes int

UTF-8 byte length of text.

binding_constraints tuple[str, ...]

Which ceilings excluded at least one document, in the fixed order ("document count", "bytes", "tokens"). Empty means everything fit.

collapsed_exact int

Documents rendered as a pointer because a byte-identical copy was sent. Lossless.

collapsed_near int

Documents rendered as a pointer because a similar copy was sent. Their differences are not in the envelope.

compacted_count int

Documents sent with commentary removed because they would not fit whole.

partial_count int

Documents represented by only part of their content — the spans packed selected, rather than the whole file.

carried_over int

Documents this reduction kept because the previous one had them, when previous= was supplied.

tier_metadata Mapping[str, Any] | None

Strategy-specific detail (tier composition, chunk counts).

collapsed_count property

collapsed_count: int

How many documents were represented by a pointer to another.

omitted_count property

omitted_count: int

How many offered documents are not represented at all.

Collapsed duplicates are not omitted: their content reached the model under another path, and the envelope says so.

complete property

complete: bool

Whether every offered document reached the model at full fidelity.

Exact collapse preserves completeness — the same bytes were sent, once. Near collapse, compaction, and chunk-level selection do not: each drops content that was offered, and a strategy that sends fragments must not report the same confidence as one that sends files.

state

state() -> ReductionState

Capture what was sent, to hand back as the next turn's previous=.

metadata

metadata() -> dict[str, Any]

The full machine-readable record, for logging or a debug pane.

summary

summary() -> str

A one-line, content-free description of what happened.

Safe to show a user or write to a log: counts and ceilings only, never paths or content — a path name can itself be sensitive.

event

event(*, calls: int = 0) -> ContextReduced

Build the telemetry event describing this reduction.

anyinfer.context.ReductionState dataclass

ReductionState(
    entries: tuple[tuple[str, str], ...] = (),
    representation: str = "",
)

What an earlier reduction sent, so the next one can send the same thing.

Selection is deterministic given the same inputs, but a corpus changes between turns and a re-ranked selection can churn for no reason — swapping one file for an equally ranked other, moving the whole prompt prefix and missing the provider's cache. Hand the previous state back through select(previous=...) and unchanged documents get a rank bonus, so the set stays put unless something real moved it.

Attributes:

Name Type Description
entries tuple[tuple[str, str], ...]

(path, sha256) for every document rendered at detail fidelity, in path order.

representation str

The strategy that produced it, for diagnostics.

of classmethod

of(reduction: Reduction) -> ReductionState

Capture the state of a completed reduction.

unchanged

unchanged(
    documents: Sequence[ContextDocument],
) -> frozenset[str]

Paths present here and still byte-identical in documents.

A path whose content changed is deliberately excluded: carrying it over would move the prompt prefix anyway, so there is nothing to preserve.

metadata

metadata() -> dict[str, Any]

The machine-readable record, content-free apart from paths the caller owns.

anyinfer.context.Strategy module-attribute

Strategy = Literal[
    "auto", "whole", "ranked", "tiered", "packed"
]

Names accepted by select's strategy argument.

anyinfer.context.RenderOrder module-attribute

RenderOrder = Literal['path', 'rank']

Whether selected documents render by stable path order or relevance rank.

anyinfer.context.RankCache dataclass

RankCache(
    term_counts: dict[str, Counter[str]] = dict(),
    document_frequency: Counter[str] = Counter(),
    document_lengths: dict[str, int] = dict(),
    total_documents: int = 0,
    split_identifiers: bool = False,
)

Precomputed term statistics for one corpus.

Ranking a corpus repeatedly — as an interactive app does on every turn — otherwise re-tokenizes every document each time. Build one with anyinfer.context.rank.build_rank_cache() and pass it back in.

Validity is the caller's responsibility: key it on a corpus hash and rebuild when the corpus changes. Passing a cache built from different documents produces undefined ranking rather than an error, which is why the cache is not consulted for document identity.

Attributes:

Name Type Description
term_counts dict[str, Counter[str]]

Per-document term frequencies, keyed by document path.

document_frequency Counter[str]

How many documents contain each term.

document_lengths dict[str, int]

Total token count per document, for length normalization.

total_documents int

Corpus size, for the inverse-document-frequency term.

split_identifiers bool

Which tokenization produced these counts. Ranking checks it and rebuilds rather than scoring a query tokenized one way against statistics gathered the other — a mismatch there produces plausible, wrong ordering.

Advanced Settings

One record carries every algorithmic choice. The same field names are the context block of the configuration file and the --context-* flags of anyinfer context. Every setting that changes what gets sent is off by default; ContextTuning.recommended() enables the set worth having for a source-code corpus.

Setting Default What it changes
collapse_duplicates True Render byte-identical documents once
near_duplicate_threshold 0.0 Collapse merely similar documents too
selection_order "rank" "density" admits by score per token
diversity 0.0 Penalize candidates resembling what is already chosen
split_identifiers False Tokenize compound identifiers into their parts
query_expansion False Pseudo-relevance feedback before ranking
salience_weight 0.0 Blend in import-graph centrality
compact_fallback False Shorten a document rather than drop it
carry_over_bonus 0.0 Keep the previous turn's selection, with previous=
chunk_tokens 512 Chunk size for packed and distill
rollup_share 0.45 Budget share tiered reserves for its rollup

Two orderings deserve a note. "rank" admits documents strongest-first; "density" admits them by score divided by token cost, which packs measurably more relevance into a fixed budget (the classic knapsack result) at the risk of preferring two good small files over one great large one. diversity penalizes each candidate by how much it resembles what is already selected (multiplicatively, value * (1 - diversity * similarity), because the two value scales differ by orders of magnitude), so a budget is not spent on eight files that say the same thing.

anyinfer.context.ContextTuning dataclass

ContextTuning(
    collapse_duplicates: bool = True,
    near_duplicate_threshold: float = 0.0,
    shingle_size: int = 5,
    selection_order: SelectionOrder = "rank",
    diversity: float = 0.0,
    split_identifiers: bool = False,
    query_expansion: bool = False,
    expansion_terms: int = 8,
    feedback_documents: int = 5,
    expansion_weight: float = 0.4,
    salience_weight: float = 0.0,
    salience_damping: float = 0.85,
    salience_iterations: int = 20,
    compact_fallback: bool = False,
    chunk_tokens: int = DEFAULT_CHUNK_TOKENS,
    rollup_share: float = 0.45,
    carry_over_bonus: float = 0.0,
)

Advanced settings for corpus reduction.

Every field defaults to the behaviour AnyInfer has always had, except collapse_duplicates. Construct one, or start from recommended() and override.

Attributes:

Name Type Description
collapse_duplicates bool

Render byte-identical documents once, with the rest as pointer elements. Lossless — the content is still present, and on by default, since sending the same bytes twice helps nobody.

near_duplicate_threshold float

Jaccard similarity at or above which two documents are treated as duplicates of each other. 0.0 disables near-duplicate detection; 0.9 is a good starting point for vendored or generated siblings. Lossy: the near-duplicate's differences are not sent, so it is off by default.

shingle_size int

Word count per shingle for near-duplicate comparison. Larger is stricter.

selection_order SelectionOrder

rank admits documents strongest-first. density admits them by score per token, which fits measurably more relevance into the same budget at the cost of sometimes preferring two good small files to one great large one.

diversity float

Similarity penalty applied to candidates resembling what is already selected, between 0.0 (pure relevance) and 1.0 (near-pure novelty). Stops a budget being spent on eight files that say the same thing.

split_identifiers bool

Tokenize resolve_credentials and resolveCredentials as their parts and as the whole, so a query of "resolve credentials" matches the identifier that names it.

query_expansion bool

Rank once, harvest distinctive terms from the strongest documents, then re-rank with the expanded query. The lexical answer to vocabulary mismatch: it finds "login" from "authentication" whenever the two co-occur anywhere in the corpus. Costs a second ranking pass, no inference.

expansion_terms int

How many harvested terms to add.

feedback_documents int

How many top-ranked documents to harvest them from.

expansion_weight float

Weight of an expansion term relative to an original query term.

salience_weight float

How much a document's centrality in the corpus's own import graph contributes to its score. The signal is query-independent, so this is what orders a corpus when the query is weak or absent; 0.0 disables the graph pass entirely.

salience_damping float

Random-restart probability complement for the centrality iteration. The conventional 0.85.

salience_iterations int

Fixed iteration count. Fixed rather than convergence-tested, because determinism outranks the last decimal place.

compact_fallback bool

When a document will not fit whole, send it with comments, docstrings, and blank runs removed before giving up on it. Elisions are counted in the rendered element, never silent.

chunk_tokens int

Target chunk size for packed and for distill.

rollup_share float

Share of the budget tiered reserves for its module rollup.

carry_over_bonus float

Rank bonus applied to documents an earlier reduction already sent unchanged, when previous= is supplied. Keeps the selected set — and therefore the rendered prefix — stable across turns so provider prompt caches keep hitting. 0.0 ranks each turn from scratch.

ranking_is_default property

ranking_is_default: bool

Whether ranking behaves exactly as the unconfigured ranker does.

Selection consults this to skip the expansion and centrality passes entirely rather than running them with neutral parameters.

__post_init__

__post_init__() -> None

Reject settings that cannot produce a usable reduction.

Raises:

Type Description
ValueError

On an out-of-range or non-finite value, naming the field.

recommended classmethod

recommended() -> ContextTuning

The settings worth turning on for a typical source-code corpus.

Near-duplicate collapse at a strict threshold, density-ordered selection with a mild diversity penalty, identifier splitting and query expansion, a light centrality signal, and compact fallback instead of dropping a file outright. Every one of these changes what gets sent, which is why they are a named preset rather than the default.

from_mapping classmethod

from_mapping(values: Mapping[str, Any]) -> ContextTuning

Build settings from a JSON-shaped mapping.

Used by the shared configuration loader and the CLI, so a context block in a config file and a --context-* flag mean exactly the same thing.

Parameters:

Name Type Description Default
values Mapping[str, Any]

Field names to values. Unknown names are an error rather than being ignored, so a typo does not silently do nothing.

required

Returns:

Type Description
ContextTuning

The settings.

Raises:

Type Description
ValueError

On an unknown key, a wrong type, or an out-of-range value.

merged

merged(**overrides: Any) -> ContextTuning

Return a copy with overrides applied, dropping any that are None.

The shape command-line parsing wants: unspecified flags arrive as None and must leave the configured value alone.

to_mapping

to_mapping() -> dict[str, Any]

The settings as a JSON-shaped mapping, for logging or round-tripping.

anyinfer.context.SelectionOrder module-attribute

SelectionOrder = Literal['rank', 'density']

How the greedy selector orders candidates.

anyinfer.context.SELECTION_ORDERS module-attribute

SELECTION_ORDERS = ('rank', 'density')

Accepted ContextTuning.selection_order values.

Selection

anyinfer.context.select

Corpus selection: the strategies, the result type, and the dispatch rule.

Reduction is emulation of a larger context window, and emulation announces itself. Every reduction returns what it kept, what it dropped, what it collapsed, which ceiling bound it, and a content-free summary — plus a ContextReduced telemetry event when an observer is supplied. A silent truncation that looks like a complete answer is the failure mode this module exists to prevent.

Everything algorithmic is a setting rather than a constant. ContextTuning decides how duplicates collapse, whether candidates are ordered by relevance or relevance per token, whether near-identical documents are penalized against each other, and what a document degrades to instead of being dropped. The defaults reproduce the plain behaviour exactly, so turning nothing on changes nothing.

DEFAULT_MAX_DOCUMENTS module-attribute

DEFAULT_MAX_DOCUMENTS = 200

Ceiling on documents in one envelope; a long tail of tiny files helps nobody.

DEFAULT_MAX_BYTES module-attribute

DEFAULT_MAX_BYTES = 4 * 1024 * 1024

Byte ceiling, enforced independently of tokens because transports cap bytes.

VALID_STRATEGIES module-attribute

VALID_STRATEGIES = (
    "auto",
    "whole",
    "ranked",
    "tiered",
    "packed",
)

Strategy names select() accepts.

Strategy module-attribute

Strategy = Literal[
    "auto", "whole", "ranked", "tiered", "packed"
]

Names accepted by select's strategy argument.

RenderOrder module-attribute

RenderOrder = Literal['path', 'rank']

Whether selected documents render by stable path order or relevance rank.

ReductionState dataclass

ReductionState(
    entries: tuple[tuple[str, str], ...] = (),
    representation: str = "",
)

What an earlier reduction sent, so the next one can send the same thing.

Selection is deterministic given the same inputs, but a corpus changes between turns and a re-ranked selection can churn for no reason — swapping one file for an equally ranked other, moving the whole prompt prefix and missing the provider's cache. Hand the previous state back through select(previous=...) and unchanged documents get a rank bonus, so the set stays put unless something real moved it.

Attributes:

Name Type Description
entries tuple[tuple[str, str], ...]

(path, sha256) for every document rendered at detail fidelity, in path order.

representation str

The strategy that produced it, for diagnostics.

of classmethod

of(reduction: Reduction) -> ReductionState

Capture the state of a completed reduction.

unchanged

unchanged(
    documents: Sequence[ContextDocument],
) -> frozenset[str]

Paths present here and still byte-identical in documents.

A path whose content changed is deliberately excluded: carrying it over would move the prompt prefix anyway, so there is nothing to preserve.

metadata

metadata() -> dict[str, Any]

The machine-readable record, content-free apart from paths the caller owns.

Reduction dataclass

Reduction(
    strategy: str,
    representation: str,
    documents: tuple[ContextDocument, ...],
    candidate_count: int,
    text: str,
    estimated_tokens: int,
    max_tokens: int,
    max_bytes: int,
    max_documents: int,
    total_bytes: int,
    binding_constraints: tuple[str, ...] = (),
    collapsed_exact: int = 0,
    collapsed_near: int = 0,
    compacted_count: int = 0,
    partial_count: int = 0,
    carried_over: int = 0,
    tier_metadata: Mapping[str, Any] | None = None,
)

What a reduction produced, and what it cost.

Attributes:

Name Type Description
strategy str

The strategy that was requested. auto stays auto even after dispatch, so the caller can see what they asked for.

representation str

The strategy actually applied — what auto resolved to.

documents tuple[ContextDocument, ...]

Documents represented at full, compact, or extract fidelity. In tiered this is the set actually rendered in detail, not the ranked prefix.

candidate_count int

How many documents were offered.

text str

The rendered envelope. Always present — place it in your own message.

estimated_tokens int

Planning-side estimate of text.

max_tokens int

The token budget this reduction was held to.

max_bytes int

The byte ceiling in force.

max_documents int

The document-count ceiling in force.

total_bytes int

UTF-8 byte length of text.

binding_constraints tuple[str, ...]

Which ceilings excluded at least one document, in the fixed order ("document count", "bytes", "tokens"). Empty means everything fit.

collapsed_exact int

Documents rendered as a pointer because a byte-identical copy was sent. Lossless.

collapsed_near int

Documents rendered as a pointer because a similar copy was sent. Their differences are not in the envelope.

compacted_count int

Documents sent with commentary removed because they would not fit whole.

partial_count int

Documents represented by only part of their content — the spans packed selected, rather than the whole file.

carried_over int

Documents this reduction kept because the previous one had them, when previous= was supplied.

tier_metadata Mapping[str, Any] | None

Strategy-specific detail (tier composition, chunk counts).

collapsed_count property

collapsed_count: int

How many documents were represented by a pointer to another.

omitted_count property

omitted_count: int

How many offered documents are not represented at all.

Collapsed duplicates are not omitted: their content reached the model under another path, and the envelope says so.

complete property

complete: bool

Whether every offered document reached the model at full fidelity.

Exact collapse preserves completeness — the same bytes were sent, once. Near collapse, compaction, and chunk-level selection do not: each drops content that was offered, and a strategy that sends fragments must not report the same confidence as one that sends files.

state

state() -> ReductionState

Capture what was sent, to hand back as the next turn's previous=.

metadata

metadata() -> dict[str, Any]

The full machine-readable record, for logging or a debug pane.

summary

summary() -> str

A one-line, content-free description of what happened.

Safe to show a user or write to a log: counts and ceilings only, never paths or content — a path name can itself be sensitive.

event

event(*, calls: int = 0) -> ContextReduced

Build the telemetry event describing this reduction.

StrategyOutlook dataclass

StrategyOutlook(
    strategy: str,
    representation: str,
    selected_count: int,
    omitted_count: int,
    collapsed_count: int,
    compacted_count: int,
    partial_count: int,
    estimated_tokens: int,
    total_bytes: int,
    binding_constraints: tuple[str, ...],
    complete: bool,
)

What one strategy would produce, costed exactly rather than modelled.

Attributes:

Name Type Description
strategy str

The strategy this describes.

representation str

What it resolved to — identical to strategy here, since plan() never costs auto.

selected_count int

Documents it would represent at detail fidelity.

omitted_count int

Documents it would not represent at all.

collapsed_count int

Documents it would render as a pointer to another.

compacted_count int

Documents it would shorten rather than drop.

partial_count int

Documents it would represent by fragments rather than whole.

estimated_tokens int

Planning-side estimate of the envelope it would render.

total_bytes int

Byte length of that envelope.

binding_constraints tuple[str, ...]

Which ceilings would bind.

complete bool

Whether it would send everything at full fidelity.

of classmethod

of(strategy: str, reduction: Reduction) -> StrategyOutlook

Describe a reduction that was run purely to be measured.

metadata

metadata() -> dict[str, Any]

The machine-readable record.

ReductionPlan dataclass

ReductionPlan(
    candidate_count: int,
    max_tokens: int,
    options: tuple[StrategyOutlook, ...],
    distill_chunks: int,
    distill_calls: int,
)

What every strategy would do with this corpus and this budget.

A dry run for context preparation, in the same spirit as the pre-dispatch request preflight: choose a strategy from measured outcomes instead of guessing and finding out in the transcript. Costing it spends no inference and touches no network — the deterministic strategies are actually executed and their envelopes measured, then discarded, so the numbers are exact rather than modelled.

Attributes:

Name Type Description
candidate_count int

Documents offered.

max_tokens int

The budget every option was held to.

options tuple[StrategyOutlook, ...]

One outlook per deterministic strategy, in a fixed order.

distill_chunks int

How many chunks anyinfer.context.distill would split this corpus into at the configured chunk size.

distill_calls int

The floor on generation calls distillation would spend — one per chunk plus a single reduce. A corpus whose notes do not fit at once reduces hierarchically and spends more.

option

option(strategy: str) -> StrategyOutlook | None

The outlook for one strategy, or None if it was not costed.

best

best() -> StrategyOutlook | None

The option that gets the most of the corpus to the model.

Prefers an option that sends everything at full fidelity; failing that, the one representing the most documents at detail fidelity; and among equals, the one that represents them most faithfully — a whole file beats a summary beats a fragment. It is a recommendation, not a decision: an app that would rather have twelve whole files than four hundred summarized ones should read options and pick for itself.

metadata

metadata() -> dict[str, Any]

The full machine-readable record.

summary

summary() -> str

A one-line, content-free description of the plan.

normalize_strategy

normalize_strategy(value: str | None) -> str

Normalize a strategy name.

Parameters:

Name Type Description Default
value str | None

The requested strategy; None or blank means "auto".

required

Returns:

Type Description
str

The lowercase strategy name.

Raises:

Type Description
ValueError

On an unrecognized strategy, listing the valid names.

select

select(
    documents: Iterable[ContextDocument],
    query: str,
    *,
    max_tokens: int,
    strategy: str = "auto",
    max_documents: int = DEFAULT_MAX_DOCUMENTS,
    max_bytes: int = DEFAULT_MAX_BYTES,
    estimator: TokenEstimator | None = None,
    rank_cache: RankCache | None = None,
    module_digests: Mapping[str, str] | None = None,
    render_order: RenderOrder = "path",
    tuning: ContextTuning | None = None,
    previous: ReductionState | None = None,
    observer: Observer | None = None,
    ranker: SemanticRanker | None = None,
) -> Reduction

Reduce a corpus to fit a token budget.

Parameters:

Name Type Description Default
documents Iterable[ContextDocument]

The corpus the app has already collected and approved.

required
query str

What the request is about, used for relevance ranking.

required
max_tokens int

The token budget. Normally client.budget(messages, target=...).remaining_tokens — an explicit number, because an unknown context window stays unknown rather than being guessed at.

required
strategy str

auto (default), whole, ranked, tiered, or packed.

'auto'
max_documents int

Ceiling on documents represented.

DEFAULT_MAX_DOCUMENTS
max_bytes int

Ceiling on envelope bytes.

DEFAULT_MAX_BYTES
estimator TokenEstimator | None

Token counting strategy; defaults to the byte heuristic.

None
rank_cache RankCache | None

Precomputed corpus statistics, for repeated queries.

None
module_digests Mapping[str, str] | None

App-supplied per-module summaries, rendered by tiered. The library never generates these.

None
render_order RenderOrder

path (default) renders selected documents in path order regardless of rank, so consecutive turns over the same corpus share a stable prompt prefix and provider prompt caches hit. rank renders strongest-first.

'path'
tuning ContextTuning | None

Advanced settings — duplicate collapse, selection order, diversity, query expansion, centrality, compact fallback. Defaults reproduce the plain behaviour exactly.

None
previous ReductionState | None

The state of the last reduction over this corpus. Unchanged documents get tuning.carry_over_bonus so the selected set, and the rendered prefix — stays stable across turns.

None
observer Observer | None

Receives a ContextReduced event describing the outcome.

None
ranker SemanticRanker | None

Caller-supplied semantic scoring (SemanticRanker). When set, its scores replace the lexical ranking for both ordering and admission — one scoring call per reduction. The default stays lexical and offline; build a rerank-backed implementation with anyinfer.semantic_ranker.

None

Returns:

Type Description
Reduction

The Reduction, whose text is the envelope to place in your own message.

Raises:

Type Description
ValueError

On an unknown strategy or a non-positive budget.

plan

plan(
    documents: Iterable[ContextDocument],
    query: str,
    *,
    max_tokens: int,
    max_documents: int = DEFAULT_MAX_DOCUMENTS,
    max_bytes: int = DEFAULT_MAX_BYTES,
    estimator: TokenEstimator | None = None,
    module_digests: Mapping[str, str] | None = None,
    tuning: ContextTuning | None = None,
) -> ReductionPlan

Cost every strategy against this corpus without committing to one.

Spends no inference and performs no I/O: each deterministic strategy is run, its envelope measured, and the text discarded. The distillation figures are projections — that is the only strategy whose cost cannot be known without paying it.

Parameters:

Name Type Description Default
documents Iterable[ContextDocument]

The corpus.

required
query str

What the request is about.

required
max_tokens int

The budget to hold every option to.

required
max_documents int

Ceiling on documents represented.

DEFAULT_MAX_DOCUMENTS
max_bytes int

Ceiling on envelope bytes.

DEFAULT_MAX_BYTES
estimator TokenEstimator | None

Token counting strategy; defaults to the byte heuristic.

None
module_digests Mapping[str, str] | None

App-supplied module summaries, costed into the tiered option.

None
tuning ContextTuning | None

Advanced settings, applied to every option so the comparison is fair.

None

Returns:

Type Description
ReductionPlan

The ReductionPlan.

Raises:

Type Description
ValueError

On a non-positive budget.

anyinfer.context.normalize_strategy

normalize_strategy(value: str | None) -> str

Normalize a strategy name.

Parameters:

Name Type Description Default
value str | None

The requested strategy; None or blank means "auto".

required

Returns:

Type Description
str

The lowercase strategy name.

Raises:

Type Description
ValueError

On an unrecognized strategy, listing the valid names.

anyinfer.context.VALID_STRATEGIES module-attribute

VALID_STRATEGIES = (
    "auto",
    "whole",
    "ranked",
    "tiered",
    "packed",
)

Strategy names select() accepts.

anyinfer.context.DEFAULT_MAX_DOCUMENTS module-attribute

DEFAULT_MAX_DOCUMENTS = 200

Ceiling on documents in one envelope; a long tail of tiny files helps nobody.

anyinfer.context.DEFAULT_MAX_BYTES module-attribute

DEFAULT_MAX_BYTES = 4 * 1024 * 1024

Byte ceiling, enforced independently of tokens because transports cap bytes.

Planning

Cost every strategy before committing to one. Spends no inference and performs no I/O.

anyinfer.context.plan

plan(
    documents: Iterable[ContextDocument],
    query: str,
    *,
    max_tokens: int,
    max_documents: int = DEFAULT_MAX_DOCUMENTS,
    max_bytes: int = DEFAULT_MAX_BYTES,
    estimator: TokenEstimator | None = None,
    module_digests: Mapping[str, str] | None = None,
    tuning: ContextTuning | None = None,
) -> ReductionPlan

Cost every strategy against this corpus without committing to one.

Spends no inference and performs no I/O: each deterministic strategy is run, its envelope measured, and the text discarded. The distillation figures are projections — that is the only strategy whose cost cannot be known without paying it.

Parameters:

Name Type Description Default
documents Iterable[ContextDocument]

The corpus.

required
query str

What the request is about.

required
max_tokens int

The budget to hold every option to.

required
max_documents int

Ceiling on documents represented.

DEFAULT_MAX_DOCUMENTS
max_bytes int

Ceiling on envelope bytes.

DEFAULT_MAX_BYTES
estimator TokenEstimator | None

Token counting strategy; defaults to the byte heuristic.

None
module_digests Mapping[str, str] | None

App-supplied module summaries, costed into the tiered option.

None
tuning ContextTuning | None

Advanced settings, applied to every option so the comparison is fair.

None

Returns:

Type Description
ReductionPlan

The ReductionPlan.

Raises:

Type Description
ValueError

On a non-positive budget.

anyinfer.context.ReductionPlan dataclass

ReductionPlan(
    candidate_count: int,
    max_tokens: int,
    options: tuple[StrategyOutlook, ...],
    distill_chunks: int,
    distill_calls: int,
)

What every strategy would do with this corpus and this budget.

A dry run for context preparation, in the same spirit as the pre-dispatch request preflight: choose a strategy from measured outcomes instead of guessing and finding out in the transcript. Costing it spends no inference and touches no network — the deterministic strategies are actually executed and their envelopes measured, then discarded, so the numbers are exact rather than modelled.

Attributes:

Name Type Description
candidate_count int

Documents offered.

max_tokens int

The budget every option was held to.

options tuple[StrategyOutlook, ...]

One outlook per deterministic strategy, in a fixed order.

distill_chunks int

How many chunks anyinfer.context.distill would split this corpus into at the configured chunk size.

distill_calls int

The floor on generation calls distillation would spend — one per chunk plus a single reduce. A corpus whose notes do not fit at once reduces hierarchically and spends more.

option

option(strategy: str) -> StrategyOutlook | None

The outlook for one strategy, or None if it was not costed.

best

best() -> StrategyOutlook | None

The option that gets the most of the corpus to the model.

Prefers an option that sends everything at full fidelity; failing that, the one representing the most documents at detail fidelity; and among equals, the one that represents them most faithfully — a whole file beats a summary beats a fragment. It is a recommendation, not a decision: an app that would rather have twelve whole files than four hundred summarized ones should read options and pick for itself.

metadata

metadata() -> dict[str, Any]

The full machine-readable record.

summary

summary() -> str

A one-line, content-free description of the plan.

anyinfer.context.StrategyOutlook dataclass

StrategyOutlook(
    strategy: str,
    representation: str,
    selected_count: int,
    omitted_count: int,
    collapsed_count: int,
    compacted_count: int,
    partial_count: int,
    estimated_tokens: int,
    total_bytes: int,
    binding_constraints: tuple[str, ...],
    complete: bool,
)

What one strategy would produce, costed exactly rather than modelled.

Attributes:

Name Type Description
strategy str

The strategy this describes.

representation str

What it resolved to — identical to strategy here, since plan() never costs auto.

selected_count int

Documents it would represent at detail fidelity.

omitted_count int

Documents it would not represent at all.

collapsed_count int

Documents it would render as a pointer to another.

compacted_count int

Documents it would shorten rather than drop.

partial_count int

Documents it would represent by fragments rather than whole.

estimated_tokens int

Planning-side estimate of the envelope it would render.

total_bytes int

Byte length of that envelope.

binding_constraints tuple[str, ...]

Which ceilings would bind.

complete bool

Whether it would send everything at full fidelity.

of classmethod

of(strategy: str, reduction: Reduction) -> StrategyOutlook

Describe a reduction that was run purely to be measured.

metadata

metadata() -> dict[str, Any]

The machine-readable record.

Duplicate Collapse

anyinfer.context.find_duplicates

find_duplicates(
    documents: Sequence[ContextDocument],
    *,
    tuning: ContextTuning = DEFAULT_TUNING,
) -> DuplicateMap

Group duplicate documents and choose one representative for each group.

Parameters:

Name Type Description Default
documents Sequence[ContextDocument]

The corpus.

required
tuning ContextTuning

Supplies collapse_duplicates, near_duplicate_threshold, and shingle_size.

DEFAULT_TUNING

Returns:

Type Description
DuplicateMap

The DuplicateMap. Empty when both mechanisms are disabled, when the corpus has

DuplicateMap

fewer than two documents, or when nothing repeated.

anyinfer.context.DuplicateMap dataclass

DuplicateMap(
    canonical: Mapping[str, str] = dict(),
    exact: frozenset[str] = frozenset(),
)

Which documents were collapsed into which.

Attributes:

Name Type Description
canonical Mapping[str, str]

Duplicate path to the path that represents it. A path absent from this mapping is itself canonical.

exact frozenset[str]

Paths collapsed because they were byte-identical, as opposed to merely similar. Rendering distinguishes the two, because one is lossless and the other is not.

collapsed_count property

collapsed_count: int

How many documents are represented by another.

__bool__

__bool__() -> bool

Whether anything was collapsed at all.

is_exact

is_exact(path: str) -> bool

Whether path was collapsed losslessly.

members

members(canonical_path: str) -> tuple[str, ...]

Paths collapsed into canonical_path, in path order.

History Compaction

Reduce a conversation rather than a corpus, without breaking tool-call pairing. Call it directly, or hand anyinfer.HistoryPolicy to a client and let every frontend built on that client apply the same rules on the request path.

anyinfer.context.compact_history

compact_history(
    messages: Iterable[Message],
    *,
    max_tokens: int,
    estimator: TokenEstimator | None = None,
    keep_recent: int = DEFAULT_KEEP_RECENT,
    keep_system: bool = True,
    observer: Observer | None = None,
) -> HistoryCompaction

Shrink a conversation to fit a token budget without invalidating it.

Three passes over the unprotected middle, cheapest loss first: tool-result payloads are elided, then long text payloads, then plain messages are dropped outright. Each pass stops the moment the conversation fits, so a transcript that needs one large tool result elided loses exactly that and nothing else.

Parameters:

Name Type Description Default
messages Iterable[Message]

The conversation so far.

required
max_tokens int

The budget. Normally client.budget(...).remaining_tokens less whatever the next turn will add — an explicit number, because an unknown window stays unknown.

required
estimator TokenEstimator | None

Token counting strategy; defaults to the byte heuristic.

None
keep_recent int

Trailing messages held at full fidelity.

DEFAULT_KEEP_RECENT
keep_system bool

Whether system messages are protected wherever they appear. Leave this on unless the application's system prompt is genuinely disposable.

True
observer Observer | None

Receives a ContextReduced event describing the outcome.

None

Returns:

Type Description
HistoryCompaction

The HistoryCompaction. Check fits: a conversation whose protected messages

HistoryCompaction

alone exceed the budget comes back unchanged and honest rather than mutilated.

Raises:

Type Description
ValueError

On a non-positive budget or a negative keep_recent.

anyinfer.context.HistoryCompaction dataclass

HistoryCompaction(
    messages: tuple[Message, ...],
    original_count: int,
    dropped_count: int,
    elided_results: int,
    elided_texts: int,
    estimated_tokens: int,
    original_tokens: int,
    max_tokens: int,
    fits: bool,
)

What compaction produced, and what it cost.

Attributes:

Name Type Description
messages tuple[Message, ...]

The compacted conversation, ready to send.

original_count int

Messages offered.

dropped_count int

Messages removed entirely.

elided_results int

Tool results whose payload was replaced by a marker.

elided_texts int

Text parts whose payload was replaced by a marker.

estimated_tokens int

Planning-side estimate of the compacted conversation.

original_tokens int

The same estimate before compaction.

max_tokens int

The budget it was held to.

fits bool

Whether the result is within that budget. False means the protected messages alone exceed it, and no further compaction was available — the caller decides what to do, because dropping a system prompt or the current turn is not a decision a library should make quietly.

changed property

changed: bool

Whether anything was dropped or elided at all.

complete property

complete: bool

Whether the conversation reached the model intact.

saved_tokens property

saved_tokens: int

How many planning tokens compaction recovered.

metadata

metadata() -> dict[str, Any]

The full machine-readable record, for logging or a debug pane.

summary

summary() -> str

A one-line, content-free description of what happened.

Safe to show a user or write to a log: counts and ceilings only, never message text.

event

event() -> ContextReduced

Build the telemetry event describing this compaction.

anyinfer.context.DEFAULT_KEEP_RECENT module-attribute

DEFAULT_KEEP_RECENT = 6

Trailing messages held at full fidelity.

Six covers a tool call, its result, and the exchange around them — enough that the model is never asked to continue from a turn it cannot see.

anyinfer.HistoryPolicy dataclass

HistoryPolicy(
    enabled: bool = True,
    mode: HistoryMode = "last_resort",
    keep_recent: int = 6,
    keep_system: bool = True,
)

Opt-in conversation compaction, applied by the client on the request path.

A prompt that outgrows the window has two possible answers: send it somewhere with a bigger window, or make it smaller. The router has always owned the first (Route.context_window_targets). This owns the second, at the same layer, so the Python API, the command line, and the OpenAI-compatible frontend all behave the same way, because all three are the same client wearing different skins.

Compaction is never silent: it emits a ContextReduced telemetry event, and it is off unless a policy is supplied.

Attributes:

Name Type Description
enabled bool

Whether to compact at all. Present so a configuration file can turn the policy off without deleting how it was tuned.

mode HistoryMode

last_resort compacts only after the route's context-overflow chain is exhausted, so a larger-window model is always preferred to losing history. proactive compacts to fit the resolved target before dispatch, trading that preference for one fewer failed preflight.

keep_recent int

Trailing messages held at full fidelity.

keep_system bool

Whether system messages are protected wherever they appear.

See anyinfer.context.compact_history for the rules compaction follows and for the same behaviour as a function you call yourself.

active property

active: bool

Whether this policy will actually compact anything.

__post_init__

__post_init__() -> None

Reject a policy that cannot be applied.

Raises:

Type Description
ValueError

On an unknown mode or a negative recent window.

Ranking

Public so it can be replaced: the shipped ranker is lexical, and an application needing semantic retrieval ranks its own documents and passes the result through.

anyinfer.context.rank

Lexical relevance ranking.

A BM25-style scorer, deliberately lexical and dependency-free: term frequency saturated and length-normalized, weighted by inverse document frequency, plus two signals that matter a great deal in a code or document corpus and nothing in classical IR — a path match outweighs a body match, and well-known anchor files (README, pyproject.toml, ARCHITECTURE) get a small bonus.

What this is not. There are no embeddings and no semantic matching. That is a deliberate boundary — see the concept documentation, and the reason ranking is exposed as a function you can replace rather than hidden inside selection.

Three optional settings narrow the gap without an index or a model. Identifier splitting tokenizes resolve_credentials as its parts as well as the whole, so a query phrased in words matches an identifier phrased in code. Query expansion ranks once, harvests distinctive terms from the strongest documents, and re-ranks, which does find "login" from "authentication" whenever the two co-occur anywhere in the corpus. And centrality scores a document by its position in the corpus's own import graph, a query-independent signal that is what orders a corpus when the query is weak or absent.

Tokenization is ASCII alphanumeric. Ranking is fully deterministic: ties break on path depth, then path, then digest, so the same corpus and query always produce the same order regardless of the order documents were supplied in.

STOP_WORDS module-attribute

STOP_WORDS = frozenset(
    {
        "a",
        "about",
        "all",
        "an",
        "and",
        "any",
        "are",
        "as",
        "at",
        "be",
        "been",
        "but",
        "by",
        "can",
        "do",
        "does",
        "for",
        "from",
        "get",
        "has",
        "have",
        "how",
        "i",
        "if",
        "in",
        "into",
        "is",
        "it",
        "its",
        "of",
        "on",
        "or",
        "please",
        "should",
        "so",
        "some",
        "than",
        "that",
        "the",
        "their",
        "them",
        "then",
        "there",
        "these",
        "this",
        "to",
        "use",
        "was",
        "were",
        "what",
        "when",
        "where",
        "which",
        "who",
        "why",
        "will",
        "with",
        "would",
        "you",
        "your",
    }
)

Words carrying no retrieval signal, dropped from queries and documents alike.

TERM_SATURATION module-attribute

TERM_SATURATION = 1.2

BM25's k1: how fast repeated occurrences of a term stop adding score.

LENGTH_NORMALIZATION module-attribute

LENGTH_NORMALIZATION = 0.001

Penalty per token of document length, so a long file cannot win on volume alone.

PATH_MATCH_WEIGHT module-attribute

PATH_MATCH_WEIGHT = 4.0

How much more a query term in the path counts than the same term in the body.

Deliberately large: someone asking about credentials almost always means the file named for it, even when a dozen other files mention the word more often.

ANCHOR_SCORE module-attribute

ANCHOR_SCORE = 0.25

Bonus for files that orient a reader regardless of the query.

ANCHOR_NAMES module-attribute

ANCHOR_NAMES = frozenset(
    {
        "architecture",
        "changelog",
        "contributing",
        "design",
        "overview",
        "readme",
        "cargo.toml",
        "go.mod",
        "package.json",
        "pom.xml",
        "pyproject.toml",
    }
)

Filenames (and stems) worth a small unconditional boost.

SemanticRanker

Bases: Protocol

Caller-supplied relevance scoring for context reduction.

The default ranking is lexical and offline on purpose; this protocol is the opt-in seam for a semantic ranker backed by a rerank model. Scores are keyed by ContextDocument.path — a document absent from the mapping scores 0.0. Scores are only compared against each other within one call, never persisted.

Implementations live outside this package (context reduction is a leaf consumer and never imports the client); anyinfer.semantic_ranker builds one from a client and a rerank target.

scores

scores(
    documents: Sequence[ContextDocument], query: str
) -> Mapping[str, float]

Score every document's relevance to query, keyed by document path.

tokenize

tokenize(
    text: str, *, split_identifiers: bool = False
) -> list[str]

Split text into lowercase alphanumeric terms, dropping stop words.

ASCII-only by design — the tradeoff is stated in the module documentation.

Parameters:

Name Type Description Default
text str

What to tokenize.

required
split_identifiers bool

Also emit the parts of compound identifiers. resolveCredentials and resolve_credentials both yield the compound and resolve and credentials, so a query written in words matches an identifier written in code. The compound is kept as well, so an exact match on the full identifier still scores highest.

False

Returns:

Type Description
list[str]

The terms, in order of appearance.

build_rank_cache

build_rank_cache(
    documents: Iterable[ContextDocument],
    *,
    split_identifiers: bool = False,
) -> RankCache

Precompute term statistics for a corpus.

Pass the result to rank() on subsequent queries over the same corpus. The caller owns invalidation; see RankCache.

Parameters:

Name Type Description Default
documents Iterable[ContextDocument]

The corpus.

required
split_identifiers bool

Tokenize compound identifiers into their parts as well. Must match the setting ranking will use; rank() rebuilds a cache that disagrees.

False

Returns:

Type Description
RankCache

The statistics.

score_document

score_document(
    document: ContextDocument,
    query_terms: Mapping[str, float],
    cache: RankCache,
) -> float

Score one document against a query.

Parameters:

Name Type Description Default
document ContextDocument

The candidate.

required
query_terms Mapping[str, float]

Query terms mapped to their weights. Counter(tokenize(query)) for a plain query; expand_query produces a weighted one.

required
cache RankCache

Corpus statistics covering this document.

required

Returns:

Type Description
float

A non-negative relevance score. Zero means nothing matched, which is still a

float

valid candidate, just an unranked one.

query_terms

query_terms(
    query: str, *, tuning: ContextTuning = DEFAULT_TUNING
) -> dict[str, float]

Tokenize a query into weighted terms, without expansion.

The base a expand_query call starts from, and what score_document wants when no expansion is configured.

expand_query

expand_query(
    query: str,
    documents: Sequence[ContextDocument],
    *,
    cache: RankCache,
    tuning: ContextTuning = DEFAULT_TUNING,
) -> dict[str, float]

Add distinctive terms from the strongest documents to the query.

Pseudo-relevance feedback: rank once against the query as written, take the top documents on faith, and harvest the terms that make them distinctive — high frequency within that set, low frequency across the corpus. Those terms join the query at a reduced weight and everything is ranked again.

This is the lexical answer to vocabulary mismatch. It has no index and no model, and it finds a file that says "login" from a query that says "authentication" whenever some document in the corpus uses both. It also inherits the classic failure mode: if the top documents are wrong, expansion makes them wronger, which is why expansion_weight defaults well below one.

Parameters:

Name Type Description Default
query str

The query as written.

required
documents Sequence[ContextDocument]

The corpus.

required
cache RankCache

Statistics for that corpus.

required
tuning ContextTuning

Supplies expansion_terms, feedback_documents, and expansion_weight.

DEFAULT_TUNING

Returns:

Type Description
dict[str, float]

Term weights, with the original query terms at full weight. Returns the

dict[str, float]

unexpanded terms when expansion is disabled, the query is empty, or the corpus

dict[str, float]

has nothing to harvest.

salience

salience(
    documents: Sequence[ContextDocument],
    *,
    tuning: ContextTuning = DEFAULT_TUNING,
) -> dict[str, float]

Score documents by their centrality in the corpus's own import graph.

An edge runs from a document to every document whose filename stem it imports. The stationary distribution of a damped random walk over those edges answers "what does this corpus depend on?", which is query-independent, and therefore what orders a corpus when the query is weak or missing entirely. Ranking with an empty query otherwise falls through to the path tie-break, which is arbitrary.

Parameters:

Name Type Description Default
documents Sequence[ContextDocument]

The corpus.

required
tuning ContextTuning

Supplies salience_damping and salience_iterations.

DEFAULT_TUNING

Returns:

Type Description
dict[str, float]

Path to a score in [0, 1], normalized so the most central document scores

dict[str, float]

one. An empty mapping when the corpus has no resolvable edges at all, so callers

dict[str, float]

can skip the blend entirely.

rank

rank(
    documents: Sequence[ContextDocument],
    query: str,
    *,
    rank_cache: RankCache | None = None,
    tuning: ContextTuning = DEFAULT_TUNING,
    carry_over: Iterable[str] = (),
) -> list[ContextDocument]

Order documents by relevance, pinned ones first.

Parameters:

Name Type Description Default
documents Sequence[ContextDocument]

The corpus.

required
query str

What the app is asking about. An empty query ranks everything at zero, leaving the deterministic tie-break as the order — unless tuning.salience_weight is set, which is exactly what that setting is for.

required
rank_cache RankCache | None

Precomputed statistics for this corpus; built on the fly when absent, and rebuilt when its tokenization disagrees with tuning.

None
tuning ContextTuning

Advanced settings. Defaults reproduce the plain lexical ranker.

DEFAULT_TUNING
carry_over Iterable[str]

Paths an earlier reduction already sent unchanged. Each receives tuning.carry_over_bonus, which keeps a turn's selection, and therefore its rendered prefix — stable enough for a prompt cache to hit.

()

Returns:

Type Description
list[ContextDocument]

A new list, most relevant first. Ordering is total and deterministic: every

list[ContextDocument]

pinned document precedes every unpinned one, then higher score, then shallower

list[ContextDocument]

path, then path, then digest.

scores_for

scores_for(
    documents: Sequence[ContextDocument],
    query: str,
    *,
    cache: RankCache,
    tuning: ContextTuning = DEFAULT_TUNING,
) -> dict[str, float]

Relevance scores keyed by path, for selectors that need magnitudes not order.

rank() returns an ordering, which is all a greedy fill needs. Density ordering and the diversity penalty need the numbers themselves.

anyinfer.context.SemanticRanker

Bases: Protocol

Caller-supplied relevance scoring for context reduction.

The default ranking is lexical and offline on purpose; this protocol is the opt-in seam for a semantic ranker backed by a rerank model. Scores are keyed by ContextDocument.path — a document absent from the mapping scores 0.0. Scores are only compared against each other within one call, never persisted.

Implementations live outside this package (context reduction is a leaf consumer and never imports the client); anyinfer.semantic_ranker builds one from a client and a rerank target.

scores

scores(
    documents: Sequence[ContextDocument], query: str
) -> Mapping[str, float]

Score every document's relevance to query, keyed by document path.

anyinfer.semantic_ranker

semantic_ranker(
    client: Client,
    target: Target,
    *,
    batch: BatchPolicy | None = None,
) -> SemanticRanker

Build a SemanticRanker for anyinfer.context.select from a rerank target.

One rerank call per reduction, spending real provider usage — which is exactly why context reduction does not do this by default. Reduction is synchronous, so this takes the synchronous Client.

Parameters:

Name Type Description Default
client Client

An open synchronous client configured with the target's provider.

required
target Target

A rerank-capable target, e.g. "cohere:rerank-v3.5".

required
batch BatchPolicy | None

Batching policy for corpora larger than the provider's document limit. Splitting a rerank produces chunk-local scores, so enabling rerank_cross_batch here trades global comparability for coverage — the result's warning says so.

None

Returns:

Type Description
SemanticRanker

An object satisfying the SemanticRanker protocol.

anyinfer.context.build_rank_cache

build_rank_cache(
    documents: Iterable[ContextDocument],
    *,
    split_identifiers: bool = False,
) -> RankCache

Precompute term statistics for a corpus.

Pass the result to rank() on subsequent queries over the same corpus. The caller owns invalidation; see RankCache.

Parameters:

Name Type Description Default
documents Iterable[ContextDocument]

The corpus.

required
split_identifiers bool

Tokenize compound identifiers into their parts as well. Must match the setting ranking will use; rank() rebuilds a cache that disagrees.

False

Returns:

Type Description
RankCache

The statistics.

anyinfer.context.tokenize

tokenize(
    text: str, *, split_identifiers: bool = False
) -> list[str]

Split text into lowercase alphanumeric terms, dropping stop words.

ASCII-only by design — the tradeoff is stated in the module documentation.

Parameters:

Name Type Description Default
text str

What to tokenize.

required
split_identifiers bool

Also emit the parts of compound identifiers. resolveCredentials and resolve_credentials both yield the compound and resolve and credentials, so a query written in words matches an identifier written in code. The compound is kept as well, so an exact match on the full identifier still scores highest.

False

Returns:

Type Description
list[str]

The terms, in order of appearance.

anyinfer.context.expand_query

expand_query(
    query: str,
    documents: Sequence[ContextDocument],
    *,
    cache: RankCache,
    tuning: ContextTuning = DEFAULT_TUNING,
) -> dict[str, float]

Add distinctive terms from the strongest documents to the query.

Pseudo-relevance feedback: rank once against the query as written, take the top documents on faith, and harvest the terms that make them distinctive — high frequency within that set, low frequency across the corpus. Those terms join the query at a reduced weight and everything is ranked again.

This is the lexical answer to vocabulary mismatch. It has no index and no model, and it finds a file that says "login" from a query that says "authentication" whenever some document in the corpus uses both. It also inherits the classic failure mode: if the top documents are wrong, expansion makes them wronger, which is why expansion_weight defaults well below one.

Parameters:

Name Type Description Default
query str

The query as written.

required
documents Sequence[ContextDocument]

The corpus.

required
cache RankCache

Statistics for that corpus.

required
tuning ContextTuning

Supplies expansion_terms, feedback_documents, and expansion_weight.

DEFAULT_TUNING

Returns:

Type Description
dict[str, float]

Term weights, with the original query terms at full weight. Returns the

dict[str, float]

unexpanded terms when expansion is disabled, the query is empty, or the corpus

dict[str, float]

has nothing to harvest.

anyinfer.context.salience

salience(
    documents: Sequence[ContextDocument],
    *,
    tuning: ContextTuning = DEFAULT_TUNING,
) -> dict[str, float]

Score documents by their centrality in the corpus's own import graph.

An edge runs from a document to every document whose filename stem it imports. The stationary distribution of a damped random walk over those edges answers "what does this corpus depend on?", which is query-independent, and therefore what orders a corpus when the query is weak or missing entirely. Ranking with an empty query otherwise falls through to the path tie-break, which is arbitrary.

Parameters:

Name Type Description Default
documents Sequence[ContextDocument]

The corpus.

required
tuning ContextTuning

Supplies salience_damping and salience_iterations.

DEFAULT_TUNING

Returns:

Type Description
dict[str, float]

Path to a score in [0, 1], normalized so the most central document scores

dict[str, float]

one. An empty mapping when the corpus has no resolvable edges at all, so callers

dict[str, float]

can skip the blend entirely.

Structure and Tiers

anyinfer.context.detect_language

detect_language(path: str) -> str | None

Infer a language from a path's suffix, or None when it is ambiguous.

anyinfer.context.structural_extract

structural_extract(
    content: str, *, language: str | None
) -> str

Reduce a document to its declarations, imports, and headings.

Parameters:

Name Type Description Default
content str

The document text.

required
language str | None

The detected language. None yields no extract — guessing patterns for an unknown language produces noise, not a summary.

required

Returns:

Type Description
str

The extract, or "" when none could be produced. Files under

str

SMALL_FILE_VERBATIM_BYTES are returned whole: they are already their own

str

summary.

anyinfer.context.imported_names

imported_names(
    content: str, *, language: str | None = None
) -> tuple[str, ...]

Names a document imports, as bare identifiers.

The corpus's own dependency graph is derivable from this: a name here that matches another document's filename stem is an edge. Used for query-independent centrality ranking, where "which files does everything else depend on?" is the whole question.

Parameters:

Name Type Description Default
content str

The document text.

required
language str | None

Accepted for symmetry with the rest of this module and to allow future per-language refinement; the current extraction is language-agnostic.

None

Returns:

Type Description
str

Distinct identifiers, in first-appearance order. Path separators, dots, and

...

:: are all split, so from ..context.rank import score yields

tuple[str, ...]

context, rank, and score.

anyinfer.context.is_generated_path

is_generated_path(path: str) -> bool

Whether a path looks machine-generated or vendored.

Offered as a public helper because the decision belongs at collection time — the library never walks a filesystem, but an app deciding what to collect needs the same heuristic AnyInfer would have applied.

anyinfer.context.module_surfaces

module_surfaces(
    documents: Sequence[ContextDocument], *, depth: int = 2
) -> dict[str, str]

Group a corpus into modules and render each one's surface text.

Offered as a public helper for the app-side digest recipe: generate a summary per module with your own client, cache it keyed on the surface's digest, and hand the results back as module_digests. Deterministic, so the cache key is stable.

Parameters:

Name Type Description Default
documents Sequence[ContextDocument]

The corpus.

required
depth int

Path-prefix depth at which to group.

2

Returns:

Type Description
dict[str, str]

Module path to concatenated extract (or content) text, in path order.

anyinfer.context.DEFAULT_ROLLUP_SHARE module-attribute

DEFAULT_ROLLUP_SHARE = 0.45

Share of the token and byte budget reserved for the module rollup.

The default for ContextTuning.rollup_share, which is what actually applies.

Compaction

The fidelity between a structural extract and a whole file.

anyinfer.context.compact_source

compact_source(
    content: str,
    *,
    language: str | None = None,
    path: str | None = None,
) -> CompactSource

Remove commentary and blank runs from a document.

Parameters:

Name Type Description Default
content str

The document text.

required
language str | None

The language, from anyinfer.context.detect_language. Inferred from path when omitted.

None
path str | None

Used to infer the language when one was not supplied.

None

Returns:

Type Description
CompactSource

The CompactSource. An unknown language, or one with no commentary syntax,

CompactSource

yields the input with blank runs collapsed and nothing else touched — a safe

CompactSource

no-op is always better than a guess at foreign syntax.

anyinfer.context.CompactSource dataclass

CompactSource(
    text: str, original_lines: int, elided_lines: int
)

A document with its commentary removed.

Attributes:

Name Type Description
text str

The compacted content.

original_lines int

Line count before compaction.

elided_lines int

How many lines were removed. Zero means compaction found nothing to drop, and text equals the input.

is_reduced property

is_reduced: bool

Whether compaction actually removed anything.

anyinfer.context.supports_compaction

supports_compaction(language: str | None) -> bool

Whether compaction knows how to shorten this language.

Parameters:

Name Type Description Default
language str | None

A language name from anyinfer.context.detect_language.

required

Returns:

Type Description
bool

Whether compact_source can do better than returning its input.

Chunking

anyinfer.context.Chunk dataclass

Chunk(
    document: ContextDocument,
    text: str,
    index: int,
    start_line: int,
    end_line: int,
)

One span of a document.

Attributes:

Name Type Description
document ContextDocument

The document this came from.

text str

The span's text.

index int

Position within the document, from zero.

start_line int

First line of the span, 1-based and inclusive.

end_line int

Last line of the span, 1-based and inclusive.

anyinfer.context.split_document

split_document(
    document: ContextDocument,
    *,
    chunk_tokens: int = DEFAULT_CHUNK_TOKENS,
) -> list[Chunk]

Split a document into boundary-aware chunks with line spans.

Prefers the last blank line within budget, falls back to the last line break, and hard-cuts only when neither lands past a quarter of the budget.

Parameters:

Name Type Description Default
document ContextDocument

The document to split.

required
chunk_tokens int

Target chunk size in planning tokens.

DEFAULT_CHUNK_TOKENS

Returns:

Type Description
list[Chunk]

Chunks in document order. A document shorter than one chunk yields exactly one.

anyinfer.context.DEFAULT_CHUNK_TOKENS module-attribute

DEFAULT_CHUNK_TOKENS = 512

Target chunk size for sub-document splitting, shared by packed and distill.

Large enough for a whole function, small enough to pack several. The canonical home for this default; anyinfer.context.pack imports it rather than redefining it, since a chunk size chosen for packed's splitter is the same number ContextTuning.chunk_tokens means for distill.

Distillation

The only reduction that spends inference. See distill a corpus for the cookbook.

anyinfer.context.distill

The distill strategy: map/reduce a corpus that will never fit.

The other strategies decide what to drop. This one reads everything and writes something shorter: each chunk is summarized against the query (the map phase), then the notes are synthesized into one answer (the reduce phase).

It is separated from anyinfer.context.select by construction, because it is the one strategy that spends money. It takes your client, issues real generation calls, and reports the count and aggregate usage so the multiplier is never a surprise.

Two properties distinguish this from a naive map/reduce. Reduction is hierarchical: if the map notes together exceed the target's window, they are reduced in batches and the batch summaries reduced again, rather than being sent in one overflowing request. And a deterministic reducer can replace the reduce call entirely, so an application that merges structurally pays for the map phase only.

Prompts here are mechanical scaffolding — "here is chunk 3 of 9, take notes" — not application prose. You own the question; override map_instructions and reduce_instructions to own the framing too.

DEFAULT_CONCURRENCY module-attribute

DEFAULT_CONCURRENCY = 4

Map calls in flight at once. Bounded because a fan-out is someone's rate limit.

SupportsGenerate

Bases: Protocol

The slice of an async client distill() needs.

A structural protocol rather than an import, so this subpackage never depends on the client — anyinfer.AsyncClient satisfies it as-is.

generate async

generate(
    messages: Any, *, target: str, **kwargs: Any
) -> Generation

Generate one result.

budget

budget(
    messages: Any, *, target: str, **kwargs: Any
) -> ContextBudget

Compute a context budget without issuing a request.

SupportsGenerateSync

Bases: Protocol

The synchronous mirror of SupportsGenerate, satisfied by anyinfer.Client.

generate

generate(
    messages: Any, *, target: str, **kwargs: Any
) -> Generation

Generate one result.

budget

budget(
    messages: Any, *, target: str, **kwargs: Any
) -> ContextBudget

Compute a context budget without issuing a request.

Distillation dataclass

Distillation(
    text: str,
    chunk_count: int,
    calls: int,
    usage: Usage,
    reduce_depth: int = 1,
    notes: tuple[str, ...] = (),
)

What a distillation produced, and what it cost.

Attributes:

Name Type Description
text str

The synthesized answer.

chunk_count int

How many chunks the source was split into.

calls int

Total generation calls spent, map and reduce together. This is the multiplier over a single request.

usage Usage

Merged usage across every call, including cost when providers report it.

reduce_depth int

1 for a single-pass reduce; higher when notes were reduced in batches and the batch summaries reduced again.

notes tuple[str, ...]

The intermediate map outputs. Payload-bearing — excluded from repr and never placed in telemetry.

summary

summary() -> str

A one-line, content-free description of the run.

event

event(*, max_tokens: int) -> ContextReduced

Build the telemetry event describing this distillation.

distill async

distill(
    source: str | Iterable[ContextDocument],
    query: str,
    *,
    client: SupportsGenerate,
    target: str,
    max_output_tokens: int = 1024,
    chunk_tokens: int | None = None,
    concurrency: int = DEFAULT_CONCURRENCY,
    map_instructions: str | None = None,
    reduce_instructions: str | None = None,
    reducer: Callable[[Sequence[str]], str] | None = None,
    observer: Observer | None = None,
) -> Distillation

Summarize a corpus larger than the window by mapping and reducing over it.

Parameters:

Name Type Description Default
source str | Iterable[ContextDocument]

Raw text, or documents. Documents split per document, because a document boundary is a natural chunk boundary.

required
query str

What the summary should answer.

required
client SupportsGenerate

Anything satisfying SupportsGenerate — normally an anyinfer.AsyncClient.

required
target str

Where to send the calls.

required
max_output_tokens int

Ceiling on the final answer.

1024
chunk_tokens int | None

Chunk size. Derived from the target's remaining budget when omitted.

None
concurrency int

Map calls in flight at once.

DEFAULT_CONCURRENCY
map_instructions str | None

Replaces the default note-taking instruction.

None
reduce_instructions str | None

Replaces the default synthesis instruction.

None
reducer Callable[[Sequence[str]], str] | None

Merge the notes deterministically instead of with a reduce call. Saves every reduce call, and makes the merge reproducible.

None
observer Observer | None

Receives a ContextReduced event when the run finishes.

None

Returns:

Type Description
Distillation

The Distillation.

Raises:

Type Description
ConfigError

When chunk_tokens is omitted and the target's context window is unknown. An unknown window stays unknown — the caller chooses the number.

distill_sync

distill_sync(
    source: str | Iterable[ContextDocument],
    query: str,
    *,
    client: SupportsGenerateSync,
    target: str,
    max_output_tokens: int = 1024,
    chunk_tokens: int | None = None,
    map_instructions: str | None = None,
    reduce_instructions: str | None = None,
    reducer: Callable[[Sequence[str]], str] | None = None,
    observer: Observer | None = None,
) -> Distillation

Run distill() sequentially against a synchronous client.

Chunks are processed one at a time: concurrency is the async path's feature, and a sync caller that wants it should use distill() with an anyinfer.AsyncClient.

Args and returns are as distill(), minus concurrency.

Raises:

Type Description
ConfigError

When chunk_tokens is omitted and the window is unknown.

anyinfer.context.distill_sync

distill_sync(
    source: str | Iterable[ContextDocument],
    query: str,
    *,
    client: SupportsGenerateSync,
    target: str,
    max_output_tokens: int = 1024,
    chunk_tokens: int | None = None,
    map_instructions: str | None = None,
    reduce_instructions: str | None = None,
    reducer: Callable[[Sequence[str]], str] | None = None,
    observer: Observer | None = None,
) -> Distillation

Run distill() sequentially against a synchronous client.

Chunks are processed one at a time: concurrency is the async path's feature, and a sync caller that wants it should use distill() with an anyinfer.AsyncClient.

Args and returns are as distill(), minus concurrency.

Raises:

Type Description
ConfigError

When chunk_tokens is omitted and the window is unknown.

anyinfer.context.Distillation dataclass

Distillation(
    text: str,
    chunk_count: int,
    calls: int,
    usage: Usage,
    reduce_depth: int = 1,
    notes: tuple[str, ...] = (),
)

What a distillation produced, and what it cost.

Attributes:

Name Type Description
text str

The synthesized answer.

chunk_count int

How many chunks the source was split into.

calls int

Total generation calls spent, map and reduce together. This is the multiplier over a single request.

usage Usage

Merged usage across every call, including cost when providers report it.

reduce_depth int

1 for a single-pass reduce; higher when notes were reduced in batches and the batch summaries reduced again.

notes tuple[str, ...]

The intermediate map outputs. Payload-bearing — excluded from repr and never placed in telemetry.

summary

summary() -> str

A one-line, content-free description of the run.

event

event(*, max_tokens: int) -> ContextReduced

Build the telemetry event describing this distillation.

anyinfer.context.SupportsGenerate

Bases: Protocol

The slice of an async client distill() needs.

A structural protocol rather than an import, so this subpackage never depends on the client — anyinfer.AsyncClient satisfies it as-is.

generate async

generate(
    messages: Any, *, target: str, **kwargs: Any
) -> Generation

Generate one result.

budget

budget(
    messages: Any, *, target: str, **kwargs: Any
) -> ContextBudget

Compute a context budget without issuing a request.

Rendering

The envelope format, exposed for applications that parse reduced context back out of stored transcripts.

anyinfer.context.render_corpus

render_corpus(blocks: Iterable[str]) -> str

Wrap rendered blocks in the corpus element.

anyinfer.context.render_file_block

render_file_block(document: ContextDocument) -> str

Render a whole document.

anyinfer.context.render_extract_block

render_extract_block(document: ContextDocument) -> str

Render a document's structural extract.

anyinfer.context.render_chunk_block

render_chunk_block(
    document: ContextDocument,
    text: str,
    start_line: int,
    end_line: int,
) -> str

Render one contiguous span of a document, with its line range.

anyinfer.context.render_compact_block

render_compact_block(
    document: ContextDocument,
    text: str,
    *,
    elided_lines: int,
) -> str

Render a document with its commentary removed, saying how much was removed.

A distinct element from render_file_block on purpose: a reader must be able to tell a whole file from a shortened one, and elided_lines makes the shortening a number rather than an impression.

anyinfer.context.render_duplicate_block

render_duplicate_block(
    path: str, canonical: str, *, identical: bool
) -> str

Render a pointer from a collapsed document to the one that represents it.

identical="true" means byte-for-byte, and nothing was lost. "false" means the documents were merely similar above the configured threshold, and this one's differences are not in the envelope — a real loss of fidelity, stated rather than implied.

anyinfer.context.ENVELOPE_FORMAT module-attribute

ENVELOPE_FORMAT = 1

Version stamped on every rendered wrapper.

Bumped when an existing element's meaning changes, not when a new one is added: a reader that ignores unknown elements keeps working across additions, which is the point of declaring the version at all.